├── .slugignore ├── .dockerignore ├── assets ├── robots.txt ├── media │ ├── logo.png │ ├── favicon.ico │ └── signature.png └── humans.txt ├── client ├── components │ ├── login │ │ ├── index.js │ │ └── LoginBox.js │ ├── questionnaire │ │ ├── index.js │ │ ├── widgets │ │ │ ├── FoodPreferences.js │ │ │ ├── food-preferences │ │ │ │ ├── FoodCategorySelector.spec.js │ │ │ │ ├── FoodCategorySelector.js │ │ │ │ ├── FoodItemSelector.spec.js │ │ │ │ └── FoodItem.js │ │ │ ├── FoodPreferences.spec.js │ │ │ ├── Household.js │ │ │ └── household │ │ │ │ └── HouseholdRow.js │ │ ├── questionnaire.css │ │ └── Section.js │ ├── questionnaire-view │ │ ├── index.js │ │ ├── widgets │ │ │ ├── FoodPreferencesView.js │ │ │ └── HouseholdView.js │ │ └── QuestionnaireView.js │ ├── error │ │ ├── index.js │ │ ├── Error.js │ │ ├── ErrorWrapper.spec.js │ │ └── ErrorWrapper.js │ ├── box │ │ ├── index.js │ │ ├── BoxHeader.js │ │ ├── Box.js │ │ └── BoxBody.js │ ├── page │ │ ├── index.js │ │ ├── Page.js │ │ ├── PageHeader.js │ │ ├── PageBody.js │ │ └── Page.spec.js │ ├── form │ │ ├── index.js │ │ └── Checkbox.js │ ├── router │ │ ├── SwitchWithNotFound.js │ │ ├── userOrRedirect.js │ │ ├── requireRole.js │ │ ├── ownerOrAdmin.js │ │ ├── requireRole.spec.js │ │ └── guestOrRedirect.js │ ├── LoadingWrapper.js │ ├── FoodbankLogo.js │ ├── ClientStatusLabel.js │ ├── AssistanceInfo.js │ └── ClientCreateSuccess.js ├── modules │ ├── page │ │ ├── types.js │ │ ├── components │ │ │ ├── quill-toolbar.css │ │ │ ├── QuillSubjectToolbar.js │ │ │ └── PageSelector.js │ │ └── PageRouter.js │ ├── questionnaire │ │ ├── types.js │ │ ├── questionnaire.css │ │ ├── QuestionnaireRouter.js │ │ ├── components │ │ │ └── QuestionnaireSelector.js │ │ └── reducers │ │ │ └── editor │ │ │ ├── fields.js │ │ │ └── fields.spec.js │ ├── driver │ │ ├── images │ │ │ ├── home.png │ │ │ ├── home2.png │ │ │ ├── car-green.png │ │ │ ├── car-red.png │ │ │ ├── car-red2.png │ │ │ ├── m1-cyan.png │ │ │ ├── m1-pink.png │ │ │ ├── m1-purple.png │ │ │ ├── car-green2.png │ │ │ ├── client-marker.png │ │ │ ├── driver-marker.png │ │ │ ├── gm-marker-blue.png │ │ │ ├── gm-marker-grey.png │ │ │ ├── gm-marker-pink.png │ │ │ ├── gm-marker-green.png │ │ │ ├── gm-marker-orange.png │ │ │ └── gm-marker-yellow.png │ │ ├── reducers │ │ │ └── index.js │ │ ├── components │ │ │ ├── driver-assignment │ │ │ │ ├── AssignmentForm.js │ │ │ │ ├── FilterCustomers.js │ │ │ │ ├── DriverList.js │ │ │ │ └── RouteSummary.js │ │ │ ├── Map.js │ │ │ └── route │ │ │ │ ├── RouteOrder.js │ │ │ │ └── googlemap │ │ │ │ └── RouteMap.js │ │ ├── DriverRouter.js │ │ └── driver.css │ ├── donor │ │ ├── donor.css │ │ └── components │ │ │ ├── DonationItems.js │ │ │ └── DonationItemRow.js │ ├── users │ │ ├── img │ │ │ └── buttons │ │ │ │ ├── github.png │ │ │ │ ├── google.png │ │ │ │ ├── twitter.png │ │ │ │ ├── facebook.png │ │ │ │ └── linkedin.png │ │ ├── css │ │ │ └── users.css │ │ └── components │ │ │ ├── signup.css │ │ │ └── ConfirmNewGoogleAccount.js │ ├── customer │ │ └── customer.css │ ├── core │ │ ├── reducers │ │ │ ├── index.js │ │ │ └── dialog.spec.js │ │ ├── components │ │ │ ├── errors │ │ │ │ ├── NotFound.js │ │ │ │ ├── Unauthorized.js │ │ │ │ ├── ServerError.js │ │ │ │ └── Error.js │ │ │ ├── Home.js │ │ │ ├── sidebar │ │ │ │ ├── SidebarMenuItem.js │ │ │ │ ├── SidebarMenuGroup.js │ │ │ │ ├── Sidebar.js │ │ │ │ └── SidebarMenu.js │ │ │ ├── navbar │ │ │ │ ├── ClientNavbarMenuItem.js │ │ │ │ ├── AdminNavbar.js │ │ │ │ ├── NavbarMenu.js │ │ │ │ ├── ClientNavbarMenuGroup.js │ │ │ │ └── ClientNavbar.js │ │ │ ├── Footer.spec.js │ │ │ ├── Footer.js │ │ │ └── Header.js │ │ └── css │ │ │ └── core.css │ ├── food │ │ ├── components │ │ │ ├── Packing.js │ │ │ └── Inventory.js │ │ ├── reducers │ │ │ └── index.js │ │ └── FoodRouter.js │ ├── settings │ │ ├── SettingsRouter.js │ │ └── components │ │ │ ├── Keys.js │ │ │ ├── Settings.js │ │ │ └── Images.js │ └── volunteer │ │ └── components │ │ └── RoleSelector.js ├── .babelrc ├── lib │ ├── get-address.js │ ├── user-client-role.js │ └── test-helpers.js ├── entry.test.js ├── store │ ├── entities.js │ ├── selectors.spec.js │ ├── middleware │ │ └── notify.js │ ├── index.js │ ├── entities.spec.js │ └── reducer.js ├── index.html └── app.js ├── server ├── index.js ├── config │ ├── env │ │ ├── development.js │ │ ├── test.js │ │ ├── production.js │ │ ├── secrets-template.js │ │ └── all.js │ ├── index.js │ ├── strategies │ │ └── local.js │ └── passport.js ├── models │ ├── location-schema.js │ ├── notification.js │ ├── index.js │ ├── media.js │ ├── page.js │ ├── food.js │ ├── package.js │ ├── donation.js │ ├── questionnaire.js │ ├── donor.js │ └── settings.js ├── lib │ ├── enforce-ssl-middleware.js │ ├── seed │ │ └── address-generator.js │ ├── media-helpers.js │ ├── mapquest-client.js │ ├── geolocate.js │ ├── notification-sender.js │ ├── update-linked-fields.js │ ├── websocket-middleware.js │ └── media-helpers.spec.js ├── controllers │ ├── users.js │ ├── media.js │ ├── users │ │ └── authorization.js │ └── questionnaire.js ├── routes │ ├── settings.js │ ├── page.js │ ├── media.js │ ├── donation.js │ ├── delivery.js │ ├── questionnaire.js │ ├── packing.js │ ├── donor.js │ ├── volunteer.js │ ├── api.spec.js │ └── customer.js ├── entry.test.js └── server.js ├── flow-typed └── npm │ ├── Empty.js │ ├── flow-bin_v0.x.x.js │ ├── isomorphic-fetch_v2.x.x.js │ ├── uuid_v3.x.x.js │ ├── font-awesome_vx.x.x.js │ ├── react-dnd-html5-backend_v2.1.x.js │ ├── del_vx.x.x.js │ ├── helmet_vx.x.x.js │ ├── thenify_vx.x.x.js │ ├── whatwg-fetch_vx.x.x.js │ ├── gulp-babel_vx.x.x.js │ ├── url-loader_vx.x.x.js │ ├── mime-types_vx.x.x.js │ ├── compression_vx.x.x.js │ ├── file-loader_vx.x.x.js │ ├── sinon-chai_vx.x.x.js │ ├── cookie-parser_vx.x.x.js │ ├── babel-preset-flow_vx.x.x.js │ ├── babel-preset-react_vx.x.x.js │ ├── babel-preset-es2015_vx.x.x.js │ ├── babel-preset-stage-0_vx.x.x.js │ ├── chai-as-promised_vx.x.x.js │ ├── sanitize-html_v1.x.x.js │ ├── babel-plugin-transform-async-to-generator_vx.x.x.js │ ├── babel-plugin-transform-object-rest-spread_vx.x.x.js │ ├── striptags_vx.x.x.js │ ├── babel-plugin-transform-es2015-modules-commonjs_vx.x.x.js │ ├── geoposition-to-object_vx.x.x.js │ ├── passport-google-oauth2_vx.x.x.js │ ├── mongoose-auto-increment_vx.x.x.js │ ├── express-socket.io-session_vx.x.x.js │ ├── ghooks_vx.x.x.js │ ├── gulp-nodemon_vx.x.x.js │ ├── prop-types_v15.x.x.js │ ├── babel-register_vx.x.x.js │ ├── postcss-loader_vx.x.x.js │ ├── passport-local_vx.x.x.js │ ├── body-parser_v1.x.x.js │ ├── ignore-styles_vx.x.x.js │ ├── gulp_vx.x.x.js │ ├── supertest_vx.x.x.js │ ├── socket.io_vx.x.x.js │ └── connect-mongo_vx.x.x.js ├── .flowconfig ├── Dockerfile ├── resources ├── azure │ ├── outputs.tf │ └── terraform.tfvars └── nginx │ └── conf.d │ └── pantry-for-good.conf ├── .babelrc ├── .gitignore ├── .csslintrc ├── .nycrc ├── docker-compose.yml ├── DEPLOYMENT.md ├── app.json ├── .editorconfig ├── LICENSE.md ├── .osfg-dir-config.js ├── .eslintrc └── webpack.config.prod.js /.slugignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .git 3 | flow-typed -------------------------------------------------------------------------------- /assets/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /client/components/login/index.js: -------------------------------------------------------------------------------- 1 | export LoginBox from './LoginBox' 2 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | require('./server') 3 | -------------------------------------------------------------------------------- /client/components/questionnaire/index.js: -------------------------------------------------------------------------------- 1 | export Questionnaire from './Questionnaire' 2 | -------------------------------------------------------------------------------- /flow-typed/npm/Empty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @providesModule Empty 3 | */ 4 | module.exports = ''; 5 | -------------------------------------------------------------------------------- /client/components/questionnaire-view/index.js: -------------------------------------------------------------------------------- 1 | export QuestionnaireView from './QuestionnaireView' 2 | -------------------------------------------------------------------------------- /assets/media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/assets/media/logo.png -------------------------------------------------------------------------------- /assets/media/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/assets/media/favicon.ico -------------------------------------------------------------------------------- /client/components/error/index.js: -------------------------------------------------------------------------------- 1 | export Error from './Error' 2 | export ErrorWrapper from './ErrorWrapper' 3 | -------------------------------------------------------------------------------- /assets/media/signature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/assets/media/signature.png -------------------------------------------------------------------------------- /client/modules/page/types.js: -------------------------------------------------------------------------------- 1 | export const editorTypes = { 2 | SUBJECT: 'subjectEditor', 3 | BODY: 'bodyEditor' 4 | } 5 | -------------------------------------------------------------------------------- /client/modules/questionnaire/types.js: -------------------------------------------------------------------------------- 1 | export const dragDropTypes = { 2 | FIELD: 'field', 3 | SECTION: 'section' 4 | } 5 | -------------------------------------------------------------------------------- /client/components/box/index.js: -------------------------------------------------------------------------------- 1 | export Box from './Box' 2 | export BoxBody from './BoxBody' 3 | export BoxHeader from './BoxHeader' 4 | -------------------------------------------------------------------------------- /client/modules/driver/images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/home.png -------------------------------------------------------------------------------- /client/components/page/index.js: -------------------------------------------------------------------------------- 1 | export Page from './Page' 2 | export PageBody from './PageBody' 3 | export PageHeader from './PageHeader' 4 | -------------------------------------------------------------------------------- /client/modules/driver/images/home2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/home2.png -------------------------------------------------------------------------------- /client/modules/donor/donor.css: -------------------------------------------------------------------------------- 1 | /* Tax receipt */ 2 | .modal-content > .invoice-header { 3 | border-bottom: none; 4 | padding-bottom: 0; 5 | } 6 | -------------------------------------------------------------------------------- /client/modules/driver/images/car-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/car-green.png -------------------------------------------------------------------------------- /client/modules/driver/images/car-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/car-red.png -------------------------------------------------------------------------------- /client/modules/driver/images/car-red2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/car-red2.png -------------------------------------------------------------------------------- /client/modules/driver/images/m1-cyan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/m1-cyan.png -------------------------------------------------------------------------------- /client/modules/driver/images/m1-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/m1-pink.png -------------------------------------------------------------------------------- /client/modules/driver/images/m1-purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/m1-purple.png -------------------------------------------------------------------------------- /client/modules/driver/images/car-green2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/car-green2.png -------------------------------------------------------------------------------- /client/modules/users/img/buttons/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/users/img/buttons/github.png -------------------------------------------------------------------------------- /client/modules/users/img/buttons/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/users/img/buttons/google.png -------------------------------------------------------------------------------- /client/modules/users/img/buttons/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/users/img/buttons/twitter.png -------------------------------------------------------------------------------- /client/components/form/index.js: -------------------------------------------------------------------------------- 1 | export FieldGroup from './FieldGroup' 2 | export RFFieldGroup from './RFFieldGroup' 3 | export Checkbox from './Checkbox' 4 | -------------------------------------------------------------------------------- /client/modules/driver/images/client-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/client-marker.png -------------------------------------------------------------------------------- /client/modules/driver/images/driver-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/driver-marker.png -------------------------------------------------------------------------------- /client/modules/driver/images/gm-marker-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/gm-marker-blue.png -------------------------------------------------------------------------------- /client/modules/driver/images/gm-marker-grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/gm-marker-grey.png -------------------------------------------------------------------------------- /client/modules/driver/images/gm-marker-pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/gm-marker-pink.png -------------------------------------------------------------------------------- /client/modules/users/img/buttons/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/users/img/buttons/facebook.png -------------------------------------------------------------------------------- /client/modules/users/img/buttons/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/users/img/buttons/linkedin.png -------------------------------------------------------------------------------- /client/modules/driver/images/gm-marker-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/gm-marker-green.png -------------------------------------------------------------------------------- /client/modules/driver/images/gm-marker-orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/gm-marker-orange.png -------------------------------------------------------------------------------- /client/modules/driver/images/gm-marker-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeCodeCamp/pantry-for-good/HEAD/client/modules/driver/images/gm-marker-yellow.png -------------------------------------------------------------------------------- /server/config/env/development.js: -------------------------------------------------------------------------------- 1 | export default { 2 | db: process.env.MONGODB_URI || 'mongodb://localhost/fb-dev', 3 | sessionSecret: 'foodbank-app' 4 | } 5 | -------------------------------------------------------------------------------- /client/modules/customer/customer.css: -------------------------------------------------------------------------------- 1 | textarea { 2 | resize: none; 3 | } 4 | 5 | .table-header { 6 | background-color: #d9edf7 !important; 7 | font-weight: bold !important; 8 | } -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"], 3 | "env": { 4 | "test": { 5 | "plugins": ["istanbul", "babel-plugin-rewire"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /server/config/env/test.js: -------------------------------------------------------------------------------- 1 | export default { 2 | db: process.env.MONGODB_URI || 'mongodb://localhost:27017/fb-test', 3 | port: 3001, 4 | sessionSecret: 'foodbank-app' 5 | } 6 | 7 | -------------------------------------------------------------------------------- /flow-typed/npm/flow-bin_v0.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 6a5610678d4b01e13bbfbbc62bdaf583 2 | // flow-typed version: 3817bc6980/flow-bin_v0.x.x/flow_>=v0.25.x 3 | 4 | declare module "flow-bin" { 5 | declare module.exports: string; 6 | } 7 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/.* 3 | 4 | [include] 5 | 6 | [libs] 7 | flow-typed/npm 8 | 9 | [lints] 10 | 11 | [options] 12 | module.name_mapper='.*\.\(svg\|png\|jpg\|gif\|css\)$' -> '/flow-typed/npm/EmptyModule.js' -------------------------------------------------------------------------------- /server/config/env/production.js: -------------------------------------------------------------------------------- 1 | if (!process.env.SECRET) throw new Error('environment variable SECRET must be set') 2 | 3 | export default { 4 | db: process.env.MONGODB_URI || 'mongodb://localhost:27017/fb-prod', 5 | sessionSecret: process.env.SECRET 6 | } 7 | -------------------------------------------------------------------------------- /assets/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | -- -- 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | HTML5, CSS3 15 | jQuery, Modernizr 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mhart/alpine-node:8.11.2 2 | 3 | WORKDIR /opt 4 | COPY . /opt 5 | ENV NODE_ENV=production 6 | 7 | RUN cp server/config/env/secrets-template.js server/config/env/secrets.js 8 | 9 | RUN npm install 10 | RUN npm run build 11 | 12 | CMD ["npm", "start"] 13 | EXPOSE 3000 14 | -------------------------------------------------------------------------------- /resources/azure/outputs.tf: -------------------------------------------------------------------------------- 1 | output "hostname" { 2 | value = "${var.hostname}" 3 | } 4 | 5 | output "vm_fqdn" { 6 | value = "${azurerm_public_ip.publicip.fqdn}" 7 | } 8 | 9 | output "ssh_command" { 10 | value = "ssh ${var.admin_username}@${azurerm_public_ip.publicip.fqdn}" 11 | } 12 | -------------------------------------------------------------------------------- /client/modules/users/css/users.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 992px) { 2 | .nav-users { 3 | position: fixed; 4 | } 5 | } 6 | .remove-account-container { 7 | display: inline-block; 8 | position: relative; 9 | } 10 | .btn-remove-account { 11 | position: absolute; 12 | right: 10px; 13 | top: 10px; 14 | } 15 | -------------------------------------------------------------------------------- /server/models/location-schema.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | 3 | const {Schema} = mongoose 4 | 5 | export default Schema({ 6 | lat: { 7 | type: Number, 8 | required: true 9 | }, 10 | lng: { 11 | type: Number, 12 | required: true 13 | } 14 | }, { 15 | _id: false 16 | }) 17 | -------------------------------------------------------------------------------- /client/lib/get-address.js: -------------------------------------------------------------------------------- 1 | import {take} from 'lodash' 2 | 3 | export default (client, num) => { 4 | const fields = client.fields.filter(field => 5 | field.meta && field.meta.type === 'address') 6 | 7 | return take(fields, num || fields.length) 8 | .map(field => field.value) 9 | .join(', ') 10 | } 11 | -------------------------------------------------------------------------------- /resources/azure/terraform.tfvars: -------------------------------------------------------------------------------- 1 | resource_group = "pantry-for-good" 2 | 3 | rg_prefix = "pfg" 4 | 5 | hostname = "pfgvm" 6 | 7 | dns_name = "pantry-for-good" 8 | 9 | location = "ukwest" 10 | 11 | post_deployment_script_url = "https://gist.githubusercontent.com/jspaine/773df27c2ff5cb24e34db839c4755f64/raw/deploy.sh" 12 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets" : [ 3 | "flow" 4 | ], 5 | "plugins": [ 6 | "transform-es2015-modules-commonjs", 7 | "transform-async-to-generator", 8 | "transform-object-rest-spread" 9 | ], 10 | "env": { 11 | "test": { 12 | "plugins": ["istanbul", "babel-plugin-rewire"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/modules/core/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux' 2 | 3 | import dialog, {createSelectors as createDialogSelectors} from './dialog' 4 | 5 | export default combineReducers({ 6 | dialog 7 | }) 8 | 9 | export const createSelectors = path => ({ 10 | dialog: createDialogSelectors(`${path}.dialog`) 11 | }) 12 | -------------------------------------------------------------------------------- /flow-typed/npm/isomorphic-fetch_v2.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 47370d221401bec823c43c3598266e26 2 | // flow-typed version: ec28077c25/isomorphic-fetch_v2.x.x/flow_>=v0.25.x 3 | 4 | 5 | declare module 'isomorphic-fetch' { 6 | declare module.exports: (input: string | Request | URL, init?: RequestOptions) => Promise; 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | heroku_5svv90mx 2 | .DS_Store 3 | .nodemonignore 4 | .sass-cache/ 5 | npm-debug.log 6 | node_modules/ 7 | .vscode 8 | .idea/ 9 | .c9/ 10 | .env 11 | *.swp 12 | .settings/ 13 | typings/ 14 | server/config/env/secrets.js 15 | dist 16 | .nyc_output/ 17 | coverage/ 18 | resources/**/.terraform 19 | *.tfstate 20 | *.tfstate.backup 21 | -------------------------------------------------------------------------------- /client/modules/page/components/quill-toolbar.css: -------------------------------------------------------------------------------- 1 | .ql-picker.ql-placeholder { 2 | width: 118px; 3 | } 4 | 5 | .ql-picker.ql-placeholder > span.ql-picker-label::before { 6 | content: 'Placeholders'; 7 | } 8 | 9 | .ql-picker.ql-placeholder > span.ql-picker-options > span.ql-picker-item::before { 10 | content: attr(data-label); 11 | } 12 | -------------------------------------------------------------------------------- /server/config/env/secrets-template.js: -------------------------------------------------------------------------------- 1 | export default { 2 | gmapsApiKey: process.env.GMAPS_API_KEY || '', 3 | oauth: { 4 | googleClientID: process.env.GOOGLE_CLIENT_ID || '', 5 | googleClientSecret: process.env.GOOGLE_CLIENT_SECRET || '', 6 | }, 7 | sendgrid: { 8 | API_KEY: process.env.SENDGRID_API_KEY || '' 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /client/modules/questionnaire/questionnaire.css: -------------------------------------------------------------------------------- 1 | .nav-stacked.bs-default-nav > li.active > a, 2 | .nav-stacked.bs-default-nav > li.active > a:hover { 3 | background-color: #337ab7; 4 | color: #fff; 5 | border: none; 6 | } 7 | .form-group-no-margin { 8 | margin-bottom: 0; 9 | } 10 | div > .list-group-item:focus { 11 | outline: none; 12 | } 13 | -------------------------------------------------------------------------------- /client/modules/core/components/errors/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import ErrorPage from './Error' 4 | 5 | const NotFoundPage = () => 6 | 12 | 13 | export default NotFoundPage 14 | -------------------------------------------------------------------------------- /client/modules/core/components/errors/Unauthorized.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import ErrorPage from './Error' 4 | 5 | const UnauthorizedPage = () => 6 | 12 | 13 | export default UnauthorizedPage 14 | 15 | -------------------------------------------------------------------------------- /server/lib/enforce-ssl-middleware.js: -------------------------------------------------------------------------------- 1 | import {BadRequestError} from './errors' 2 | 3 | export default function(req, res, next) { 4 | if (req.headers['x-forwarded-proto'] !== 'https') { 5 | if (req.method === 'GET') { 6 | return res.redirect(301, `https://${req.hostname}${req.url}`) 7 | } else { 8 | throw new BadRequestError 9 | } 10 | } 11 | next() 12 | } 13 | -------------------------------------------------------------------------------- /client/components/page/Page.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import P from 'prop-types' 3 | 4 | import LoadingWrapper from '../LoadingWrapper' 5 | 6 | const Page = ({loading, children}) => 7 | 8 | {children} 9 | 10 | 11 | Page.propTypes = { 12 | loading: P.bool, 13 | children: P.node 14 | } 15 | 16 | export default Page 17 | -------------------------------------------------------------------------------- /server/controllers/users.js: -------------------------------------------------------------------------------- 1 | import {extend} from 'lodash' 2 | 3 | import * as authentication from './users/authentication' 4 | import * as authorization from './users/authorization' 5 | import * as password from './users/password' 6 | import * as profile from './users/profile' 7 | 8 | export default extend( 9 | authentication, 10 | authorization, 11 | password, 12 | profile 13 | ) 14 | -------------------------------------------------------------------------------- /client/components/router/SwitchWithNotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Switch, Route} from 'react-router-dom' 3 | 4 | import NotFound from '../../modules/core/components/errors/NotFound' 5 | 6 | const SwitchWithNotFound = ({children, ...props}) => 7 | 8 | {children} 9 | 10 | 11 | 12 | export default SwitchWithNotFound 13 | -------------------------------------------------------------------------------- /.csslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "adjoining-classes": false, 3 | "box-model": false, 4 | "box-sizing": false, 5 | "floats": false, 6 | "font-sizes": false, 7 | "important": false, 8 | "known-properties": false, 9 | "overqualified-elements": false, 10 | "qualified-headings": false, 11 | "regex-selectors": false, 12 | "unique-headings": false, 13 | "universal-selector": false, 14 | "unqualified-attributes": false 15 | } 16 | -------------------------------------------------------------------------------- /client/modules/food/components/Packing.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import {Page, PageBody} from '../../../components/page' 4 | import Packages from './packing/Packages' 5 | import PackingList from './packing/PackingList' 6 | 7 | const Packing = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | ) 15 | 16 | export default Packing 17 | -------------------------------------------------------------------------------- /client/modules/page/PageRouter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Route} from 'react-router-dom' 3 | 4 | import EditPages from './components/EditPages' 5 | import SwitchWithNotFound from '../../components/router/SwitchWithNotFound' 6 | 7 | const PageRouter = ({match}) => 8 | 9 | 10 | 11 | 12 | export default PageRouter 13 | -------------------------------------------------------------------------------- /client/components/box/BoxHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const BoxHeader = ({children, heading}) => 5 |
6 |

7 | {heading} 8 |

9 | {children} 10 |
11 | 12 | BoxHeader.propTypes = { 13 | children: PropTypes.node, 14 | heading: PropTypes.string 15 | } 16 | 17 | 18 | export default BoxHeader 19 | -------------------------------------------------------------------------------- /server/config/index.js: -------------------------------------------------------------------------------- 1 | import {merge} from 'lodash' 2 | 3 | const env = process.env.NODE_ENV || 'development' 4 | 5 | let secrets 6 | try { 7 | secrets = require('./env/secrets').default 8 | } catch (err) { 9 | secrets = require('./env/secrets-template').default 10 | } // eslint-disable-line no-empty 11 | 12 | export default merge( 13 | require('./env/all').default, 14 | require(`./env/${env}`).default, 15 | secrets 16 | ) 17 | -------------------------------------------------------------------------------- /client/lib/user-client-role.js: -------------------------------------------------------------------------------- 1 | import {head, intersection, values} from 'lodash' 2 | 3 | import {clientRoles} from '../../common/constants' 4 | 5 | /** 6 | * get the client type of a user 7 | * 8 | * @param {?object} user 9 | * @returns {(string|undefined)} the client role 10 | */ 11 | function userClientRole(user) { 12 | if (!user) return 13 | return head(intersection(user.roles, values(clientRoles))) 14 | } 15 | 16 | export default userClientRole 17 | -------------------------------------------------------------------------------- /client/modules/questionnaire/QuestionnaireRouter.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Route} from 'react-router-dom' 3 | 4 | import Questionnaire from './Questionnaire' 5 | import SwitchWithNotFound from '../../components/router/SwitchWithNotFound' 6 | 7 | const QuestionnaireRouter = ({match}) => 8 | 9 | 10 | 11 | 12 | export default QuestionnaireRouter 13 | -------------------------------------------------------------------------------- /server/models/notification.js: -------------------------------------------------------------------------------- 1 | 2 | import mongoose from 'mongoose' 3 | 4 | const Schema = mongoose.Schema 5 | 6 | /** 7 | * Notification Schema 8 | */ 9 | export const notificationSchema = new Schema({ 10 | message: { 11 | type: String, 12 | required: true 13 | }, 14 | url: { 15 | type: String, 16 | required: true 17 | }, 18 | date: { 19 | type: Date, 20 | required: true, 21 | default: Date.now() 22 | } 23 | }, { 24 | _id: false 25 | }) 26 | -------------------------------------------------------------------------------- /server/routes/settings.js: -------------------------------------------------------------------------------- 1 | import Router from 'express-promise-router' 2 | 3 | import usersController from '../controllers/users' 4 | import settingsController from '../controllers/settings' 5 | 6 | const {requiresLogin} = usersController 7 | 8 | export default () => { 9 | const settingsRouter = Router({mergeParams: true}) 10 | 11 | settingsRouter.route('/settings') 12 | .post(requiresLogin, settingsController.save) 13 | .get(settingsController.read) 14 | 15 | return settingsRouter 16 | } 17 | -------------------------------------------------------------------------------- /client/components/LoadingWrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const LoadingWrapper = ({loading, children, ...props}) => 5 |
6 | {children} 7 | {loading && 8 |
9 | 10 |
11 | } 12 |
13 | 14 | LoadingWrapper.propTypes = { 15 | loading: PropTypes.bool, 16 | children: PropTypes.node 17 | } 18 | 19 | export default LoadingWrapper 20 | -------------------------------------------------------------------------------- /client/modules/food/components/Inventory.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import FoodCategories from './inventory/FoodCategories' 4 | import FoodItems from './inventory/FoodItems' 5 | 6 | const Inventory = () => ( 7 |
8 |
9 |
10 | 11 |
12 |
13 | 14 |
15 |
16 |
17 | ) 18 | 19 | export default Inventory 20 | -------------------------------------------------------------------------------- /flow-typed/npm/uuid_v3.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: ca3f7f1b46804b3c09c838773ece34ba 2 | // flow-typed version: b43dff3e0e/uuid_v3.x.x/flow_>=v0.32.x 3 | 4 | declare module 'uuid' { 5 | declare function v1(options?: {| 6 | node?: number[], 7 | clockseq?: number, 8 | msecs?: number | Date, 9 | nsecs?: number, 10 | |}, buffer?: number[] | Buffer, offset?: number): string; 11 | declare function v4(options?: {| 12 | random?: number[], 13 | rng?: () => number[] | Buffer, 14 | |}, buffer?: number[] | Buffer, offset?: number): string; 15 | } 16 | -------------------------------------------------------------------------------- /server/models/index.js: -------------------------------------------------------------------------------- 1 | export {default as Customer} from './customer' 2 | export {default as Donation} from './donation' 3 | export {default as Donor} from './donor' 4 | export {default as Food} from './food' 5 | export {default as Media} from './media' 6 | export {default as Page} from './page' 7 | export { 8 | Field as Field, 9 | Section as Section, 10 | Questionnaire as Questionnaire 11 | } from './questionnaire' 12 | export {default as Settings} from './settings' 13 | export {default as User} from './user' 14 | export {default as Volunteer} from './volunteer' 15 | -------------------------------------------------------------------------------- /server/routes/page.js: -------------------------------------------------------------------------------- 1 | import Router from 'express-promise-router' 2 | 3 | import pageController from '../controllers/page' 4 | 5 | export default () => { 6 | const pageRouter = Router({mergeParams: true}) 7 | 8 | pageRouter.route('/admin/pages') 9 | .get(pageController.list) 10 | 11 | pageRouter.route('/admin/pages/:identifier') 12 | .put(pageController.update) 13 | 14 | pageRouter.route('/pages/:identifier') 15 | .get(pageController.read) 16 | 17 | pageRouter.param('identifier', pageController.pageByIdentifier) 18 | 19 | return pageRouter 20 | } 21 | -------------------------------------------------------------------------------- /flow-typed/npm/font-awesome_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 8251376781e3c1d5a6753c525aae7ace 2 | // flow-typed version: <>/font-awesome_v^4.7.0/flow_v0.53.1 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * 'font-awesome' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module 'font-awesome' { 17 | declare module.exports: any; 18 | } 19 | -------------------------------------------------------------------------------- /client/modules/core/components/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import FoodbankLogo from '../../../components/FoodbankLogo' 5 | import ContentPage from '../../../components/ContentPage' 6 | 7 | const Home = ({match}) => 8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | Home.propTypes = { 16 | match: PropTypes.shape({ 17 | url: PropTypes.string 18 | }).isRequired 19 | } 20 | 21 | export default Home 22 | -------------------------------------------------------------------------------- /server/config/env/all.js: -------------------------------------------------------------------------------- 1 | const production = process.env.NODE_ENV === 'production' 2 | 3 | const protocol = production ? 'https' : 'http' 4 | const host = process.env.HOST_NAME || 'localhost' 5 | const port = process.env.PORT || 3000 6 | 7 | const url = `${protocol}://${host}` + production ? '' : `:${port}` 8 | 9 | export default { 10 | protocol: process.env.PROTOCOL || protocol, 11 | host, 12 | port, 13 | sessionCollection: 'sessions', 14 | sessionIdleTimeout: 3600000, 15 | mailFrom: `no-reply@${host}`, 16 | oauth: { 17 | googleCallbackURL: `${url}/api/auth/google/callback` 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/components/questionnaire-view/widgets/FoodPreferencesView.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const FoodPreferencesView = ({model}) => 5 |
6 |
7 | Food Preferences: 8 |
9 | {model.foodPreferences.map(food => food && food.name).join(', ')} 10 |
11 | 12 | FoodPreferencesView.propTypes = { 13 | model: PropTypes.shape({ 14 | foodPreferences: PropTypes.array.isRequired 15 | }).isRequired 16 | } 17 | 18 | export default FoodPreferencesView 19 | -------------------------------------------------------------------------------- /client/modules/food/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux' 2 | 3 | import item, {createSelectors as createItemSelectors} from './item' 4 | import category, {createSelectors as createCategorySelectors} from './category' 5 | import packing, {createSelectors as createPackingSelectors} from './packing' 6 | 7 | export default combineReducers({item, category, packages: packing}) 8 | 9 | export const createSelectors = path => ({ 10 | item: createItemSelectors(`${path}.item`), 11 | category: createCategorySelectors(`${path}.category`), 12 | packing: createPackingSelectors(`${path}.packages`) 13 | }) 14 | -------------------------------------------------------------------------------- /server/routes/media.js: -------------------------------------------------------------------------------- 1 | import Router from 'express-promise-router' 2 | import {upload} from '../lib/media-helpers' 3 | 4 | import mediaController from '../controllers/media' 5 | 6 | export default () => { 7 | const mediaRouter = Router({mergeParams: true}) 8 | 9 | mediaRouter.route('/admin/media/upload') 10 | .post(upload.fields([ 11 | {name: 'logo', maxCount: 1}, 12 | {name: 'signature', maxCount: 1}, 13 | {name: 'favicon', maxCount: 1} 14 | ]), mediaController.upload) 15 | 16 | mediaRouter.route('/media') 17 | .get(mediaController.read) 18 | 19 | return mediaRouter 20 | } 21 | -------------------------------------------------------------------------------- /client/components/page/PageHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | import FoodbankLogo from '../FoodbankLogo' 5 | 6 | const PageHeader = ({heading, showLogo, center, children}) => 7 |
8 | {showLogo && } 9 |

{heading}

10 | {children} 11 |
12 | 13 | PageHeader.propTypes = { 14 | heading: PropTypes.string, 15 | showLogo: PropTypes.bool, 16 | center: PropTypes.bool, 17 | children: PropTypes.node 18 | } 19 | 20 | export default PageHeader 21 | -------------------------------------------------------------------------------- /server/models/media.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | 3 | import {modelTypes} from '../../common/constants' 4 | 5 | const {Schema} = mongoose 6 | 7 | /** 8 | * Settings Schema 9 | */ 10 | const MediaSchema = new Schema({ 11 | path: { 12 | type: String, 13 | default: 'media/' 14 | }, 15 | logo: { 16 | type: String, 17 | default: 'logo.png' 18 | }, 19 | signature: { 20 | type: String, 21 | default: 'signature.png' 22 | }, 23 | favicon: { 24 | type: String, 25 | default: 'favicon.ico' 26 | } 27 | }) 28 | 29 | export default mongoose.model(modelTypes.MEDIA, MediaSchema) 30 | -------------------------------------------------------------------------------- /client/modules/core/components/errors/ServerError.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const ServerErrorPage = () => 4 |
5 |
6 |

500}

7 |
8 |

9 | 10 | Oops! Something went wrong. 11 |

12 |

13 | We will work on fixing that right away. 14 |

15 |
16 |
17 |
18 | 19 | export default ServerErrorPage 20 | -------------------------------------------------------------------------------- /client/modules/page/components/QuillSubjectToolbar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import './quill-toolbar.css' 4 | 5 | const QuillSubjectToolbar = ({placeholders}) => 6 |
7 | 8 | 16 | 17 |
18 | 19 | export default QuillSubjectToolbar 20 | -------------------------------------------------------------------------------- /client/modules/core/components/sidebar/SidebarMenuItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import {Link, Route} from 'react-router-dom' 4 | 5 | const SidebarMenuItem = ({path, title}) => 6 | 7 | {({match}) => 8 |
  • 9 | 10 | {title} 11 | 12 |
  • 13 | } 14 |
    15 | 16 | SidebarMenuItem.propTypes = { 17 | path: PropTypes.string.isRequired, 18 | title: PropTypes.string.isRequired 19 | } 20 | 21 | export default SidebarMenuItem 22 | -------------------------------------------------------------------------------- /client/components/error/Error.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import P from 'prop-types' 3 | import {Col, Row} from 'react-bootstrap' 4 | 5 | const Error = ({error}) => { 6 | if (!error) return null 7 | 8 | return ( 9 | 10 | 11 |
    12 | 13 | {error.message || error} 14 |
    15 | 16 |
    17 | ) 18 | } 19 | 20 | Error.propTypes = { 21 | error: P.oneOfType([ 22 | P.string, 23 | P.shape({message: P.string}) 24 | ]) 25 | } 26 | 27 | export default Error 28 | -------------------------------------------------------------------------------- /client/modules/core/components/navbar/ClientNavbarMenuItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import {Link, Route} from 'react-router-dom' 4 | 5 | export const Divider = () =>
  • 6 | 7 | const ClientNavbarMenuItem = ({path, title}) => 8 | 9 | {({match}) => 10 |
  • 11 | {title} 12 |
  • 13 | } 14 |
    15 | 16 | ClientNavbarMenuItem.propTypes = { 17 | path: PropTypes.string.isRequired, 18 | title: PropTypes.string.isRequired 19 | } 20 | 21 | export default ClientNavbarMenuItem 22 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "reporter": [ 3 | "lcov" 4 | ], 5 | "require": [ 6 | "babel-core/register", 7 | "ignore-styles", 8 | "./client/entry.test.js", 9 | "./server/entry.test.js" 10 | ], 11 | "sourceMap": false, 12 | "instrument": false, 13 | "include": [ 14 | "client/**/*.js", 15 | "server/**/*.js" 16 | ], 17 | "all": true, 18 | "exclude": [ 19 | "**/*.spec.js", 20 | "node_modules", 21 | "flow-typed", 22 | "client/entry.test.js", 23 | "server/entry.test.js", 24 | "server/index.js", 25 | "server/config/env", 26 | "server/tests" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /client/entry.test.js: -------------------------------------------------------------------------------- 1 | import {JSDOM} from 'jsdom' 2 | import chai from 'chai' 3 | import sinon from 'sinon' 4 | import sinonChai from 'sinon-chai' 5 | 6 | const {window} = new JSDOM('', {url: 'http://localhost'}) 7 | global.window = window 8 | global.document = window.document 9 | 10 | Object.keys(document.defaultView).forEach(property => { 11 | if (typeof global[property] === 'undefined') { 12 | global[property] = document.defaultView[property] 13 | } 14 | }) 15 | 16 | global.navigator = { 17 | userAgent: 'node.js' 18 | } 19 | 20 | global.expect = chai.expect 21 | global.sinon = sinon 22 | 23 | chai.use(sinonChai) 24 | chai.use(require('chai-enzyme')()) 25 | -------------------------------------------------------------------------------- /server/models/page.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | 3 | import {modelTypes, pageTypes} from '../../common/constants' 4 | 5 | const {Schema} = mongoose 6 | 7 | const PageSchema = new Schema({ 8 | identifier: { 9 | type: String, 10 | required: true 11 | }, 12 | title: { 13 | type: String, 14 | required: true 15 | }, 16 | type: { 17 | type: String, 18 | enum: [pageTypes.PAGE, pageTypes.EMAIL], 19 | default: pageTypes.PAGE 20 | }, 21 | disabled: { 22 | type: Boolean, 23 | default: false 24 | }, 25 | subject: String, 26 | body: String 27 | }) 28 | 29 | export default mongoose.model(modelTypes.PAGE, PageSchema) 30 | -------------------------------------------------------------------------------- /client/modules/driver/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux' 2 | 3 | import location, {createSelectors as createLocationSelectors} from './location' 4 | import assignment, {createSelectors as createAssignmentSelectors} from './assignment' 5 | import route, {createSelectors as createRouteSelectors} from './route' 6 | 7 | export default combineReducers({location, assignment, route}) 8 | 9 | export const createSelectors = (path, customerSelectors) => ({ 10 | location: createLocationSelectors(`${path}.location`), 11 | assignment: createAssignmentSelectors(`${path}.assignment`, customerSelectors), 12 | route: createRouteSelectors(`${path}.route`) 13 | }) 14 | -------------------------------------------------------------------------------- /server/routes/donation.js: -------------------------------------------------------------------------------- 1 | import Router from 'express-promise-router' 2 | import donationController from '../controllers/donation' 3 | import usersController from '../controllers/users' 4 | 5 | const {requiresLogin} = usersController 6 | 7 | const donationRouter = Router({mergeParams: true}) 8 | 9 | donationRouter.route('/donations') 10 | .post(donationController.create) 11 | 12 | donationRouter.route('/admin/donations/:donationId/approve') 13 | .put(donationController.approve) 14 | 15 | donationRouter.route('/donations/:donationId') 16 | .put(requiresLogin, donationController.hasAuthorization, donationController.sendEmail) 17 | 18 | export default donationRouter 19 | -------------------------------------------------------------------------------- /client/components/box/Box.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import P from 'prop-types' 3 | import {Col, Row} from 'react-bootstrap' 4 | 5 | const Box = ({children, type = 'primary', style, solid, ...props}) => { 6 | let boxClass = 'box' 7 | if (type) boxClass += ` box-${type}` 8 | if (solid) boxClass += ' box-solid' 9 | 10 | return ( 11 | 12 | 13 |
    14 | {children} 15 |
    16 | 17 |
    18 | ) 19 | } 20 | 21 | Box.propTypes = { 22 | children: P.node, 23 | type: P.string, 24 | style: P.object, 25 | solid: P.bool 26 | } 27 | 28 | export default Box 29 | -------------------------------------------------------------------------------- /client/modules/core/components/Footer.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | 4 | import { Footer } from './Footer' 5 | 6 | describe("Footer", function() { 7 | it("Should have a copyright year", function() 8 | { 9 | let f = shallow(