├── server ├── views │ ├── _footer.ejs │ ├── 404.ejs │ ├── footer.ejs │ ├── header.ejs │ ├── test.ejs │ ├── entry.ejs │ ├── signup.ejs │ ├── users.ejs │ ├── login.ejs │ ├── index.ejs │ ├── profile.ejs │ ├── new-entry.ejs │ ├── edit-csrf.ejs │ ├── edit.ejs │ └── _header.ejs ├── static │ ├── cat.png │ └── resume.pdf ├── start-client.js ├── modules │ ├── capitalize.js │ └── getInfoFromURL.js ├── run-dev.js ├── run-prod.js ├── api1 │ ├── posts.js │ └── notifs.js ├── models │ ├── Notif.js │ ├── Post.js │ └── Project.js └── auth │ └── newUserHelper.js ├── client ├── build │ ├── favicon.ico │ └── manifest.json ├── public │ ├── favicon.ico │ ├── manifest.json │ └── index.html ├── src │ ├── assets │ │ ├── logo.png │ │ ├── user.png │ │ ├── favicon.ico │ │ ├── og-image.jpg │ │ ├── looseleaf.png │ │ ├── team │ │ │ ├── ollie.png │ │ │ ├── ollie2.png │ │ │ ├── team.png │ │ │ ├── xiaoyunyang.png │ │ │ └── andrewfenner.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── landing │ │ │ ├── share.png │ │ │ ├── cultivate.png │ │ │ ├── expertise.png │ │ │ ├── landing-plane.png │ │ │ ├── landing-world.png │ │ │ ├── landing1-large.png │ │ │ ├── landing1-medium.png │ │ │ ├── landing1-small.png │ │ │ ├── mobile-laptop1.png │ │ │ ├── mobile-laptop2.png │ │ │ ├── mobile-laptop3.png │ │ │ ├── landing-bookmark.png │ │ │ ├── tribal-knowledge.png │ │ │ └── what-is-looseleaf.png │ │ ├── apple-touch-icon.png │ │ ├── mstile-150x150.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── browserconfig.xml │ │ ├── site.webmanifest │ │ └── markdown │ │ │ ├── our-mission.md │ │ │ ├── our-values.md │ │ │ ├── careers.md │ │ │ └── our-story.md │ ├── shared │ │ ├── Landing │ │ │ ├── Root.js │ │ │ ├── Main.js │ │ │ ├── App.js │ │ │ ├── screens │ │ │ │ ├── Terms.js │ │ │ │ ├── Careers.js │ │ │ │ ├── Privacy.js │ │ │ │ ├── HowItWorks.js │ │ │ │ └── About.js │ │ │ └── routes.js │ │ ├── User │ │ │ ├── Root.js │ │ │ ├── Main.js │ │ │ ├── screens │ │ │ │ ├── Privacy.js │ │ │ │ ├── Terms.js │ │ │ │ └── Home │ │ │ │ │ ├── Stats.js │ │ │ │ │ └── Main.js │ │ │ ├── App.js │ │ │ └── routes.js │ │ ├── Explore │ │ │ ├── Root.js │ │ │ ├── Main.js │ │ │ ├── routes.js │ │ │ ├── App.js │ │ │ └── screens │ │ │ │ ├── Interests │ │ │ │ └── Main.js │ │ │ │ └── Communities │ │ │ │ └── Main.js │ │ ├── UserPage │ │ │ ├── Root.js │ │ │ ├── Main.js │ │ │ ├── routes.js │ │ │ └── App.js │ │ ├── components │ │ │ ├── Collection │ │ │ │ ├── Interests │ │ │ │ │ └── lib.js │ │ │ │ ├── Notifs │ │ │ │ │ ├── NotifIcon.js │ │ │ │ │ └── Wrapper.js │ │ │ │ ├── Communities │ │ │ │ │ ├── lib.js │ │ │ │ │ └── Chips.js │ │ │ │ └── Projects │ │ │ │ │ ├── Badge.js │ │ │ │ │ ├── Card.js │ │ │ │ │ ├── Cards.js │ │ │ │ │ └── Main.js │ │ │ ├── RedirectWithStatus.js │ │ │ ├── LoadMoreBtn.js │ │ │ ├── Nav │ │ │ │ ├── NavLink.js │ │ │ │ └── AppNav.js │ │ │ ├── TextWithLinks.js │ │ │ ├── Portfolio │ │ │ │ ├── Two.js │ │ │ │ ├── routes.js │ │ │ │ ├── Four.js │ │ │ │ ├── Three.js │ │ │ │ └── One.js │ │ │ ├── Login │ │ │ │ ├── LoginForm.js │ │ │ │ ├── LegalInfo.js │ │ │ │ ├── LoginPage.js │ │ │ │ ├── SignupPage.js │ │ │ │ ├── Modal.js │ │ │ │ └── SocialLogin.js │ │ │ ├── Discussion │ │ │ │ ├── Comments.js │ │ │ │ ├── PostParts.js │ │ │ │ ├── Posts.js │ │ │ │ ├── Reaction.js │ │ │ │ ├── PostEditMenu.js │ │ │ │ └── PostContent.js │ │ │ ├── FlashNotif.js │ │ │ ├── NotFound.js │ │ │ ├── MdText.js │ │ │ ├── Form │ │ │ │ ├── InputAutocomplete.js │ │ │ │ ├── TextAreaInput.js │ │ │ │ ├── DatePicker.js │ │ │ │ ├── TextInput.js │ │ │ │ ├── InputRadioBtns.js │ │ │ │ ├── InputDropdown.js │ │ │ │ └── InputTags.js │ │ │ └── TopNavUser │ │ │ │ ├── NewDropdown.js │ │ │ │ └── CommunityDropdown.js │ │ ├── Post │ │ │ ├── Root.js │ │ │ ├── Main.js │ │ │ ├── routes.js │ │ │ ├── App.js │ │ │ └── screens │ │ │ │ └── Page │ │ │ │ └── Main.js │ │ ├── CommunityUser │ │ │ ├── Root.js │ │ │ ├── Main.js │ │ │ ├── routes.js │ │ │ ├── Tabs │ │ │ │ ├── One.js │ │ │ │ └── Two.js │ │ │ └── App.js │ │ ├── ProjectPage │ │ │ ├── Root.js │ │ │ ├── screens │ │ │ │ └── Page │ │ │ │ │ ├── Contributors.js │ │ │ │ │ └── Feed.js │ │ │ ├── Main.js │ │ │ ├── routes.js │ │ │ └── App.js │ │ ├── CommunityGuest │ │ │ ├── Root.js │ │ │ ├── Main.js │ │ │ ├── Tabs │ │ │ │ ├── Two.js │ │ │ │ ├── One.js │ │ │ │ └── Three.js │ │ │ ├── routes.js │ │ │ └── App.js │ │ ├── redux │ │ │ ├── Recipe │ │ │ │ ├── actions │ │ │ │ │ └── user.js │ │ │ │ ├── init-redux.js │ │ │ │ └── reducers │ │ │ │ │ ├── recipes.js │ │ │ │ │ └── projects.js │ │ │ ├── reducers │ │ │ │ ├── community.js │ │ │ │ └── post.js │ │ │ ├── configureStore │ │ │ │ ├── userPage.js │ │ │ │ ├── explorePage.js │ │ │ │ ├── communityGuestPage.js │ │ │ │ ├── postPage.js │ │ │ │ ├── projectPage.js │ │ │ │ ├── communityUserPage.js │ │ │ │ ├── initUserPage.js │ │ │ │ ├── initExplorePage.js │ │ │ │ ├── initCommunityGuestPage.js │ │ │ │ ├── initPostPage.js │ │ │ │ ├── initProjectPage.js │ │ │ │ └── initCommunityUserPage.js │ │ │ └── actions │ │ │ │ ├── post.js │ │ │ │ └── page.js │ │ └── data │ │ │ ├── appPage.js │ │ │ ├── community.json │ │ │ ├── assetLinks.js │ │ │ ├── team.js │ │ │ └── deliverableFormats.json │ ├── craApps │ │ ├── AppLanding.js │ │ ├── AppCommunityGuest.js │ │ ├── App.js │ │ ├── AppUserPage.js │ │ ├── AppExplore.js │ │ ├── AppPost.js │ │ ├── AppProjectPage.js │ │ ├── AppCommunityUser.js │ │ └── AppUser.js │ ├── lib │ │ └── createReducer.js │ ├── clientApps │ │ ├── landing.js │ │ ├── recipe.js │ │ ├── explore.js │ │ ├── post.js │ │ ├── communityguest.js │ │ ├── projectpage.js │ │ ├── userpage.js │ │ ├── communityuser.js │ │ └── user.js │ └── index.js ├── config │ ├── jest │ │ ├── fileTransform.js │ │ └── cssTransform.js │ ├── polyfills.js │ └── paths.js ├── scripts │ └── test.js └── iso-middleware │ ├── renderLandingApp.jsx │ ├── renderExploreApp.jsx │ └── renderCommunityGuestApp.jsx ├── .babelrc ├── docker-compose.yml ├── Dockerfile ├── .gitignore ├── test ├── txt.js ├── html.js └── capitalize.js └── .eslintrc /server/views/_footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /server/static/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/server/static/cat.png -------------------------------------------------------------------------------- /server/views/404.ejs: -------------------------------------------------------------------------------- 1 | <% include header %> 2 |

404! Page not found.

3 | <% include footer %> 4 | -------------------------------------------------------------------------------- /client/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/build/favicon.ico -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /server/static/resume.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/server/static/resume.pdf -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/logo.png -------------------------------------------------------------------------------- /client/src/assets/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/user.png -------------------------------------------------------------------------------- /client/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/favicon.ico -------------------------------------------------------------------------------- /client/src/assets/og-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/og-image.jpg -------------------------------------------------------------------------------- /client/src/assets/looseleaf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/looseleaf.png -------------------------------------------------------------------------------- /client/src/assets/team/ollie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/team/ollie.png -------------------------------------------------------------------------------- /client/src/assets/team/ollie2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/team/ollie2.png -------------------------------------------------------------------------------- /client/src/assets/team/team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/team/team.png -------------------------------------------------------------------------------- /client/src/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/favicon-16x16.png -------------------------------------------------------------------------------- /client/src/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/favicon-32x32.png -------------------------------------------------------------------------------- /client/src/assets/landing/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/share.png -------------------------------------------------------------------------------- /client/src/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /client/src/assets/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/mstile-150x150.png -------------------------------------------------------------------------------- /client/src/assets/team/xiaoyunyang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/team/xiaoyunyang.png -------------------------------------------------------------------------------- /server/views/footer.ejs: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /client/src/assets/landing/cultivate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/cultivate.png -------------------------------------------------------------------------------- /client/src/assets/landing/expertise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/expertise.png -------------------------------------------------------------------------------- /client/src/assets/team/andrewfenner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/team/andrewfenner.png -------------------------------------------------------------------------------- /client/src/assets/landing/landing-plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/landing-plane.png -------------------------------------------------------------------------------- /client/src/assets/landing/landing-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/landing-world.png -------------------------------------------------------------------------------- /client/src/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /client/src/assets/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/android-chrome-512x512.png -------------------------------------------------------------------------------- /client/src/assets/landing/landing1-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/landing1-large.png -------------------------------------------------------------------------------- /client/src/assets/landing/landing1-medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/landing1-medium.png -------------------------------------------------------------------------------- /client/src/assets/landing/landing1-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/landing1-small.png -------------------------------------------------------------------------------- /client/src/assets/landing/mobile-laptop1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/mobile-laptop1.png -------------------------------------------------------------------------------- /client/src/assets/landing/mobile-laptop2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/mobile-laptop2.png -------------------------------------------------------------------------------- /client/src/assets/landing/mobile-laptop3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/mobile-laptop3.png -------------------------------------------------------------------------------- /client/src/assets/landing/landing-bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/landing-bookmark.png -------------------------------------------------------------------------------- /client/src/assets/landing/tribal-knowledge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/tribal-knowledge.png -------------------------------------------------------------------------------- /client/src/assets/landing/what-is-looseleaf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoyunyang/looseleaf-node/HEAD/client/src/assets/landing/what-is-looseleaf.png -------------------------------------------------------------------------------- /server/start-client.js: -------------------------------------------------------------------------------- 1 | const args = ['start']; 2 | const opts = { stdio: 'inherit', cwd: 'client', shell: true }; 3 | require('child_process').spawn('npm', args, opts); 4 | -------------------------------------------------------------------------------- /server/modules/capitalize.js: -------------------------------------------------------------------------------- 1 | function capitalize(str) { 2 | const firstLetter = str.charAt(0).toUpperCase(); 3 | const rest = str.slice(1).toLowerCase(); 4 | return firstLetter + rest; 5 | } 6 | 7 | module.exports = capitalize; 8 | -------------------------------------------------------------------------------- /client/src/shared/Landing/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Main from './Main'; 3 | 4 | const Root = ({ route }) => ( 5 |
6 |
7 |
8 | ); 9 | export default Root; 10 | -------------------------------------------------------------------------------- /client/src/shared/User/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Main from './Main'; 3 | 4 | const Root = ({ route, state, actions }) => ( 5 |
6 | ); 7 | 8 | export default Root; 9 | -------------------------------------------------------------------------------- /client/src/shared/Explore/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Main from './Main'; 3 | 4 | const Root = ({ route, state }) => ( 5 |
9 | ); 10 | 11 | export default Root; 12 | -------------------------------------------------------------------------------- /client/src/shared/UserPage/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Main from './Main'; 3 | 4 | const Root = ({ route, state, actions }) => ( 5 |
6 | ); 7 | 8 | export default Root; 9 | -------------------------------------------------------------------------------- /client/src/shared/Landing/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderRoutes } from 'react-router-config'; 3 | 4 | const Main = ({ routes }) => ( 5 |
6 | {renderRoutes(routes)} 7 |
8 | ); 9 | 10 | export default Main; 11 | -------------------------------------------------------------------------------- /client/src/shared/components/Collection/Interests/lib.js: -------------------------------------------------------------------------------- 1 | export const interests = require('../../../data/interests.json'); 2 | 3 | export const interestName = slug => interests[slug] ? interests[slug].name : ''; 4 | 5 | export const interestsArr = Object.values(interests); 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "stage-2", 5 | "react" 6 | ], 7 | "plugins": [ 8 | "transform-runtime", 9 | "transform-es2015-destructuring", 10 | "transform-es2015-parameters", 11 | "transform-object-rest-spread" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /client/src/shared/Landing/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderRoutes } from 'react-router-config'; 3 | import { routes } from './routes'; 4 | 5 | const App = () => ( 6 |
7 | {renderRoutes(routes)} 8 |
9 | ); 10 | 11 | export default App; 12 | -------------------------------------------------------------------------------- /client/src/shared/Post/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Main from './Main'; 3 | 4 | const Root = ({ route, state, actions }) => ( 5 |
10 | ); 11 | 12 | export default Root; 13 | -------------------------------------------------------------------------------- /client/src/shared/CommunityUser/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Main from './Main'; 3 | 4 | const Root = ({ route, state, actions }) => ( 5 |
6 |
7 |
8 | ); 9 | 10 | export default Root; 11 | -------------------------------------------------------------------------------- /client/src/shared/ProjectPage/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Main from './Main'; 3 | 4 | const Root = ({ route, state, actions }) => ( 5 |
10 | ); 11 | 12 | export default Root; 13 | -------------------------------------------------------------------------------- /client/src/assets/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /client/src/craApps/AppLanding.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter } from 'react-router-dom'; 3 | import { hot } from 'react-hot-loader'; 4 | import App from '../shared/Landing/App'; 5 | 6 | const AppLanding = () => ( 7 | 8 | 9 | 10 | ); 11 | 12 | export default hot(module)(AppLanding); 13 | -------------------------------------------------------------------------------- /client/src/shared/CommunityGuest/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Main from './Main'; 3 | import Footer from '../components/Footer'; 4 | 5 | const Root = ({ route, user, community }) => ( 6 |
7 |
8 |
9 |
10 | ) 11 | 12 | export default Root; 13 | -------------------------------------------------------------------------------- /client/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | web: 4 | container_name: looseleaf-node-app 5 | build: . 6 | ports: 7 | - "3001:3001" 8 | depends_on: 9 | - mongo 10 | mongo: 11 | container_name: mongo 12 | image: mongo:latest 13 | ports: 14 | - "27017:27017" 15 | volumes: 16 | - data-volume:/dbdata/db 17 | volumes: 18 | data-volume: 19 | -------------------------------------------------------------------------------- /client/src/shared/components/RedirectWithStatus.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Redirect } from 'react-router-dom'; 3 | 4 | const RedirectWithStatus = ({ route }) => ( 5 | { 6 | if (staticContext) { staticContext.status = route.status; } 7 | return ; 8 | }} 9 | /> 10 | ); 11 | export default RedirectWithStatus; 12 | -------------------------------------------------------------------------------- /client/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /client/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /client/src/shared/components/LoadMoreBtn.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const LoadMoreBtn = ({ handleClick, itemName }) => ( 4 | 12 | ) 13 | 14 | export default LoadMoreBtn; 15 | -------------------------------------------------------------------------------- /client/src/shared/CommunityGuest/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderRoutes } from 'react-router-config'; 3 | 4 | export default class Main extends React.Component { 5 | render() { 6 | const state = { 7 | community: this.props.community.info 8 | } 9 | return ( 10 |
11 | {renderRoutes(this.props.routes, state)} 12 |
13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/src/shared/Explore/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderRoutes } from 'react-router-config'; 3 | 4 | class Main extends React.Component { 5 | render() { 6 | const appProps = { 7 | user: this.props.state.user 8 | } 9 | return ( 10 |
11 | {renderRoutes(this.props.routes, appProps)} 12 |
13 | ) 14 | } 15 | } 16 | export default Main; 17 | -------------------------------------------------------------------------------- /client/src/shared/components/Nav/NavLink.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | // import PropTypes from 'prop-types'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const NavLink = ({external, to, className, id, name}) => ( 6 | external ? 7 | {name} 8 | : 9 | {name} 10 | ); 11 | 12 | export default NavLink; 13 | -------------------------------------------------------------------------------- /server/views/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Express Guestbook 6 | 7 | 8 | 9 |

10 | Express Guestbook 11 | 12 | Write in the guestbook 13 | 14 |

15 | -------------------------------------------------------------------------------- /server/views/test.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 |

Your User Agent is:

14 |

15 | <%- userAgent %> 16 |

17 | 18 | 19 | -------------------------------------------------------------------------------- /server/views/entry.ejs: -------------------------------------------------------------------------------- 1 | <% include header %> 2 | 3 |
4 |
5 |
6 | <%= entry.published %> 7 |
8 | 9 | <%= entry.title %> 10 | 11 |
12 |
13 | <%= entry.content %> 14 |
15 |
16 | 17 | <% include footer %> 18 | -------------------------------------------------------------------------------- /client/src/shared/User/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderRoutes } from 'react-router-config'; 3 | 4 | class Main extends React.Component { 5 | render() { 6 | const appProps = { 7 | user: this.props.user, 8 | actions: this.props.actions 9 | } 10 | return ( 11 |
12 | {renderRoutes(this.props.routes, appProps)} 13 |
14 | ) 15 | } 16 | } 17 | export default Main; 18 | -------------------------------------------------------------------------------- /server/views/signup.ejs: -------------------------------------------------------------------------------- 1 | <% include _header %> 2 |

Sign up

3 |
4 | 6 | 8 | 9 |
10 | <% include _footer %> 11 | -------------------------------------------------------------------------------- /server/views/users.ejs: -------------------------------------------------------------------------------- 1 | <% include _header %> 2 |

Welcome to Learn About Me!

3 | <% users.forEach(function(user) { %> 4 |
5 | 10 | <% if (user.bio) { %> 11 |
<%= user.bio %>
12 | <% } %>
13 | <% }) %> 14 | <% include _footer %> 15 | -------------------------------------------------------------------------------- /client/src/shared/CommunityUser/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderRoutes } from 'react-router-config'; 3 | 4 | export default class Main extends React.Component { 5 | render() { 6 | const appProps = { 7 | state: this.props.state, 8 | actions: this.props.actions 9 | } 10 | return ( 11 |
12 | { 13 | renderRoutes(this.props.routes, appProps) 14 | } 15 |
16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/src/shared/components/Nav/AppNav.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import NavLink from './NavLink'; 3 | import { userPage } from '../../data/appPage'; 4 | 5 | // valid pageName include: ['home', 'profile', 'newProject', 'userSettings'] 6 | const UserAppNav = ({ pageName, username, external, id }) => ( 7 | 8 | ); 9 | 10 | export { UserAppNav }; 11 | -------------------------------------------------------------------------------- /client/src/lib/createReducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * https://medium.com/@jonlebensold/getting-started-with-react-native-redux-2b01408c0053 3 | * Creating a reducer without using switch statements 4 | */ 5 | export default function createReducer(initialState, handlers) { 6 | return function reducer(state = initialState, action) { 7 | if (handlers.hasOwnProperty(action.type)) { 8 | return handlers[action.type](state, action); 9 | } 10 | return state; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /client/src/shared/Post/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderRoutes } from 'react-router-config'; 3 | 4 | class Main extends React.Component { 5 | render() { 6 | const appProps = { 7 | user: this.props.state.user, 8 | post: this.props.state.post, 9 | actions: this.props.actions 10 | } 11 | return ( 12 |
13 | {renderRoutes(this.props.routes, appProps)} 14 |
15 | ) 16 | } 17 | } 18 | export default Main; 19 | -------------------------------------------------------------------------------- /client/src/shared/ProjectPage/screens/Page/Contributors.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import UserCards from '../../../components/Collection/UserCards'; 3 | 4 | const Contributors = ({ contributors, inviteNewContributor }) => ( 5 |
6 |
{`Project Contributors (${contributors.length})`}
7 | 11 |
12 | ); 13 | 14 | export default Contributors; 15 | -------------------------------------------------------------------------------- /server/run-dev.js: -------------------------------------------------------------------------------- 1 | // Include Babel 2 | // setup this project to use babel inline in development mode on the server. 3 | // This means you don’t have to precompile any of the code to have it run on 4 | // the server. 5 | // it will parse all code that comes after it. 6 | // (Not recommended for production use). 7 | 8 | process.env.NODE_ENV = 'development'; 9 | require('babel-register')({ 10 | ignore: /\/(build|node_modules)\//, 11 | presets: ['env', 'react-app'] 12 | }); 13 | 14 | require('./server.js'); 15 | -------------------------------------------------------------------------------- /server/run-prod.js: -------------------------------------------------------------------------------- 1 | // Include Babel 2 | // setup this project to use babel inline in development mode on the server. 3 | // This means you don’t have to precompile any of the code to have it run on 4 | // the server. 5 | // it will parse all code that comes after it. 6 | // (Not recommended for production use). 7 | 8 | process.env.NODE_ENV = 'production'; 9 | require('babel-register')({ 10 | ignore: /\/(build|node_modules)\//, 11 | presets: ['env', 'react-app'] 12 | }); 13 | 14 | require('./server.js'); 15 | -------------------------------------------------------------------------------- /server/views/login.ejs: -------------------------------------------------------------------------------- 1 | <% include _header %> 2 |

Log in

3 |
4 | 7 | 8 | 9 | 10 |
11 | <% include _footer %> 12 | -------------------------------------------------------------------------------- /client/src/shared/User/screens/Privacy.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TopNav from '../../components/TopNavUser/Main'; 3 | import { markdown } from '../../data/assetLinks'; 4 | import MdText from '../../components/MdText'; 5 | 6 | export default ({ route, user }) => ( 7 |
8 | 9 |
10 | 11 |
12 |
13 | ); 14 | -------------------------------------------------------------------------------- /client/src/shared/User/screens/Terms.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TopNav from '../../components/TopNavUser/Main'; 3 | import { markdown } from '../../data/assetLinks'; 4 | import MdText from '../../components/MdText'; 5 | 6 | export default ({ route, user }) => ( 7 |
8 | 9 |
10 | 11 |
12 |
13 | ); 14 | -------------------------------------------------------------------------------- /client/src/shared/redux/Recipe/actions/user.js: -------------------------------------------------------------------------------- 1 | import { 2 | fetchNewProjects, 3 | fetchCompletedProjects 4 | } from './projects'; 5 | 6 | import { 7 | fetchRecipes, 8 | fetchFeaturedRecipe 9 | } from './recipes' 10 | 11 | export function getHomePageData() { 12 | return (dispatch, getState) => { 13 | return Promise.all([ 14 | dispatch(fetchCompletedProjects()), 15 | dispatch(fetchNewProjects()), 16 | dispatch(fetchFeaturedRecipe()), 17 | dispatch(fetchRecipes()) 18 | ]) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/src/assets/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /client/src/shared/ProjectPage/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderRoutes } from 'react-router-config'; 3 | 4 | class Main extends React.Component { 5 | render() { 6 | const appProps = { 7 | user: this.props.state.user, 8 | projectInfo: this.props.state.project.info, 9 | contributors: this.props.state.project.contributors, 10 | actions: this.props.actions 11 | } 12 | return ( 13 |
14 | {renderRoutes(this.props.routes, appProps)} 15 |
16 | ) 17 | } 18 | } 19 | export default Main; 20 | -------------------------------------------------------------------------------- /client/src/shared/Landing/screens/Terms.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TopNav from '../TopNav'; 3 | import { markdown } from '../../data/assetLinks'; 4 | import MdText from '../../components/MdText'; 5 | import Footer from '../../components/Footer'; 6 | 7 | export default ({ route }) => ( 8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 | ); 16 | -------------------------------------------------------------------------------- /client/src/shared/Landing/screens/Careers.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TopNav from '../TopNav'; 3 | import { markdown } from '../../data/assetLinks'; 4 | import MdText from '../../components/MdText'; 5 | import Footer from '../../components/Footer'; 6 | 7 | export default ({ route }) => ( 8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 | ); 16 | -------------------------------------------------------------------------------- /client/src/shared/Landing/screens/Privacy.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TopNav from '../TopNav'; 3 | import { markdown } from '../../data/assetLinks'; 4 | import MdText from '../../components/MdText'; 5 | import Footer from '../../components/Footer'; 6 | 7 | export default ({ route }) => ( 8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 | ); 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.11.1 2 | 3 | # Set image metadata 4 | LABEL version="1.0" 5 | LABEL description="LooseLeaf Node" 6 | 7 | # Create app directory 8 | WORKDIR /usr/src/app 9 | 10 | # install dependencies 11 | COPY package*.json ./ 12 | RUN npm cache clean --force && npm install 13 | 14 | 15 | # copy app source to image _after_ npm install so that 16 | # application code changes don't bust the docker cache of npm install step 17 | COPY . . 18 | 19 | # set application PORT and expose docker PORT, 80 is what Elastic Beanstalk expects 20 | EXPOSE 3001 21 | 22 | CMD [ "npm", "run", "start" ] 23 | -------------------------------------------------------------------------------- /client/src/craApps/AppCommunityGuest.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter } from 'react-router-dom'; 3 | import { Provider } from 'react-redux'; 4 | import App from '../shared/CommunityGuest/App'; 5 | import { hot } from 'react-hot-loader'; 6 | import initStore from '../shared/redux/configureStore/initCommunityGuestPage'; 7 | 8 | const store = initStore(); 9 | 10 | const AppCommunity = () => ( 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | export default hot(module)(AppCommunity); 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.swp 10 | 11 | pids 12 | logs 13 | results 14 | tmp 15 | coverage 16 | 17 | # Git 18 | .git 19 | 20 | # API keys and private stuff 21 | .env 22 | secrets 23 | localhost-ssl 24 | config 25 | dbdata 26 | .ssh 27 | 28 | # Dependency directory 29 | node_modules 30 | bower_components 31 | package-lock.json 32 | .map 33 | 34 | # Builds 35 | build 36 | browser.js 37 | *.map 38 | *.bundle.js 39 | 40 | # Editors 41 | .idea 42 | *.iml 43 | 44 | # OS metadata 45 | .DS_Store 46 | Thumbs.db 47 | 48 | .dockerignore 49 | -------------------------------------------------------------------------------- /client/src/shared/Landing/screens/HowItWorks.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TopNav from '../TopNav'; 3 | import { markdown } from '../../data/assetLinks'; 4 | import MdText from '../../components/MdText'; 5 | import Footer from '../../components/Footer'; 6 | 7 | export default ({ route }) => ( 8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 | ); 16 | -------------------------------------------------------------------------------- /client/src/shared/Explore/routes.js: -------------------------------------------------------------------------------- 1 | import Root from './Root'; 2 | import Home from './screens/Communities/Main'; 3 | import NotFound from '../components/NotFound'; 4 | import appRoute from '../data/appRoute'; 5 | 6 | // function noop() {} 7 | const routes = [ 8 | { 9 | component: Root, 10 | routes: [ 11 | { 12 | path: appRoute('exploreCommunities'), 13 | exact: true, 14 | component: Home 15 | }, 16 | { 17 | path: appRoute('exploreWildcard'), 18 | component: NotFound 19 | } 20 | ] 21 | } 22 | ]; 23 | 24 | export { routes }; 25 | -------------------------------------------------------------------------------- /client/src/shared/components/TextWithLinks.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { parseUrl, amendHref } from '../../lib/helpers'; 3 | 4 | 5 | const TextWithLinks = ({ content }) => ( 6 |

7 | { 8 | parseUrl(content).map((d,i) => { 9 | return 10 | { 11 | d.strType === 'url' ? 12 | {d.value} 13 | : 14 | {`${d.value}`} 15 | } 16 | 17 | }) 18 | } 19 |

20 | ); 21 | 22 | export default TextWithLinks; 23 | -------------------------------------------------------------------------------- /client/src/assets/markdown/our-mission.md: -------------------------------------------------------------------------------- 1 | Our mission is threefold: (1) To support companies (for-profits or non-profits), entrepreneurs, academics, basically anyone who can clearly articulate a problem they need solving in form of a project in getting their problem solved for little to no cost. This is accomplished by posting projects to be completed by amateurs looking to get practice and build a portfolio with real projects; (2) To support amateurs in exploring a new career track by building an impressive portfolio with real projects; (3) To provide a passive recruiting platform for employers find and track these budding talents. 2 | -------------------------------------------------------------------------------- /client/src/shared/UserPage/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderRoutes } from 'react-router-config'; 3 | import Footer from '../components/Footer'; 4 | 5 | class Main extends React.Component { 6 | render() { 7 | const appProps = { 8 | user: this.props.user, 9 | loggedinUser: this.props.loggedinUser, 10 | actions: this.props.actions 11 | } 12 | return ( 13 |
14 | {renderRoutes(this.props.routes, appProps)} 15 | { 16 | !this.props.user.loggedinUser &&
17 | } 18 |
19 | ) 20 | } 21 | } 22 | export default Main; 23 | -------------------------------------------------------------------------------- /client/src/shared/components/Collection/Notifs/NotifIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class NotifIcon extends React.Component { 4 | render() { 5 | const { notifs } = this.props; 6 | if(notifs.length === 0 || notifs[0].read === true) { 7 | return notifications_none; 8 | } 9 | const numUnread = notifs.filter(notif => notif.read === false).length; 10 | 11 | return ( 12 |
13 | {numUnread} 14 |
15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/src/shared/components/Portfolio/Two.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Discussion from '../Discussion/Main'; 3 | 4 | export default class Two extends React.Component { 5 | render() { 6 | const context = {name: 'user', queryBy: this.props.userId}; 7 | return ( 8 |
9 |
10 |

Posts

11 | 17 |
18 |
19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/src/shared/redux/reducers/community.js: -------------------------------------------------------------------------------- 1 | const initState = { 2 | info: { 3 | slug: "developers", 4 | name: "Developers", 5 | desc: "Make web, mobile, and desktop apps.", 6 | members: ['xiaoyun-yang', 'afenner'], 7 | projects: ['create-kids-app-12345', 'rewrite-website-for-nonprofit-56789'] 8 | } 9 | }; 10 | 11 | export default function community(state = initState, action) { 12 | switch (action.type) { 13 | default: 14 | return state; // If the reducer is triggered but no case matches, return the current store state. No changes are required so you don’t need to create a new object. 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /client/src/craApps/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter } from 'react-router-dom'; 3 | import { Provider } from 'react-redux'; 4 | import Header from '../components/Header'; 5 | import Main from '../components/Main'; 6 | import configureStore from '../store/configureStore'; 7 | 8 | const store = configureStore(window.INITIAL_STATE); 9 | 10 | /* 11 | * App 12 | * caller: index.js 13 | */ 14 | const App = () => ( 15 | 16 | 17 |
18 |
19 |
20 |
21 |
22 |
23 | ); 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /server/views/index.ejs: -------------------------------------------------------------------------------- 1 | <% include header %> 2 | <% if (entries.length) { %> 3 | <% entries.forEach(function(entry) { %> 4 |
5 |
6 |
7 | <%= entry.published %> 8 |
9 | 10 | <%= entry.title %> 11 | 12 |
13 |
14 | <%= entry.content %> 15 |
16 |
17 | <% }) %> 18 | <% } else { %> 19 | No entries! Add one! 20 | <% } %> 21 | <% include footer %> 22 | -------------------------------------------------------------------------------- /client/src/shared/ProjectPage/screens/Page/Feed.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Discussion from '../../../components/Discussion/Main'; 3 | 4 | const Feed = ({ user, projectId }) => ( 5 |
6 |
Discussion
7 |
8 | { 9 | 15 | } 16 |
17 |
18 | ); 19 | 20 | export default Feed; 21 | -------------------------------------------------------------------------------- /client/src/assets/markdown/our-values.md: -------------------------------------------------------------------------------- 1 | At LooseLeaf, we believe people with genuine passion for the work and the self initiative and grit to continue learning and improving upon their skills should not be precluded from careers simply because they did not go through a costly four year credentialing program. Everyone who's willing to invest time and effort in their professional development deserves free access to mentorship, peer support system, and opportunities to acquire practical work experience. We believe companies should have a better way to find these individuals, recruit them, and assess their abilities and culture fit based on more granular data of their track record. 2 | -------------------------------------------------------------------------------- /server/views/profile.ejs: -------------------------------------------------------------------------------- 1 | <% include _header %> 2 | <% if ((currentUser) && (currentUser.id === user.id)) { %> 3 | Edit your profile 4 | <% } %> 5 |

<%= user.name() %>

6 |

Joined on <%= user.createdAt %>

7 | <% if (user.picture) { %> 8 | height="120" width="120"/> 9 | <% } %> 10 |

Email: <%= user.email %>

11 | <% if (user.bio) { %> 12 |

Bio: <%= user.bio %>

13 | <% } %> 14 | <% if (user.gender) { %> 15 |

Gender: <%= user.gender %>

16 | <% } %> 17 | <% if (user.location) { %> 18 |

Location: <%= user.location %>

19 | <% } %> 20 | <% include _footer %> 21 | -------------------------------------------------------------------------------- /client/src/shared/Post/routes.js: -------------------------------------------------------------------------------- 1 | import Root from './Root'; 2 | import Page from './screens/Page/Main'; 3 | import NotFound from '../components/NotFound'; 4 | import appRoute from '../data/appRoute'; 5 | 6 | const getRoutes = postId => { 7 | const routes = [ 8 | { 9 | component: Root, 10 | routes: [ 11 | { 12 | path: appRoute('postPage')(postId), 13 | component: Page 14 | }, 15 | { 16 | path: appRoute('postWildcard'), 17 | component: NotFound 18 | } 19 | ] 20 | } 21 | ]; 22 | return routes; 23 | }; 24 | 25 | // Caller: 26 | // getRoutes is used by App.js 27 | // getNav is used by TopNav.js 28 | export { getRoutes }; 29 | -------------------------------------------------------------------------------- /client/src/shared/redux/configureStore/userPage.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, 4 | applyMiddleware, 5 | compose } from 'redux'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import user from '../reducers/user'; 8 | 9 | const reducers = combineReducers({ 10 | user 11 | }); 12 | 13 | //TODO: remove logger for only production mode 14 | const configureStore = ({user}, init) => { 15 | const middleware = [thunkMiddleware]; 16 | const enhancer = compose( 17 | applyMiddleware(...middleware) 18 | ); 19 | const store = createStore( 20 | reducers, 21 | { 22 | user: user 23 | }, 24 | enhancer 25 | ); 26 | return store; 27 | } 28 | 29 | export default configureStore; 30 | -------------------------------------------------------------------------------- /server/views/new-entry.ejs: -------------------------------------------------------------------------------- 1 | <% include header %> 2 |

Write a new entry

3 |
4 |
5 | 6 | 3.5.4 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 |
16 |
17 | <% include footer %> 18 | -------------------------------------------------------------------------------- /client/src/shared/redux/configureStore/explorePage.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, 4 | applyMiddleware, 5 | compose } from 'redux'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import user from '../reducers/user'; 8 | 9 | const reducers = combineReducers({ 10 | user 11 | }); 12 | 13 | //TODO: remove logger for only production mode 14 | const configureStore = ({user}, init) => { 15 | const middleware = [thunkMiddleware]; 16 | const enhancer = compose( 17 | applyMiddleware(...middleware) 18 | ); 19 | const store = createStore( 20 | reducers, 21 | { 22 | user: user 23 | }, 24 | enhancer 25 | ); 26 | return store; 27 | } 28 | 29 | export default configureStore; 30 | -------------------------------------------------------------------------------- /client/src/shared/components/Login/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import LocalLogin from './LocalLogin'; 3 | import SocialLogin from './SocialLogin'; 4 | 5 | const LoginForm = ({ header, action, redirPath }) => ( 6 |
7 |
8 |
{header}
9 |
10 |
11 | 12 |
13 |
14 | OR 15 |
16 | 17 |
18 | ); 19 | 20 | export default LoginForm; 21 | -------------------------------------------------------------------------------- /client/src/clientApps/landing.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import 'materialize-css'; 5 | import 'materialize-css/dist/css/materialize.min.css'; 6 | import 'materialize-css/dist/js/materialize.min'; 7 | import App from '../shared/Landing/App'; 8 | import '../lib/tabs'; 9 | import '../assets/index.css'; 10 | /* 11 | * Main entry point for the client side isomorphic app 12 | */ 13 | const renderRouter = (Component, store) => { 14 | ReactDOM.hydrate( 15 | 16 | 17 | , document.getElementById('root') 18 | ); 19 | }; 20 | 21 | // Wrapping App inside of Provider 22 | // render(RecipeApp, store); 23 | renderRouter(App); 24 | -------------------------------------------------------------------------------- /client/src/shared/redux/configureStore/communityGuestPage.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, 4 | applyMiddleware, 5 | compose } from 'redux'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import community from '../reducers/community'; 8 | 9 | //TODO: remove logger for only production mode 10 | 11 | const reducers = combineReducers({ 12 | community 13 | }); 14 | 15 | const configureStore = ({community}, init) => { 16 | const middleware = [thunkMiddleware]; 17 | const enhancer = compose( 18 | applyMiddleware(...middleware) 19 | ); 20 | const store = createStore( 21 | reducers, 22 | { 23 | community: community 24 | }, 25 | enhancer 26 | ); 27 | return store; 28 | } 29 | 30 | export default configureStore; 31 | -------------------------------------------------------------------------------- /client/src/shared/components/Login/LegalInfo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import appRoute from '../../data/appRoute'; 3 | 4 | const LegalInfo = () => ( 5 |
6 | 7 | 13 | Terms 14 | {` | `} 15 | 21 | Privacy 22 | 23 | 24 |
25 | ); 26 | 27 | export default LegalInfo 28 | -------------------------------------------------------------------------------- /server/api1/posts.js: -------------------------------------------------------------------------------- 1 | import Post from '../models/Post'; 2 | 3 | export const getPosts = (findCriteria, reqLimit, reqPage, cbSuccess, cbFailure) => { 4 | const limit = reqLimit ? parseInt(reqLimit, 10) : 5; 5 | const page = reqPage ? parseInt(reqPage, 10) : 1; 6 | const options = { 7 | page, limit, sort: { createdAt: -1 } 8 | }; 9 | return Post.paginate(findCriteria, options, (err, posts) => { 10 | if (err) return cbFailure(err); 11 | return cbSuccess(posts.docs); 12 | }); 13 | }; 14 | 15 | 16 | // NOTE: Deprecated 17 | // const getPosts = ({ findCriteria, limit, cbSuccess }) => { 18 | // return Post.find(findCriteria).sort({ createdAt: -1 }).limit(limit).exec( 19 | // (err, posts) => { 20 | // cbSuccess(posts); 21 | // } 22 | // ); 23 | // }; 24 | -------------------------------------------------------------------------------- /client/src/shared/redux/actions/post.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | import { apiLink } from '../../data/apiLinks'; 3 | 4 | export const GET_POST_BY_ID = 'GET_POST_BY_ID'; 5 | 6 | export function fetchPostById(postId) { 7 | return dispatch => { 8 | return fetch(apiLink.postById(postId), { 9 | method: 'GET' 10 | }).then((response) => { 11 | if(response.length===0) return; 12 | return response.json().then((data) => { // On a successful response, get the JSON from the response. 13 | if(data.length===0) return; 14 | return dispatch({ // Dispatch the action. 15 | type: GET_POST_BY_ID, 16 | data: data.pop() 17 | }); 18 | }); 19 | }).catch((e) => { 20 | console.log("error", e) 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/src/shared/redux/configureStore/postPage.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, 4 | applyMiddleware, 5 | compose } from 'redux'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import post from '../reducers/post'; 8 | import user from '../reducers/user'; 9 | 10 | const reducers = combineReducers({ 11 | post, 12 | user 13 | }); 14 | 15 | //TODO: remove logger for only production mode 16 | const configureStore = ({post, user}, init) => { 17 | const middleware = [thunkMiddleware]; 18 | const enhancer = compose( 19 | applyMiddleware(...middleware) 20 | ); 21 | const store = createStore( 22 | reducers, 23 | { 24 | post: post, 25 | user: user 26 | }, 27 | enhancer 28 | ); 29 | return store; 30 | } 31 | 32 | export default configureStore; 33 | -------------------------------------------------------------------------------- /client/src/shared/redux/Recipe/init-redux.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, // Use the combineReducers function to create one root reducer. In bigger apps you will have many reducers. 4 | applyMiddleware, 5 | compose } from 'redux'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import recipes from './reducers/recipes'; 8 | import projects from './reducers/projects'; 9 | 10 | export default function (initialStore={}) { 11 | const reducer = combineReducers({ 12 | recipes, 13 | projects 14 | }); 15 | const middleware = [thunkMiddleware] 16 | return compose( 17 | applyMiddleware(...middleware) 18 | )(createStore)(reducer, initialStore); // the initialStore value is passed into the Redux createStore function. The store is now hydrated with the data from the server 19 | } 20 | -------------------------------------------------------------------------------- /client/src/shared/redux/configureStore/projectPage.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, 4 | applyMiddleware, 5 | compose } from 'redux'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import project from '../reducers/project'; 8 | import user from '../reducers/user'; 9 | 10 | const reducers = combineReducers({ 11 | project, 12 | user 13 | }); 14 | 15 | //TODO: remove logger for only production mode 16 | const configureStore = ({project, user}, init) => { 17 | const middleware = [thunkMiddleware]; 18 | const enhancer = compose( 19 | applyMiddleware(...middleware) 20 | ); 21 | const store = createStore( 22 | reducers, 23 | { 24 | project: project, 25 | user: user 26 | }, 27 | enhancer 28 | ); 29 | return store; 30 | } 31 | 32 | export default configureStore; 33 | -------------------------------------------------------------------------------- /client/src/shared/redux/configureStore/communityUserPage.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, 4 | applyMiddleware, 5 | compose } from 'redux'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import community from '../reducers/community'; 8 | import user from '../reducers/user'; 9 | 10 | //TODO: remove logger for only production mode 11 | 12 | const reducers = combineReducers({ 13 | community, 14 | user 15 | }); 16 | 17 | const configureStore = ({user, community}, init) => { 18 | const middleware = [thunkMiddleware]; 19 | const enhancer = compose( 20 | applyMiddleware(...middleware) 21 | ); 22 | const store = createStore( 23 | reducers, 24 | { 25 | community: community, 26 | user: user 27 | }, 28 | enhancer 29 | ); 30 | return store; 31 | } 32 | 33 | export default configureStore; 34 | -------------------------------------------------------------------------------- /client/scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | const argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI or in coverage mode 22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 23 | argv.push('--watch'); 24 | } 25 | 26 | 27 | jest.run(argv); 28 | -------------------------------------------------------------------------------- /server/api1/notifs.js: -------------------------------------------------------------------------------- 1 | import Notif from '../models/Notif'; 2 | 3 | export const createNotif = ({ 4 | fromUser, 5 | toUser, 6 | action, 7 | ref 8 | }) => { 9 | if (fromUser.toString === toUser.toString) return; 10 | const newNotif = new Notif(); 11 | newNotif.fromUser = fromUser; 12 | newNotif.toUser = toUser; 13 | newNotif.action = action; 14 | newNotif.ref = ref; 15 | newNotif.save(); 16 | }; 17 | 18 | export const getNotifs = (findCriteria, reqLimit, reqPage, cbSuccess, cbFailure) => { 19 | const limit = reqLimit ? parseInt(reqLimit, 10) : 5; 20 | const page = reqPage ? parseInt(reqPage, 10) : 1; 21 | const options = { 22 | page, limit, sort: { createdAt: -1 } 23 | }; 24 | return Notif.paginate(findCriteria, options, (err, notifs) => { 25 | if (err) return cbFailure(err); 26 | return cbSuccess(notifs.docs); 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /server/models/Notif.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import mongoosePaginate from 'mongoose-paginate'; 3 | 4 | 5 | // action may be: ['STARTED_FOLLOWING', "INVITED_TO_PROJECT", "STARTED_CONTRIBUTE_TO_PROJECT"] 6 | const schema = mongoose.Schema({ 7 | createdAt: { type: Date, default: Date.now }, 8 | fromUser: { type: mongoose.Schema.ObjectId, ref: 'User' }, 9 | toUser: { type: mongoose.Schema.ObjectId, ref: 'User' }, 10 | action: { type: String, required: true }, 11 | ref: { type: String, required: true }, 12 | read: { type: Boolean, default: false } 13 | }); 14 | 15 | // projectSchema.path('title').required(true, 'Project title cannot be blank'); 16 | 17 | schema.plugin(mongoosePaginate); 18 | 19 | // Creating and exporting the user model ======================================= 20 | const Notif = mongoose.model('Notif', schema); 21 | 22 | module.exports = Notif; 23 | -------------------------------------------------------------------------------- /client/src/shared/redux/configureStore/initUserPage.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, // Use the combineReducers function to create one root reducer. In bigger apps you will have many reducers. 4 | applyMiddleware, 5 | compose } from 'redux'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import loggerMiddleware from 'redux-logger'; 8 | import user from '../reducers/user'; 9 | 10 | // NOTE: The init[blank]Page are exclusively used by CRA for testing only 11 | export default function (initialStore={}) { 12 | const reducers = combineReducers({ 13 | user 14 | }); 15 | const middleware = [thunkMiddleware, loggerMiddleware]; 16 | return compose( 17 | applyMiddleware(...middleware) 18 | )(createStore)(reducers, initialStore); // the initialStore value is passed into the Redux createStore function. The store is now hydrated with the data from the server 19 | } 20 | -------------------------------------------------------------------------------- /client/src/shared/redux/configureStore/initExplorePage.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, // Use the combineReducers function to create one root reducer. In bigger apps you will have many reducers. 4 | applyMiddleware, 5 | compose } from 'redux'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import loggerMiddleware from 'redux-logger'; 8 | import user from '../reducers/user'; 9 | 10 | // NOTE: The init[blank]Page are exclusively used by CRA for testing only 11 | export default function (initialStore={}) { 12 | const reducers = combineReducers({ 13 | user 14 | }); 15 | const middleware = [thunkMiddleware, loggerMiddleware]; 16 | return compose( 17 | applyMiddleware(...middleware) 18 | )(createStore)(reducers, initialStore); // the initialStore value is passed into the Redux createStore function. The store is now hydrated with the data from the server 19 | } 20 | -------------------------------------------------------------------------------- /client/src/shared/components/Discussion/Comments.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Discussion from './Main'; 4 | 5 | // Comments is called by Post 6 | const Comments = ({ 7 | postId, 8 | loggedinUser, 9 | updateParentPostCommentsNum 10 | }) => ( 11 |
12 | 20 |
21 | ); 22 | 23 | Comments.propTypes = { 24 | postId: PropTypes.string.isRequired, 25 | loggedinUser: PropTypes.object.isRequired, 26 | updateParentPostCommentsNum: PropTypes.func.isRequired 27 | }; 28 | 29 | export default Comments; 30 | -------------------------------------------------------------------------------- /client/src/shared/redux/configureStore/initCommunityGuestPage.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, // Use the combineReducers function to create one root reducer. In bigger apps you will have many reducers. 4 | applyMiddleware, 5 | compose } from 'redux'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import loggerMiddleware from 'redux-logger'; 8 | import community from '../reducers/community'; 9 | 10 | // NOTE: The init[blank]Page are exclusively used by CRA for testing only 11 | export default function (initialStore={}) { 12 | const reducers = combineReducers({ 13 | community 14 | }); 15 | const middleware = [thunkMiddleware, loggerMiddleware]; 16 | return compose( 17 | applyMiddleware(...middleware) 18 | )(createStore)(reducers, initialStore); // the initialStore value is passed into the Redux createStore function. The store is now hydrated with the data from the server 19 | } 20 | -------------------------------------------------------------------------------- /client/src/shared/components/Login/LoginPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import LoginForm from './LoginForm'; 4 | import appRoute from '../../data/appRoute'; 5 | import LegalInfo from './LegalInfo'; 6 | 7 | const LoginPage = ({ route }) => ( 8 |
9 |
10 | 15 |
16 |
17 |
18 | New to LooseLeaf? Sign up 19 |
20 |
21 | 22 |
23 |
24 |
25 | ); 26 | 27 | export default LoginPage; 28 | -------------------------------------------------------------------------------- /client/src/shared/components/Login/SignupPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import LoginForm from './LoginForm'; 4 | import appRoute from '../../data/appRoute'; 5 | import LegalInfo from './LegalInfo'; 6 | 7 | const SignupPage = ({ route }) => ( 8 |
9 |
10 | 15 |
16 |
17 |
18 | Already a member? Log in 19 |
20 |
21 | 22 |
23 |
24 |
25 | ); 26 | 27 | export default SignupPage; 28 | -------------------------------------------------------------------------------- /client/src/shared/CommunityGuest/Tabs/Two.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TopNav from '../TopNav'; 3 | import { communityPage as page } from '../../data/appPage'; 4 | import Discussion from '../../components/Discussion/Main'; 5 | 6 | export default class Two extends React.Component { 7 | render() { 8 | const context = { name: 'community', queryBy: this.props.community.slug }; 9 | return ( 10 |
11 | 12 |
13 |
14 |

{page(this.props.community.slug).two.name}

15 | 18 |
19 |
20 |
21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/src/shared/redux/Recipe/reducers/recipes.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_RECIPES, 3 | GET_FEATURED_RECIPE } from '../actions/recipes'; 4 | // Include the constants from the action creators. 5 | 6 | export default function recipes(state = {}, action) { 7 | switch (action.type) { 8 | case GET_RECIPES: 9 | return { 10 | ...state, // Use the spread operator to clone the state object. This maintains the immutable store. 11 | recipes: action.data, // Use the data from the action to override the current state so that the new app state is the old app state with the modified data 12 | }; 13 | case GET_FEATURED_RECIPE: 14 | return { 15 | ...state, 16 | featuredRecipe: action.data, 17 | }; 18 | default: 19 | return state; // If the reducer is triggered but no case matches, return the current store state. No changes are required so you don’t need to create a new object. 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/src/shared/redux/configureStore/initPostPage.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, // Use the combineReducers function to create one root reducer. In bigger apps you will have many reducers. 4 | applyMiddleware, 5 | compose } from 'redux'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import loggerMiddleware from 'redux-logger'; 8 | import post from '../reducers/post'; 9 | import user from '../reducers/user'; 10 | 11 | // NOTE: The init[blank]Page are exclusively used by CRA for testing only 12 | export default function (initialStore={}) { 13 | const reducers = combineReducers({ 14 | post, 15 | user 16 | }); 17 | const middleware = [thunkMiddleware, loggerMiddleware]; 18 | return compose( 19 | applyMiddleware(...middleware) 20 | )(createStore)(reducers, initialStore); // the initialStore value is passed into the Redux createStore function. The store is now hydrated with the data from the server 21 | } 22 | -------------------------------------------------------------------------------- /test/txt.js: -------------------------------------------------------------------------------- 1 | const app = require('../server/server'); 2 | const supertest = require('supertest'); 3 | 4 | describe('plain text response', () => { 5 | let request; 6 | beforeEach(() => { 7 | request = supertest(app) // SuperTest builds up the request. 8 | .get('/test/') // You visit the "/test" URL. 9 | .set('User-Agent', 'my cool browser') 10 | .set('Accept', 'text/plain'); // Sets a header describing what content type we want back from the server 11 | }); 12 | it('returns a plain text response', (done) => { 13 | request 14 | .expect('Content-Type', /text\/plain/) 15 | .expect(200) 16 | .end(done); 17 | }); 18 | it('returns your User Agent', (done) => { 19 | request 20 | .expect((res) => { 21 | if (res.text !== 'my cool browser') { 22 | throw new Error('Response does not contain User Agent'); 23 | } 24 | }) 25 | .end(done); 26 | }); 27 | // ... 28 | }); 29 | -------------------------------------------------------------------------------- /client/config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | 18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. 19 | // We don't polyfill it in the browser--this is user's responsibility. 20 | if (process.env.NODE_ENV === 'test') { 21 | require('raf').polyfill(global); 22 | } 23 | -------------------------------------------------------------------------------- /client/src/assets/markdown/careers.md: -------------------------------------------------------------------------------- 1 | # Careers at LooseLeaf 2 | 3 | We do not have any full-time positions available now but we are looking for freelancers, interns, and apprentices in these areas: 4 | 5 | ## Front-end Developer 6 | 7 | - React and Redux 8 | - Vanilla CSS and CSS framework / preprocessor like SASS or LESS 9 | - UX/UI Design 10 | - [our stack](https://stackshare.io/looseleaf/looseleaf) 11 | 12 | ## Full-stack Developer and DevOps 13 | 14 | - Node, Express, Mongo, Mongoose 15 | - Isomorphic application 16 | - REST API 17 | - AWS ECS, AWS EFS, Docker 18 | - [our stack](https://stackshare.io/looseleaf/looseleaf) 19 | 20 | 21 | ## Sales and Marketing 22 | 23 | - Email Marketing 24 | - Newsletters writing 25 | - Coordinating Meetup events and hackathons 26 | - Social Media manager 27 | - Press release 28 | 29 | If you are interested in any of these positions, please email us at hello@looseleafapp.com. 30 | -------------------------------------------------------------------------------- /client/src/shared/redux/configureStore/initProjectPage.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, // Use the combineReducers function to create one root reducer. In bigger apps you will have many reducers. 4 | applyMiddleware, 5 | compose } from 'redux'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import loggerMiddleware from 'redux-logger'; 8 | import project from '../reducers/project'; 9 | import user from '../reducers/user'; 10 | 11 | // NOTE: The init[blank]Page are exclusively used by CRA for testing only 12 | export default function (initialStore={}) { 13 | const reducers = combineReducers({ 14 | project, 15 | user 16 | }); 17 | const middleware = [thunkMiddleware, loggerMiddleware]; 18 | return compose( 19 | applyMiddleware(...middleware) 20 | )(createStore)(reducers, initialStore); // the initialStore value is passed into the Redux createStore function. The store is now hydrated with the data from the server 21 | } 22 | -------------------------------------------------------------------------------- /client/src/shared/redux/configureStore/initCommunityUserPage.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | combineReducers, // Use the combineReducers function to create one root reducer. In bigger apps you will have many reducers. 4 | applyMiddleware, 5 | compose } from 'redux'; 6 | import thunkMiddleware from 'redux-thunk'; 7 | import loggerMiddleware from 'redux-logger'; 8 | import community from '../reducers/community'; 9 | import user from '../reducers/user'; 10 | 11 | // NOTE: The init[blank]Page are exclusively used by CRA for testing only 12 | export default function (initialStore={}) { 13 | const reducers = combineReducers({ 14 | community, 15 | user 16 | }); 17 | const middleware = [thunkMiddleware, loggerMiddleware]; 18 | return compose( 19 | applyMiddleware(...middleware) 20 | )(createStore)(reducers, initialStore); // the initialStore value is passed into the Redux createStore function. The store is now hydrated with the data from the server 21 | } 22 | -------------------------------------------------------------------------------- /client/src/shared/redux/Recipe/reducers/projects.js: -------------------------------------------------------------------------------- 1 | import { 2 | GET_NEW_PROJECTS, 3 | GET_COMPLETED_PROJECTS } from '../actions/projects'; 4 | // Include the constants from the action creators. 5 | 6 | export default function projects(state = {}, action) { 7 | switch (action.type) { 8 | case GET_NEW_PROJECTS: 9 | return { 10 | ...state, // Use the spread operator to clone the state object. This maintains the immutable store. 11 | newProjects: action.data, // Use the data from the action to override the current state so that the new app state is the old app state with the modified data 12 | }; 13 | case GET_COMPLETED_PROJECTS: 14 | return { 15 | ...state, 16 | completedProjects: action.data, 17 | }; 18 | 19 | default: 20 | return state; // If the reducer is triggered but no case matches, return the current store state. No changes are required so you don’t need to create a new object. 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client/src/shared/Explore/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderRoutes } from 'react-router-config'; 3 | import { connect } from 'react-redux'; 4 | import { withRouter } from "react-router-dom"; 5 | import { bindActionCreators } from 'redux'; 6 | import * as actionCreators from '../redux/actions/page'; 7 | import { routes } from './routes'; 8 | 9 | class AppContainer extends React.Component { 10 | render() { 11 | return ( 12 | 13 | ); 14 | } 15 | } 16 | const App = ({ state, actions }) => ( 17 |
18 | {renderRoutes(routes, { state })} 19 |
20 | ); 21 | 22 | // This function lets you convert the app state to properties on your component. 23 | function mapStateToProps(state) { 24 | return {state: state}; 25 | } 26 | 27 | function mapDispatchToProps(dispatch) { 28 | return { actions: bindActionCreators(actionCreators, dispatch) }; 29 | } 30 | export default withRouter( 31 | connect(mapStateToProps, mapDispatchToProps)(AppContainer) 32 | ); 33 | -------------------------------------------------------------------------------- /client/src/shared/Post/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { renderRoutes } from 'react-router-config'; 3 | import { connect } from 'react-redux'; 4 | import { withRouter } from "react-router-dom"; 5 | import { bindActionCreators } from 'redux'; 6 | import * as actionCreators from '../redux/actions/page'; 7 | import { getRoutes } from './routes'; 8 | 9 | class AppContainer extends React.Component { 10 | render() { 11 | return ( 12 | 13 | ); 14 | } 15 | } 16 | const App = ({ state, actions }) => ( 17 |
18 | {renderRoutes(getRoutes(state.post.main._id), {state, actions})} 19 |
20 | ); 21 | 22 | // This function lets you convert the app state to properties on your component. 23 | function mapStateToProps(state) { 24 | return {state: state}; 25 | } 26 | 27 | function mapDispatchToProps(dispatch) { 28 | return { actions: bindActionCreators(actionCreators, dispatch) }; 29 | } 30 | export default withRouter( 31 | connect(mapStateToProps, mapDispatchToProps)(AppContainer) 32 | ); 33 | -------------------------------------------------------------------------------- /client/src/shared/CommunityGuest/Tabs/One.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TopNav from '../TopNav'; 3 | import { communityPage as page } from '../../data/appPage'; 4 | import Projects from '../../components/Collection/Projects/Main'; 5 | 6 | export default class One extends React.Component { 7 | render() { 8 | return ( 9 |
10 | 11 |
12 |
13 |
14 |

{page(this.props.community.slug).one.slug}

15 | 20 |
21 |
22 |
23 |
24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/src/shared/ProjectPage/routes.js: -------------------------------------------------------------------------------- 1 | import Root from './Root'; 2 | import Page from './screens/Page/Main'; 3 | import New from './screens/New/Main'; 4 | import Edit from './screens/Edit/Main'; 5 | import NotFound from '../components/NotFound'; 6 | import appRoute from '../data/appRoute'; 7 | 8 | const getRoutes = slug => { 9 | 10 | const routes = [ 11 | { 12 | component: Root, 13 | routes: [ 14 | { 15 | path: appRoute('projectPage')(slug), 16 | component: Page 17 | }, 18 | { 19 | path: appRoute('newProject'), 20 | exact: true, 21 | component: New 22 | }, 23 | { 24 | path: appRoute('editProject')(slug), 25 | exact: true, 26 | component: Edit 27 | }, 28 | { 29 | path: appRoute('projectWildcard'), 30 | component: NotFound 31 | }, 32 | ] 33 | } 34 | ]; 35 | return routes; 36 | }; 37 | 38 | // Caller: 39 | // getRoutes is used by App.js 40 | // getNav is used by TopNav.js 41 | export { getRoutes }; 42 | -------------------------------------------------------------------------------- /server/models/Post.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import mongoosePaginate from 'mongoose-paginate'; 3 | 4 | const schema = mongoose.Schema({ 5 | createdAt: { type: Date, default: Date.now }, 6 | postedBy: { type: mongoose.Schema.ObjectId, ref: 'User' }, 7 | context: { 8 | project: { type: mongoose.Schema.ObjectId, ref: 'Project' }, 9 | post: { type: mongoose.Schema.ObjectId, ref: 'Post' }, 10 | community: { type: String } 11 | }, 12 | content: { 13 | type: String, 14 | required: true 15 | }, 16 | editedOn: { type: Date, default: null }, 17 | tags: Array, 18 | hearts: [{ type: mongoose.Schema.ObjectId, ref: 'User' }], 19 | thumbUps: [{ type: mongoose.Schema.ObjectId, ref: 'User' }], 20 | comments: { type: Array, default: [] } 21 | }); 22 | 23 | // projectSchema.path('title').required(true, 'Project title cannot be blank'); 24 | 25 | schema.plugin(mongoosePaginate); 26 | 27 | // Creating and exporting the user model ======================================= 28 | const Post = mongoose.model('Post', schema); 29 | 30 | module.exports = Post; 31 | -------------------------------------------------------------------------------- /client/src/shared/components/FlashNotif.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class FlashNotif extends React.Component { 5 | static defaultProps = { 6 | msg: 'Error. something wrong with your login' 7 | } 8 | renderNotif(state, text) { 9 | switch (state) { 10 | case 'error': 11 | return this.renderError(text); 12 | case 'success': 13 | return this.renderSuccess(text) 14 | default: 15 | return null; 16 | } 17 | } 18 | renderError(msg) { 19 | return ( 20 |

{msg}

21 | ); 22 | } 23 | renderSuccess(msg) { 24 | return ( 25 |

{msg}

26 | ); 27 | } 28 | render() { 29 | return ( 30 |
31 | { this.renderNotif(this.props.state, this.props.msg) } 32 |
33 | ); 34 | } 35 | } 36 | FlashNotif.propTypes = { 37 | state: PropTypes.string.isRequired, 38 | msg: PropTypes.string 39 | }; 40 | 41 | export default FlashNotif; 42 | -------------------------------------------------------------------------------- /client/src/shared/components/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route } from 'react-router-dom'; 3 | import Footer from './Footer'; 4 | 5 | const containerStyle = { 6 | minHeight: '58vh', 7 | overflow: 'hidden', 8 | display: 'block', 9 | position: 'relative', 10 | paddingBottom: '23%', 11 | width: '85%' 12 | }; 13 | 14 | const h1Style = { 15 | fontSize: '2em', 16 | fontWeight: '400' 17 | }; 18 | 19 | const Status = ({ code, children }) => ( 20 | { 21 | if (staticContext) { 22 | staticContext.status = code; 23 | } 24 | return children; 25 | }} 26 | /> 27 | ); 28 | 29 | const NotFound = () => ( 30 | 31 |
32 |

Page Not Found

33 |

Sorry, but the page you were trying to view does not exist.

34 | Go to home page 35 |
36 |