├── .prettierignore ├── client ├── .env.example ├── app │ ├── styles │ │ ├── core │ │ │ ├── _rtl.scss │ │ │ ├── _fixes.scss │ │ │ ├── _homepage.scss │ │ │ ├── _users.scss │ │ │ ├── page404.scss │ │ │ ├── _shop.scss │ │ │ ├── utils │ │ │ │ └── _fonts.scss │ │ │ ├── _radio.scss │ │ │ ├── _checkout.scss │ │ │ ├── _badge.scss │ │ │ ├── _merchant.scss │ │ │ ├── _table.scss │ │ │ ├── _newsletter.scss │ │ │ ├── _coming-soon.scss │ │ │ ├── _address.scss │ │ │ ├── _account.scss │ │ │ ├── _brand.scss │ │ │ ├── _login.scss │ │ │ ├── _category.scss │ │ │ ├── _spinner.scss │ │ │ ├── _share.scss │ │ │ ├── core.scss │ │ │ ├── _switch.scss │ │ │ ├── _checkbox.scss │ │ │ ├── _subpage.scss │ │ │ └── _search.scss │ │ ├── _custom.scss │ │ └── style.scss │ ├── contexts │ │ └── Socket │ │ │ ├── context.js │ │ │ ├── useSocket.js │ │ │ ├── index.js │ │ │ └── provider.js │ ├── containers │ │ ├── Homepage │ │ │ ├── constants.js │ │ │ ├── actions.js │ │ │ ├── banners.json │ │ │ └── reducer.js │ │ ├── Shop │ │ │ ├── constants.js │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ ├── Application │ │ │ ├── constants.js │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ ├── Dashboard │ │ │ ├── constants.js │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ ├── NavigationMenu │ │ │ ├── constants.js │ │ │ ├── actions.js │ │ │ └── reducer.js │ │ ├── Authentication │ │ │ ├── constants.js │ │ │ ├── actions.js │ │ │ ├── reducer.js │ │ │ └── index.js │ │ ├── WishList │ │ │ ├── constants.js │ │ │ ├── reducer.js │ │ │ ├── index.js │ │ │ └── actions.js │ │ ├── Contact │ │ │ ├── constants.js │ │ │ └── reducer.js │ │ ├── Newsletter │ │ │ ├── constants.js │ │ │ ├── reducer.js │ │ │ ├── index.js │ │ │ └── actions.js │ │ ├── Account │ │ │ ├── constants.js │ │ │ ├── index.js │ │ │ └── reducer.js │ │ ├── Users │ │ │ ├── constants.js │ │ │ ├── reducer.js │ │ │ └── actions.js │ │ ├── ResetPassword │ │ │ ├── constants.js │ │ │ ├── reducer.js │ │ │ └── index.js │ │ ├── ForgotPassword │ │ │ ├── constants.js │ │ │ ├── reducer.js │ │ │ └── actions.js │ │ ├── Login │ │ │ ├── constants.js │ │ │ └── reducer.js │ │ ├── Cart │ │ │ ├── constants.js │ │ │ └── reducer.js │ │ ├── Signup │ │ │ ├── constants.js │ │ │ └── reducer.js │ │ ├── Navigation │ │ │ ├── constants.js │ │ │ ├── reducer.js │ │ │ └── actions.js │ │ ├── Order │ │ │ ├── constants.js │ │ │ └── index.js │ │ ├── Review │ │ │ └── constants.js │ │ ├── BrandsPage │ │ │ └── index.js │ │ ├── Address │ │ │ ├── constants.js │ │ │ ├── index.js │ │ │ ├── Add.js │ │ │ ├── List.js │ │ │ └── Edit.js │ │ ├── Brand │ │ │ ├── constants.js │ │ │ ├── Add.js │ │ │ ├── index.js │ │ │ ├── List.js │ │ │ └── Edit.js │ │ ├── Support │ │ │ └── index.js │ │ ├── Notification │ │ │ └── index.js │ │ ├── Category │ │ │ ├── constants.js │ │ │ ├── index.js │ │ │ ├── Add.js │ │ │ └── List.js │ │ ├── Merchant │ │ │ ├── constants.js │ │ │ ├── index.js │ │ │ └── Add.js │ │ ├── AuthSuccess │ │ │ └── index.js │ │ ├── Product │ │ │ ├── index.js │ │ │ ├── constants.js │ │ │ ├── Add.js │ │ │ └── List.js │ │ ├── AccountSecurity │ │ │ └── index.js │ │ ├── ProductsShop │ │ │ └── index.js │ │ ├── BrandsShop │ │ │ └── index.js │ │ └── CategoryShop │ │ │ └── index.js │ ├── utils │ │ ├── store.js │ │ ├── app.js │ │ ├── token.js │ │ ├── base64.js │ │ ├── validation.js │ │ ├── date.js │ │ ├── select.js │ │ ├── index.js │ │ └── error.js │ ├── index.js │ ├── components │ │ ├── Common │ │ │ ├── Page404 │ │ │ │ └── index.js │ │ │ ├── ComingSoon │ │ │ │ └── index.js │ │ │ ├── NotFound │ │ │ │ └── index.js │ │ │ ├── Tooltip │ │ │ │ └── index.js │ │ │ ├── CarouselSlider │ │ │ │ ├── utils.js │ │ │ │ └── index.js │ │ │ ├── Popover │ │ │ │ └── index.js │ │ │ ├── LoadingIndicator │ │ │ │ └── index.js │ │ │ ├── SignupProvider │ │ │ │ └── index.js │ │ │ ├── DropdownConfirm │ │ │ │ └── index.js │ │ │ ├── CartIcon │ │ │ │ └── index.js │ │ │ ├── Badge │ │ │ │ └── index.js │ │ │ ├── Pagination │ │ │ │ └── index.js │ │ │ ├── Checkbox │ │ │ │ └── index.js │ │ │ ├── Switch │ │ │ │ └── index.js │ │ │ └── Radio │ │ │ │ └── index.js │ │ ├── Manager │ │ │ ├── SearchResultMeta │ │ │ │ └── index.js │ │ │ ├── UserSearch │ │ │ │ └── index.js │ │ │ ├── OrderSearch │ │ │ │ └── index.js │ │ │ ├── MerchantSearch │ │ │ │ └── index.js │ │ │ ├── UserRole │ │ │ │ └── index.js │ │ │ ├── SubPage │ │ │ │ └── index.js │ │ │ ├── CategoryList │ │ │ │ └── index.js │ │ │ ├── DisabledAccount │ │ │ │ └── Merchant.js │ │ │ ├── Support │ │ │ │ └── AddMessage.js │ │ │ ├── OrderDetails │ │ │ │ └── index.js │ │ │ ├── ProductList │ │ │ │ └── index.js │ │ │ ├── BrandList │ │ │ │ └── index.js │ │ │ ├── UserList │ │ │ │ └── index.js │ │ │ ├── OrderSummary │ │ │ │ └── index.js │ │ │ ├── AddressList │ │ │ │ └── index.js │ │ │ └── Dashboard │ │ │ │ ├── Customer.js │ │ │ │ └── Merchant.js │ │ └── Store │ │ │ ├── AddToWishList │ │ │ └── index.js │ │ │ ├── BrandList │ │ │ └── index.js │ │ │ ├── Checkout │ │ │ └── index.js │ │ │ ├── CartSummary │ │ │ └── index.js │ │ │ ├── ProductReviews │ │ │ └── index.js │ │ │ ├── MiniBrand │ │ │ └── index.js │ │ │ └── SocialShare │ │ │ └── index.js │ ├── scrollToTop.js │ ├── constants │ │ └── index.js │ ├── store.js │ └── app.js ├── vercel.json ├── public │ ├── images │ │ ├── pwa.png │ │ ├── bars.png │ │ ├── favicon.ico │ │ ├── banners │ │ │ ├── banner-1.jpg │ │ │ ├── banner-2.jpg │ │ │ ├── banner-3.jpg │ │ │ ├── banner-4.jpg │ │ │ ├── banner-5.jpg │ │ │ ├── banner-6.jpg │ │ │ └── banner-7.jpg │ │ ├── placeholder-image.png │ │ ├── chevron-down.svg │ │ ├── bag.svg │ │ ├── close.svg │ │ ├── arrow-right.svg │ │ └── social-icons │ │ │ ├── facebook.svg │ │ │ ├── instagram.svg │ │ │ ├── pinterest.svg │ │ │ └── twitter.svg │ └── index.html ├── .babelrc ├── Dockerfile └── webpack │ └── webpack.common.js ├── server ├── config │ ├── tax.js │ └── keys.js ├── middleware │ ├── auth.js │ └── role.js ├── utils │ ├── utils.js │ ├── auth.js │ ├── db.js │ ├── storage.js │ └── seed.js ├── vercel.json ├── nodemon.json ├── routes │ ├── index.js │ └── api │ │ ├── newsletter.js │ │ ├── index.js │ │ └── contact.js ├── Dockerfile ├── models │ ├── contact.js │ ├── order.js │ ├── wishlist.js │ ├── address.js │ ├── brand.js │ ├── category.js │ ├── merchant.js │ ├── review.js │ ├── product.js │ ├── user.js │ └── cart.js ├── .env.example ├── services │ └── mailchimp.js ├── constants │ └── index.js ├── index.js ├── package.json └── socket │ └── index.js ├── SECURITY.md ├── .gitignore ├── .vscode └── settings.json ├── dockercompose.yml ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── LICENSE └── package.json /.prettierignore: -------------------------------------------------------------------------------- 1 | README.md 2 | -------------------------------------------------------------------------------- /client/.env.example: -------------------------------------------------------------------------------- 1 | API_URL=http://localhost:3000/api -------------------------------------------------------------------------------- /client/app/styles/core/_rtl.scss: -------------------------------------------------------------------------------- 1 | // rtl styles 2 | -------------------------------------------------------------------------------- /client/app/styles/core/_fixes.scss: -------------------------------------------------------------------------------- 1 | // fixes styles 2 | -------------------------------------------------------------------------------- /client/app/styles/_custom.scss: -------------------------------------------------------------------------------- 1 | // Here you can add other styles 2 | -------------------------------------------------------------------------------- /client/app/styles/core/_homepage.scss: -------------------------------------------------------------------------------- 1 | // .homepage { 2 | // } 3 | -------------------------------------------------------------------------------- /client/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "routes": [{ "src": "/[^.]+", "dest": "/", "status": 200 }] 3 | } 4 | -------------------------------------------------------------------------------- /client/public/images/pwa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowMan128/ecommerce/HEAD/client/public/images/pwa.png -------------------------------------------------------------------------------- /client/public/images/bars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowMan128/ecommerce/HEAD/client/public/images/bars.png -------------------------------------------------------------------------------- /client/public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowMan128/ecommerce/HEAD/client/public/images/favicon.ico -------------------------------------------------------------------------------- /server/config/tax.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stateCode: 'CA', 3 | stateName: 'California', 4 | stateTaxRate: 0.05 5 | }; 6 | -------------------------------------------------------------------------------- /client/public/images/banners/banner-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowMan128/ecommerce/HEAD/client/public/images/banners/banner-1.jpg -------------------------------------------------------------------------------- /client/public/images/banners/banner-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowMan128/ecommerce/HEAD/client/public/images/banners/banner-2.jpg -------------------------------------------------------------------------------- /client/public/images/banners/banner-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowMan128/ecommerce/HEAD/client/public/images/banners/banner-3.jpg -------------------------------------------------------------------------------- /client/public/images/banners/banner-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowMan128/ecommerce/HEAD/client/public/images/banners/banner-4.jpg -------------------------------------------------------------------------------- /client/public/images/banners/banner-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowMan128/ecommerce/HEAD/client/public/images/banners/banner-5.jpg -------------------------------------------------------------------------------- /client/public/images/banners/banner-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowMan128/ecommerce/HEAD/client/public/images/banners/banner-6.jpg -------------------------------------------------------------------------------- /client/public/images/banners/banner-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowMan128/ecommerce/HEAD/client/public/images/banners/banner-7.jpg -------------------------------------------------------------------------------- /client/public/images/placeholder-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowMan128/ecommerce/HEAD/client/public/images/placeholder-image.png -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": ["@babel/plugin-proposal-class-properties"] 4 | } 5 | -------------------------------------------------------------------------------- /client/app/contexts/Socket/context.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const SocketContext = React.createContext(); 4 | 5 | export default SocketContext; 6 | -------------------------------------------------------------------------------- /client/app/containers/Homepage/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Homepage constants 4 | * 5 | */ 6 | 7 | export const DEFAULT_ACTION = 'src/Homepage/DEFAULT_ACTION'; 8 | -------------------------------------------------------------------------------- /client/app/containers/Shop/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Products constants 4 | * 5 | */ 6 | 7 | export const DEFAULT_ACTION = 'src/Products/DEFAULT_ACTION'; 8 | -------------------------------------------------------------------------------- /server/middleware/auth.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | 3 | const auth = passport.authenticate('jwt', { session: false }); 4 | 5 | module.exports = auth; 6 | -------------------------------------------------------------------------------- /client/app/containers/Application/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Application constants 4 | * 5 | */ 6 | 7 | export const DEFAULT_ACTION = 'src/Application/DEFAULT_ACTION'; 8 | -------------------------------------------------------------------------------- /client/app/containers/Dashboard/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Dashboard constants 4 | * 5 | */ 6 | 7 | export const TOGGLE_DASHBOARD_MENU = 'src/Dashboard/TOGGLE_DASHBOARD_MENU'; 8 | -------------------------------------------------------------------------------- /client/app/containers/NavigationMenu/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * NavigationMenu constants 4 | * 5 | */ 6 | 7 | export const DEFAULT_ACTION = 'src/NavigationMenu/DEFAULT_ACTION'; 8 | -------------------------------------------------------------------------------- /client/app/utils/store.js: -------------------------------------------------------------------------------- 1 | export const sortOptions = [ 2 | { value: 0, label: 'Newest First' }, 3 | { value: 1, label: 'Price High to Low' }, 4 | { value: 2, label: 'Price Low to High' } 5 | ]; 6 | -------------------------------------------------------------------------------- /server/utils/utils.js: -------------------------------------------------------------------------------- 1 | exports.asyncForEach = async (array, callback) => { 2 | for (let index = 0; index < array.length; index++) { 3 | await callback(array[index], index, array); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /client/app/styles/core/_users.scss: -------------------------------------------------------------------------------- 1 | .users-dashboard { 2 | .u-list { 3 | .user-box { 4 | border-radius: $border-radius-default; 5 | box-shadow: $box-shadow-primary; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client/app/contexts/Socket/useSocket.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import SocketContext from './context'; 4 | 5 | const useSocket = () => useContext(SocketContext); 6 | 7 | export default useSocket; 8 | -------------------------------------------------------------------------------- /client/app/contexts/Socket/index.js: -------------------------------------------------------------------------------- 1 | import SocketProvider from './provider'; 2 | import SocketContext from './context'; 3 | import useSocket from './useSocket'; 4 | 5 | export { SocketProvider, SocketContext, useSocket }; 6 | -------------------------------------------------------------------------------- /client/app/containers/Authentication/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Authentication constants 4 | * 5 | */ 6 | 7 | export const SET_AUTH = 'src/Authentication/SET_AUTH'; 8 | export const CLEAR_AUTH = 'src/Authentication/CLEAR_AUTH'; 9 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Please submit a pull request to report security bugs. 6 | 7 | I will confirm the problem, audit code to find potential similar problems and coordinate the fix. 8 | -------------------------------------------------------------------------------- /client/app/styles/core/page404.scss: -------------------------------------------------------------------------------- 1 | .page-404 { 2 | text-align: center; 3 | color: $font-custom-color; 4 | font-size: $font-size-large; 5 | font-weight: $font-weight-normal; 6 | min-height: 250px; 7 | @include center(); 8 | } 9 | -------------------------------------------------------------------------------- /client/app/containers/WishList/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * WishList constants 4 | * 5 | */ 6 | 7 | export const FETCH_WISHLIST = 'src/WishList/FETCH_WISHLIST'; 8 | export const SET_WISHLIST_LOADING = 'src/WishList/SET_WISHLIST_LOADING'; 9 | -------------------------------------------------------------------------------- /client/app/styles/core/_shop.scss: -------------------------------------------------------------------------------- 1 | .shop-page { 2 | .main { 3 | background-color: $theme-white; 4 | } 5 | 6 | .shop-toolbar { 7 | border-radius: $border-radius-primary; 8 | box-shadow: $box-shadow-primary; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # production 5 | build 6 | dist 7 | 8 | # misc 9 | package.json.bak 10 | .DS_Store 11 | 12 | # config file 13 | .env 14 | 15 | # debug 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* -------------------------------------------------------------------------------- /client/app/containers/Shop/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Shop actions 4 | * 5 | */ 6 | 7 | import { DEFAULT_ACTION } from './constants'; 8 | 9 | export const defaultAction = () => { 10 | return { 11 | type: DEFAULT_ACTION 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /server/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "index.js", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "/" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /client/app/containers/Homepage/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Homepage actions 4 | * 5 | */ 6 | 7 | import { DEFAULT_ACTION } from './constants'; 8 | 9 | export const defaultAction = () => { 10 | return { 11 | type: DEFAULT_ACTION 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /client/app/containers/Application/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Application actions 4 | * 5 | */ 6 | 7 | import { DEFAULT_ACTION } from './constants'; 8 | 9 | export const defaultAction = () => { 10 | return { 11 | type: DEFAULT_ACTION 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /client/app/containers/NavigationMenu/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * NavigationMenu actions 4 | * 5 | */ 6 | 7 | import { DEFAULT_ACTION } from './constants'; 8 | 9 | export const defaultAction = () => { 10 | return { 11 | type: DEFAULT_ACTION 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /client/public/images/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/app/containers/Dashboard/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Dashboard actions 4 | * 5 | */ 6 | 7 | import { TOGGLE_DASHBOARD_MENU } from './constants'; 8 | 9 | export const toggleDashboardMenu = () => { 10 | return { 11 | type: TOGGLE_DASHBOARD_MENU 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /client/app/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * index.js 4 | * This is the entry file for the application 5 | */ 6 | 7 | import React from 'react'; 8 | import ReactDOM from 'react-dom'; 9 | 10 | import App from './app'; 11 | 12 | ReactDOM.render(, document.getElementById('root')); 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "prettier.singleQuote": true, 4 | "prettier.arrowParens": "avoid", 5 | "prettier.jsxSingleQuote": true, 6 | "prettier.trailingComma": "none", 7 | "jumpToAliasFile.alias": { 8 | "app": "client/app" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /server/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": [ 3 | "**/*.test.ts", 4 | "**/*.spec.ts", 5 | "node_modules", 6 | "client", 7 | "build", 8 | "dist" 9 | ], 10 | "verbose": false, 11 | "watch": ["./"], 12 | "env": { 13 | "NODE_ENV": "development" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/app/styles/core/utils/_fonts.scss: -------------------------------------------------------------------------------- 1 | // @import '~@fontsource/poppins/index.css'; 2 | @import '~@fontsource/poppins/300.css'; 3 | @import '~@fontsource/poppins/400.css'; 4 | @import '~@fontsource/poppins/500.css'; 5 | @import '~@fontsource/poppins/600.css'; 6 | @import '~@fontsource/poppins/700.css'; 7 | -------------------------------------------------------------------------------- /client/app/components/Common/Page404/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Page404 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | const Page404 = () => { 10 | return ( 11 |
The page you are looking for was not found.
12 | ); 13 | }; 14 | 15 | export default Page404; 16 | -------------------------------------------------------------------------------- /client/app/containers/Contact/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Contact constants 4 | * 5 | */ 6 | 7 | export const CONTACT_FORM_CHANGE = 'src/Contact/CONTACT_FORM_CHANGE'; 8 | export const SET_CONTACT_FORM_ERRORS = 'src/Contact/SET_CONTACT_FORM_ERRORS'; 9 | export const CONTACT_FORM_RESET = 'src/Contact/CONTACT_FORM_RESET'; 10 | -------------------------------------------------------------------------------- /client/app/containers/Homepage/banners.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "imageUrl": "/images/banners/banner-3.jpg", 4 | "link": "", 5 | "title": "", 6 | "content": "

\n" 7 | }, 8 | { 9 | "imageUrl": "/images/banners/banner-4.jpg", 10 | "link": "", 11 | "title": "", 12 | "content": "

\n" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /client/app/containers/Newsletter/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Newsletter constants 4 | * 5 | */ 6 | 7 | export const NEWSLETTER_CHANGE = 'src/Newsletter/NEWSLETTER_CHANGE'; 8 | export const SET_NEWSLETTER_FORM_ERRORS = 9 | 'src/Newsletter/SET_NEWSLETTER_FORM_ERRORS'; 10 | export const NEWSLETTER_RESET = 'src/Newsletter/NEWSLETTER_RESET'; 11 | -------------------------------------------------------------------------------- /client/app/styles/style.scss: -------------------------------------------------------------------------------- 1 | /* MERN Theme */ 2 | 3 | // Import utilities 4 | @import 'core/utils/fonts'; 5 | @import 'core/utils/reset'; 6 | 7 | // Import Bootstrap source files 8 | @import 'node_modules/bootstrap/scss/bootstrap'; 9 | 10 | // Import core styles 11 | @import 'core/core'; 12 | 13 | // Import custom styles 14 | @import 'custom'; 15 | -------------------------------------------------------------------------------- /client/app/components/Common/ComingSoon/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * ComingSoon 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | const ComingSoon = props => { 10 | return ( 11 |
12 |

Coming soon

13 | {props.children} 14 |
15 | ); 16 | }; 17 | 18 | export default ComingSoon; 19 | -------------------------------------------------------------------------------- /client/app/containers/Account/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Account constants 4 | * 5 | */ 6 | 7 | export const ACCOUNT_CHANGE = 'src/Account/ACCOUNT_CHANGE'; 8 | export const FETCH_PROFILE = 'src/Account/FETCH_PROFILE'; 9 | export const CLEAR_ACCOUNT = 'src/Account/CLEAR_ACCOUNT'; 10 | export const SET_PROFILE_LOADING = 'src/Account/SET_PROFILE_LOADING'; 11 | -------------------------------------------------------------------------------- /client/app/containers/Authentication/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Authentication actions 4 | * 5 | */ 6 | 7 | import { SET_AUTH, CLEAR_AUTH } from './constants'; 8 | 9 | export const setAuth = () => { 10 | return { 11 | type: SET_AUTH 12 | }; 13 | }; 14 | 15 | export const clearAuth = () => { 16 | return { 17 | type: CLEAR_AUTH 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /client/app/containers/Users/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Users constants 4 | * 5 | */ 6 | 7 | export const FETCH_USERS = 'src/Users/FETCH_USERS'; 8 | export const FETCH_SEARCHED_USERS = 'src/Users/FETCH_SEARCHED_USERS'; 9 | export const SET_ADVANCED_FILTERS = 'src/Users/SET_ADVANCED_FILTERS'; 10 | export const SET_USERS_LOADING = 'src/Users/SET_USERS_LOADING'; 11 | -------------------------------------------------------------------------------- /client/app/styles/core/_radio.scss: -------------------------------------------------------------------------------- 1 | .radio { 2 | ul { 3 | list-style: none; 4 | margin: 0 0 1px; 5 | padding: 0; 6 | } 7 | 8 | li:not(:last-child) { 9 | margin-bottom: 5px; 10 | } 11 | 12 | label { 13 | display: flex; 14 | align-items: center; 15 | } 16 | 17 | input[type='radio'] { 18 | margin: 0 10px 0 0; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/app/utils/app.js: -------------------------------------------------------------------------------- 1 | import { ROLES, EMAIL_PROVIDER } from '../constants'; 2 | 3 | export const isProviderAllowed = provider => 4 | provider === EMAIL_PROVIDER.Google || provider === EMAIL_PROVIDER.Facebook; 5 | 6 | export const isDisabledMerchantAccount = user => 7 | user.role === ROLES.Merchant && 8 | user.merchant && 9 | user.merchant.isActive === false; 10 | -------------------------------------------------------------------------------- /client/app/containers/ResetPassword/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * ResetPassword constants 4 | * 5 | */ 6 | 7 | export const RESET_PASSWORD_CHANGE = 'src/ResetPassword/RESET_PASSWORD_CHANGE'; 8 | export const RESET_PASSWORD_RESET = 'src/ResetPassword/RESET_PASSWORD_RESET'; 9 | export const SET_RESET_PASSWORD_FORM_ERRORS = 10 | 'src/ResetPassword/SET_RESET_PASSWORD_FORM_ERRORS'; 11 | -------------------------------------------------------------------------------- /client/app/styles/core/_checkout.scss: -------------------------------------------------------------------------------- 1 | .easy-checkout { 2 | border-top: $border-default; 3 | 4 | .checkout-actions { 5 | padding: 10px; 6 | text-align: center; 7 | 8 | .input-btn { 9 | margin-right: 10px; 10 | 11 | @include media-breakpoint-down(xs) { 12 | width: 100%; 13 | margin-bottom: 10px; 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/app/components/Manager/SearchResultMeta/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * SearchResultMeta 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | const SearchResultMeta = props => { 10 | const { count, label } = props; 11 | 12 | return ( 13 |

14 | {count} {label} 15 |

16 | ); 17 | }; 18 | 19 | export default SearchResultMeta; 20 | -------------------------------------------------------------------------------- /client/app/utils/token.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * token.js 4 | * axios default headers setup 5 | */ 6 | 7 | import axios from 'axios'; 8 | 9 | const setToken = token => { 10 | if (token) { 11 | axios.defaults.headers.common['Authorization'] = token; 12 | } else { 13 | delete axios.defaults.headers.common['Authorization']; 14 | } 15 | }; 16 | 17 | export default setToken; 18 | -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | const router = require('express').Router(); 2 | const apiRoutes = require('./api'); 3 | 4 | const keys = require('../config/keys'); 5 | const { apiURL } = keys.app; 6 | 7 | const api = `/${apiURL}`; 8 | 9 | // api routes 10 | router.use(api, apiRoutes); 11 | router.use(api, (req, res) => res.status(404).json('No API route found')); 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /client/app/containers/ForgotPassword/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * ForgotPassword constants 4 | * 5 | */ 6 | 7 | export const FORGOT_PASSWORD_CHANGE = 8 | 'src/ForgotPassword/FORGOT_PASSWORD_CHANGE'; 9 | export const FORGOT_PASSWORD_RESET = 'src/ForgotPassword/FORGOT_PASSWORD_RESET'; 10 | export const SET_FORGOT_PASSWORD_FORM_ERRORS = 11 | 'src/ForgotPassword/SET_FORGOT_PASSWORD_FORM_ERRORS'; 12 | -------------------------------------------------------------------------------- /dockercompose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | client: 4 | build: client 5 | ports: 6 | - '8080:8080' 7 | server: 8 | build: server 9 | environment: 10 | - PORT=3000 11 | - BASE_API_URL=api 12 | - CLIENT_URL=http://localhost:8080 13 | - JWT_SECRET=update_your_JWT_secret 14 | - MONGO_URI=update_your_mongo_URI 15 | ports: 16 | - '3000:3000' 17 | -------------------------------------------------------------------------------- /client/app/utils/base64.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * base64.js 4 | * this helper formulate buffer data to base64 string 5 | */ 6 | 7 | export const arrayBufferToBase64 = buffer => { 8 | let binary = ''; 9 | let bytes = [].slice.call(new Uint8Array(buffer.data.data)); 10 | bytes.forEach((b) => binary += String.fromCharCode(b)); 11 | return `data:${buffer.contentType};base64,${window.btoa(binary)}`; 12 | }; 13 | -------------------------------------------------------------------------------- /client/app/containers/Login/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Login constants 4 | * 5 | */ 6 | 7 | export const LOGIN_CHANGE = 'src/Login/LOGIN_CHANGE'; 8 | export const LOGIN_RESET = 'src/Login/LOGIN_RESET'; 9 | export const SET_LOGIN_LOADING = 'src/Login/SET_LOGIN_LOADING'; 10 | export const SET_LOGIN_FORM_ERRORS = 'src/Login/SET_LOGIN_FORM_ERRORS'; 11 | export const SET_LOGIN_SUBMITTING = 'src/Login/SET_LOGIN_SUBMITTING'; 12 | -------------------------------------------------------------------------------- /client/app/containers/Cart/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Cart constants 4 | * 5 | */ 6 | 7 | export const HANDLE_CART = 'src/Cart/HANDLE_CART'; 8 | export const ADD_TO_CART = 'src/Cart/ADD_TO_CART'; 9 | export const REMOVE_FROM_CART = 'src/Cart/REMOVE_FROM_CART'; 10 | export const HANDLE_CART_TOTAL = 'src/Cart/HANDLE_CART_TOTAL'; 11 | export const SET_CART_ID = 'src/Cart/SET_CART_ID'; 12 | export const CLEAR_CART = 'src/Cart/CLEAR_CART'; 13 | -------------------------------------------------------------------------------- /client/app/containers/Application/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Application reducer 4 | * 5 | */ 6 | 7 | import { DEFAULT_ACTION } from './constants'; 8 | 9 | const initialState = {}; 10 | 11 | const applicationReducer = (state = initialState, action) => { 12 | switch (action.type) { 13 | case DEFAULT_ACTION: 14 | return state; 15 | default: 16 | return state; 17 | } 18 | }; 19 | 20 | export default applicationReducer; 21 | -------------------------------------------------------------------------------- /client/app/components/Common/NotFound/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * NotFound 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | const NotFound = props => { 10 | const { message, className, children } = props; 11 | return ( 12 |
13 | {message ? message : children} 14 |
15 | ); 16 | }; 17 | 18 | NotFound.defaultProps = { 19 | className: '' 20 | }; 21 | 22 | export default NotFound; 23 | -------------------------------------------------------------------------------- /client/app/containers/Homepage/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Homepage reducer 4 | * 5 | */ 6 | 7 | import { DEFAULT_ACTION } from './constants'; 8 | 9 | const initialState = {}; 10 | 11 | const homepageReducer = (state = initialState, action) => { 12 | let newState; 13 | switch (action.type) { 14 | case DEFAULT_ACTION: 15 | return newState; 16 | default: 17 | return state; 18 | } 19 | }; 20 | 21 | export default homepageReducer; 22 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use official Node.js 14 as base image 2 | FROM node:16.20.2-buster-slim 3 | 4 | # Set working directory 5 | WORKDIR /usr/src/app 6 | 7 | # Copy package.json and package-lock.json 8 | COPY package*.json ./ 9 | 10 | # Install dependencies 11 | RUN npm install 12 | 13 | # Copy the rest of the server code 14 | COPY . . 15 | 16 | # Expose port 3000 for the server 17 | EXPOSE 3000 18 | 19 | # Start the server 20 | CMD [ "npm", "start" ] 21 | -------------------------------------------------------------------------------- /client/app/containers/Shop/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Products reducer 4 | * 5 | */ 6 | 7 | import { DEFAULT_ACTION } from './constants'; 8 | 9 | const initialState = {}; 10 | 11 | const productsReducer = (state = initialState, action) => { 12 | switch (action.type) { 13 | case DEFAULT_ACTION: 14 | return { 15 | ...state 16 | }; 17 | default: 18 | return state; 19 | } 20 | }; 21 | 22 | export default productsReducer; 23 | -------------------------------------------------------------------------------- /server/middleware/role.js: -------------------------------------------------------------------------------- 1 | const check = 2 | (...roles) => 3 | (req, res, next) => { 4 | if (!req.user) { 5 | return res.status(401).send('Unauthorized'); 6 | } 7 | 8 | const hasRole = roles.find(role => req.user.role === role); 9 | if (!hasRole) { 10 | return res.status(403).send('You are not allowed to make this request.'); 11 | } 12 | 13 | return next(); 14 | }; 15 | 16 | const role = { check }; 17 | module.exports = role; 18 | -------------------------------------------------------------------------------- /client/app/containers/NavigationMenu/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * NavigationMenu reducer 4 | * 5 | */ 6 | 7 | import { DEFAULT_ACTION } from './constants'; 8 | 9 | const initialState = {}; 10 | 11 | const navigationMenuReducer = (state = initialState, action) => { 12 | switch (action.type) { 13 | case DEFAULT_ACTION: 14 | return { 15 | ...state 16 | }; 17 | default: 18 | return state; 19 | } 20 | }; 21 | 22 | export default navigationMenuReducer; 23 | -------------------------------------------------------------------------------- /client/app/containers/Signup/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Signup constants 4 | * 5 | */ 6 | 7 | export const SIGNUP_CHANGE = 'src/Signup/SIGNUP_CHANGE'; 8 | export const SIGNUP_RESET = 'src/Signup/SIGNUP_RESET'; 9 | export const SET_SIGNUP_LOADING = 'src/Signup/SET_SIGNUP_LOADING'; 10 | export const SET_SIGNUP_SUBMITTING = 'src/Signup/SET_SIGNUP_SUBMITTING'; 11 | export const SET_SIGNUP_FORM_ERRORS = 'src/Signup/SET_SIGNUP_FORM_ERRORS'; 12 | export const SUBSCRIBE_CHANGE = 'src/Signup/SUBSCRIBE_CHANGE'; 13 | -------------------------------------------------------------------------------- /server/models/contact.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose'); 2 | const { Schema } = Mongoose; 3 | 4 | // Contact Schema 5 | const ContactSchema = new Schema({ 6 | name: { 7 | type: String, 8 | trim: true 9 | }, 10 | email: { 11 | type: String 12 | }, 13 | message: { 14 | type: String, 15 | trim: true 16 | }, 17 | updated: Date, 18 | created: { 19 | type: Date, 20 | default: Date.now 21 | } 22 | }); 23 | 24 | module.exports = Mongoose.model('Contact', ContactSchema); 25 | -------------------------------------------------------------------------------- /server/utils/auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | 3 | const checkAuth = async req => { 4 | try { 5 | if (!req.headers.authorization) { 6 | return null; 7 | } 8 | 9 | const token = 10 | (await jwt.decode(req.headers.authorization.split(' ')[1])) || 11 | req.headers.authorization; 12 | 13 | if (!token) { 14 | return null; 15 | } 16 | 17 | return token; 18 | } catch (error) { 19 | return null; 20 | } 21 | }; 22 | 23 | module.exports = checkAuth; 24 | -------------------------------------------------------------------------------- /client/app/components/Common/Tooltip/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Tooltip 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import { UncontrolledTooltip } from 'reactstrap'; 10 | 11 | const Tooltip = props => { 12 | const { target, placement, children } = props; 13 | 14 | return ( 15 | 16 | {children} 17 | 18 | ); 19 | }; 20 | 21 | Tooltip.defaultProps = { 22 | placement: 'top' 23 | }; 24 | 25 | export default Tooltip; 26 | -------------------------------------------------------------------------------- /client/app/containers/Navigation/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Navigation constants 4 | * 5 | */ 6 | 7 | export const TOGGLE_MENU = 'src/Navigation/TOGGLE_MENU'; 8 | export const TOGGLE_CART = 'src/Navigation/TOGGLE_CART'; 9 | export const TOGGLE_BRAND = 'src/Navigation/TOGGLE_BRAND'; 10 | export const SEARCH_CHANGE = 'src/Navigation/SEARCH_CHANGE'; 11 | export const SUGGESTIONS_FETCH_REQUEST = 12 | 'src/Navigation/SUGGESTIONS_FETCH_REQUEST'; 13 | export const SUGGESTIONS_CLEAR_REQUEST = 14 | 'src/Navigation/SUGGESTIONS_CLEAR_REQUEST'; 15 | -------------------------------------------------------------------------------- /client/app/styles/core/_badge.scss: -------------------------------------------------------------------------------- 1 | .custom-badge { 2 | padding: 8px 10px; 3 | // border-radius: $border-radius-default; 4 | font-weight: $font-weight-normal; 5 | } 6 | 7 | .custom-badge-primary { 8 | background-color: $primary-bg; 9 | color: $white; 10 | } 11 | 12 | .custom-badge-secondary { 13 | background-color: $secondary-bg; 14 | } 15 | 16 | .custom-badge-dark { 17 | background-color: $dark-bg; 18 | color: $white; 19 | } 20 | 21 | .custom-badge-danger { 22 | background-color: $danger-bg; 23 | color: $white; 24 | } 25 | -------------------------------------------------------------------------------- /client/app/styles/core/_merchant.scss: -------------------------------------------------------------------------------- 1 | .sell { 2 | .agreement-banner-text { 3 | background-color: $theme-white; 4 | padding: 30px 16px 30px; 5 | border-radius: $border-radius-primary; 6 | } 7 | 8 | .agreement-banner { 9 | margin: 0 auto; 10 | width: 250px; 11 | 12 | @include media-breakpoint-up(md) { 13 | width: 300px; 14 | } 15 | } 16 | } 17 | 18 | .merchant-dashboard { 19 | .merchant-box { 20 | border-radius: $border-radius-default; 21 | box-shadow: $box-shadow-secondary; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/app/styles/core/_table.scss: -------------------------------------------------------------------------------- 1 | .table-section { 2 | .search { 3 | margin: 15px 0px; 4 | 5 | .search-label { 6 | width: 100%; 7 | } 8 | } 9 | 10 | .react-bootstrap-table { 11 | .table { 12 | overflow-x: scroll; 13 | display: block; 14 | margin: 20px 0px; 15 | color: $font-custom-color; 16 | 17 | td, 18 | th { 19 | white-space: nowrap; 20 | text-overflow: unset; 21 | overflow-wrap: break-word; 22 | width: 100%; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/app/containers/Dashboard/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Dashboard reducer 4 | * 5 | */ 6 | 7 | import { TOGGLE_DASHBOARD_MENU } from './constants'; 8 | 9 | const initialState = { 10 | isMenuOpen: false 11 | }; 12 | 13 | const dashboardReducer = (state = initialState, action) => { 14 | switch (action.type) { 15 | case TOGGLE_DASHBOARD_MENU: 16 | return { 17 | ...state, 18 | isMenuOpen: !state.isMenuOpen 19 | }; 20 | default: 21 | return state; 22 | } 23 | }; 24 | 25 | export default dashboardReducer; 26 | -------------------------------------------------------------------------------- /server/models/order.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose'); 2 | const { Schema } = Mongoose; 3 | 4 | // Order Schema 5 | const OrderSchema = new Schema({ 6 | cart: { 7 | type: Schema.Types.ObjectId, 8 | ref: 'Cart' 9 | }, 10 | user: { 11 | type: Schema.Types.ObjectId, 12 | ref: 'User' 13 | }, 14 | total: { 15 | type: Number, 16 | default: 0 17 | }, 18 | updated: Date, 19 | created: { 20 | type: Date, 21 | default: Date.now 22 | } 23 | }); 24 | 25 | module.exports = Mongoose.model('Order', OrderSchema); 26 | -------------------------------------------------------------------------------- /client/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use official Node.js 14 as base image 2 | FROM node:16.20.2-buster-slim 3 | 4 | # Set working directory 5 | WORKDIR /usr/src/app 6 | 7 | # Copy package.json and package-lock.json 8 | COPY package*.json ./ 9 | 10 | # Install dependencies 11 | RUN npm install 12 | 13 | # Copy the rest of the client code 14 | COPY . . 15 | 16 | RUN mv .env.example .env 17 | 18 | # Build the client for production 19 | RUN npm run build 20 | 21 | # Expose port 8080 for the client 22 | EXPOSE 8080 23 | 24 | # Start the client 25 | CMD [ "npm", "run", "dev" ] 26 | -------------------------------------------------------------------------------- /client/app/containers/Order/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Order constants 4 | * 5 | */ 6 | 7 | export const FETCH_ORDERS = 'src/Orders/FETCH_ORDERS'; 8 | export const FETCH_SEARCHED_ORDERS = 'src/Orders/FETCH_SEARCHED_ORDERS'; 9 | export const FETCH_ORDER = 'src/Order/FETCH_ORDER'; 10 | export const UPDATE_ORDER_STATUS = 'src/Order/UPDATE_ORDER_STATUS'; 11 | export const SET_ORDERS_LOADING = 'src/Orders/SET_ORDERS_LOADING'; 12 | export const SET_ADVANCED_FILTERS = 'src/Orders/SET_ADVANCED_FILTERS'; 13 | export const CLEAR_ORDERS = 'src/Orders/CLEAR_ORDERS'; 14 | -------------------------------------------------------------------------------- /client/app/scrollToTop.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * scrollToTop.js 4 | * scroll restoration 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import { withRouter } from 'react-router-dom'; 10 | 11 | class ScrollToTop extends React.Component { 12 | componentDidUpdate(prevProps) { 13 | if (this.props.location.pathname !== prevProps.location.pathname) { 14 | window.scroll({ 15 | top: 0, 16 | behavior: 'smooth' 17 | }); 18 | } 19 | } 20 | 21 | render() { 22 | return this.props.children; 23 | } 24 | } 25 | 26 | export default withRouter(ScrollToTop); 27 | -------------------------------------------------------------------------------- /client/app/components/Manager/UserSearch/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * UserSearch 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import SearchBar from '../../Common/SearchBar'; 10 | 11 | const UserSearch = props => { 12 | return ( 13 |
14 | 22 |
23 | ); 24 | }; 25 | 26 | export default UserSearch; 27 | -------------------------------------------------------------------------------- /server/.env.example: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | MONGO_URI=mongodb://127.0.0.1:27017/mern_ecommerce 3 | JWT_SECRET= 4 | MAILCHIMP_KEY= 5 | MAILCHIMP_LIST_KEY= 6 | MAILGUN_KEY= 7 | MAILGUN_DOMAIN= 8 | MAILGUN_EMAIL_SENDER= 9 | GOOGLE_CLIENT_ID= 10 | GOOGLE_CLIENT_SECRET= 11 | GOOGLE_CALLBACK_URL=http://localhost:3000/api/auth/google/callback 12 | FACEBOOK_CLIENT_ID= 13 | FACEBOOK_CLIENT_SECRET= 14 | FACEBOOK_CALLBACK_URL=http://localhost:3000/api/auth/facebook/callback 15 | CLIENT_URL=http://localhost:8080 16 | BASE_API_URL=api 17 | AWS_ACCESS_KEY_ID= 18 | AWS_SECRET_ACCESS_KEY= 19 | AWS_REGION=us-east-2 20 | AWS_BUCKET_NAME= -------------------------------------------------------------------------------- /client/app/styles/core/_newsletter.scss: -------------------------------------------------------------------------------- 1 | .newsletter-form { 2 | p { 3 | margin: 0; 4 | } 5 | } 6 | 7 | .subscribe { 8 | .inline-btn-box { 9 | .input-text-block { 10 | @include media-breakpoint-between(md, lg) { 11 | flex-direction: column; 12 | 13 | .input-text { 14 | border-radius: $border-radius-default; 15 | } 16 | 17 | .custom-btn-primary { 18 | margin-top: 10px; 19 | border-left: $border-default !important; 20 | border-radius: $border-radius-default !important; 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/app/components/Manager/OrderSearch/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * OrderSearch 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import SearchBar from '../../Common/SearchBar'; 10 | 11 | const OrderSearch = props => { 12 | return ( 13 |
14 | 22 |
23 | ); 24 | }; 25 | 26 | export default OrderSearch; 27 | -------------------------------------------------------------------------------- /client/public/images/bag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/app/components/Common/CarouselSlider/utils.js: -------------------------------------------------------------------------------- 1 | export const responsiveOneItemCarousel = { 2 | desktop: { 3 | breakpoint: { 4 | max: 3000, 5 | min: 1024 6 | }, 7 | items: 1, 8 | slidesToSlide: 1, 9 | partialVisibilityGutter: 0 10 | }, 11 | mobile: { 12 | breakpoint: { 13 | max: 464, 14 | min: 0 15 | }, 16 | items: 1, 17 | slidesToSlide: 1, 18 | partialVisibilityGutter: 0 19 | }, 20 | tablet: { 21 | breakpoint: { 22 | max: 1024, 23 | min: 200 24 | }, 25 | items: 1, 26 | slidesToSlide: 1, 27 | partialVisibilityGutter: 0 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /client/app/components/Manager/MerchantSearch/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * MerchantSearch 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import SearchBar from '../../Common/SearchBar'; 10 | 11 | const MerchantSearch = props => { 12 | return ( 13 |
14 | 22 |
23 | ); 24 | }; 25 | 26 | export default MerchantSearch; 27 | -------------------------------------------------------------------------------- /client/app/styles/core/_coming-soon.scss: -------------------------------------------------------------------------------- 1 | .coming-soon { 2 | animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; 3 | animation-iteration-count: 1; 4 | transform: translate3d(0, 0, 0); 5 | backface-visibility: hidden; 6 | perspective: 1000px; 7 | text-align: center; 8 | } 9 | 10 | @keyframes shake { 11 | 10%, 12 | 90% { 13 | transform: translate3d(-1px, 0, 0); 14 | } 15 | 16 | 20%, 17 | 80% { 18 | transform: translate3d(2px, 0, 0); 19 | } 20 | 21 | 30%, 22 | 50%, 23 | 70% { 24 | transform: translate3d(-4px, 0, 0); 25 | } 26 | 27 | 40%, 28 | 60% { 29 | transform: translate3d(4px, 0, 0); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /server/models/wishlist.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose'); 2 | const { Schema } = Mongoose; 3 | 4 | // Wishlist Schema 5 | const WishlistSchema = new Schema({ 6 | product: { 7 | type: Schema.Types.ObjectId, 8 | ref: 'Product', 9 | default: null 10 | }, 11 | user: { 12 | type: Schema.Types.ObjectId, 13 | ref: 'User', 14 | default: null 15 | }, 16 | isLiked: { 17 | type: Boolean, 18 | default: false 19 | }, 20 | updated: { 21 | type: Date, 22 | default: Date.now 23 | }, 24 | created: { 25 | type: Date, 26 | default: Date.now 27 | } 28 | }); 29 | 30 | module.exports = Mongoose.model('Wishlist', WishlistSchema); 31 | -------------------------------------------------------------------------------- /client/app/components/Common/Popover/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Popover 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import { UncontrolledPopover, PopoverHeader, PopoverBody } from 'reactstrap'; 10 | 11 | const Popover = props => { 12 | const { target, placement, popoverTitle, children } = props; 13 | 14 | return ( 15 | 16 | {popoverTitle && {popoverTitle}} 17 | {children} 18 | 19 | ); 20 | }; 21 | 22 | Popover.defaultProps = { 23 | placement: 'top' 24 | }; 25 | 26 | export default Popover; 27 | -------------------------------------------------------------------------------- /client/app/containers/Authentication/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Authentication reducer 4 | * 5 | */ 6 | 7 | import { SET_AUTH, CLEAR_AUTH } from './constants'; 8 | 9 | const initialState = { 10 | authenticated: false, 11 | isLoading: false 12 | }; 13 | 14 | const authenticationReducer = (state = initialState, action) => { 15 | switch (action.type) { 16 | case SET_AUTH: 17 | return { 18 | ...state, 19 | authenticated: true 20 | }; 21 | case CLEAR_AUTH: 22 | return { 23 | ...state, 24 | authenticated: false 25 | }; 26 | default: 27 | return state; 28 | } 29 | }; 30 | 31 | export default authenticationReducer; 32 | -------------------------------------------------------------------------------- /client/app/containers/Review/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Review constants 4 | * 5 | */ 6 | 7 | export const FETCH_REVIEWS = 'src/Review/FETCH_REVIEWS'; 8 | export const ADD_REVIEW = 'src/Review/ADD_REVIEW'; 9 | export const REMOVE_REVIEW = 'src/Review/REMOVE_REVIEW'; 10 | export const FETCH_PRODUCT_REVIEWS = 'src/Review/FETCH_PRODUCT_REVIEWS'; 11 | export const REVIEW_CHANGE = 'src/Review/REVIEW_CHANGE'; 12 | export const SET_REVIEWS_LOADING = 'src/Review/SET_REVIEWS_LOADING'; 13 | export const RESET_REVIEW = 'src/Review/RESET_REVIEW'; 14 | export const SET_REVIEW_FORM_ERRORS = 'src/Review/SET_REVIEW_FORM_ERRORS'; 15 | export const SET_ADVANCED_FILTERS = 'src/Review/SET_ADVANCED_FILTERS'; 16 | -------------------------------------------------------------------------------- /client/app/styles/core/_address.scss: -------------------------------------------------------------------------------- 1 | .address-dashboard { 2 | .a-list { 3 | .address-box { 4 | height: 100%; 5 | border-radius: $border-radius-default; 6 | box-shadow: $box-shadow-secondary; 7 | @include transition(); 8 | 9 | &:hover { 10 | background-color: $secondary-bg; 11 | @include transition(); 12 | } 13 | 14 | .address-icon { 15 | width: 35px; 16 | height: 35px; 17 | 18 | @include media-breakpoint-up(lg) { 19 | width: 50px; 20 | height: 50px; 21 | } 22 | } 23 | 24 | .address-desc { 25 | @include text-ellipsis(2); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /server/services/mailchimp.js: -------------------------------------------------------------------------------- 1 | const Mailchimp = require('mailchimp-api-v3'); 2 | 3 | const keys = require('../config/keys'); 4 | 5 | const { key, listKey } = keys.mailchimp; 6 | 7 | class MailchimpService { 8 | init() { 9 | try { 10 | return new Mailchimp(key); 11 | } catch (error) { 12 | console.warn('Missing mailgun keys'); 13 | } 14 | } 15 | } 16 | 17 | const mailchimp = new MailchimpService().init(); 18 | 19 | exports.subscribeToNewsletter = async email => { 20 | try { 21 | return await mailchimp.post(`lists/${listKey}/members`, { 22 | email_address: email, 23 | status: 'subscribed' 24 | }); 25 | } catch (error) { 26 | return error; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /client/app/containers/WishList/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * WishList reducer 4 | * 5 | */ 6 | 7 | import { FETCH_WISHLIST, SET_WISHLIST_LOADING } from './constants'; 8 | 9 | const initialState = { 10 | wishlist: [], 11 | isLoading: false, 12 | wishlistForm: {} 13 | }; 14 | 15 | const wishListReducer = (state = initialState, action) => { 16 | switch (action.type) { 17 | case FETCH_WISHLIST: 18 | return { 19 | ...state, 20 | wishlist: action.payload 21 | }; 22 | case SET_WISHLIST_LOADING: 23 | return { 24 | ...state, 25 | isLoading: action.payload 26 | }; 27 | default: 28 | return state; 29 | } 30 | }; 31 | 32 | export default wishListReducer; 33 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | MERN Store 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /client/webpack/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 3 | 4 | const CURRENT_WORKING_DIR = process.cwd(); 5 | 6 | module.exports = { 7 | entry: [path.join(CURRENT_WORKING_DIR, 'app/index.js')], 8 | resolve: { 9 | extensions: ['.js', '.json', '.css', '.scss', '.html'], 10 | alias: { 11 | app: 'app' 12 | } 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.(js|jsx)$/, 18 | loader: 'babel-loader', 19 | exclude: /(node_modules)/ 20 | } 21 | ] 22 | }, 23 | plugins: [ 24 | new CopyWebpackPlugin([ 25 | { 26 | from: 'public' 27 | } 28 | ]) 29 | ] 30 | }; 31 | -------------------------------------------------------------------------------- /server/models/address.js: -------------------------------------------------------------------------------- 1 | const Mongoose = require('mongoose'); 2 | const { Schema } = Mongoose; 3 | 4 | // Address Schema 5 | const AddressSchema = new Schema({ 6 | user: { 7 | type: Schema.Types.ObjectId, 8 | ref: 'User' 9 | }, 10 | address: { 11 | type: String 12 | }, 13 | city: { 14 | type: String 15 | }, 16 | state: { 17 | type: String 18 | }, 19 | country: { 20 | type: String 21 | }, 22 | zipCode: { 23 | type: String 24 | }, 25 | isDefault: { 26 | type: Boolean, 27 | default: false 28 | }, 29 | updated: Date, 30 | created: { 31 | type: Date, 32 | default: Date.now 33 | } 34 | }); 35 | 36 | module.exports = Mongoose.model('Address', AddressSchema); 37 | -------------------------------------------------------------------------------- /client/app/components/Common/LoadingIndicator/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * LoadingIndicator 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | const LoadingIndicator = props => { 10 | const { inline, backdrop } = props; 11 | 12 | return ( 13 |
18 |
23 |
24 | ); 25 | }; 26 | 27 | LoadingIndicator.defaultProps = { 28 | inline: false, 29 | backdrop: false 30 | }; 31 | 32 | export default LoadingIndicator; 33 | -------------------------------------------------------------------------------- /server/utils/db.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const chalk = require('chalk'); 3 | const mongoose = require('mongoose'); 4 | 5 | const keys = require('../config/keys'); 6 | const { database } = keys; 7 | 8 | const setupDB = async () => { 9 | try { 10 | // Connect to MongoDB 11 | mongoose.set('useCreateIndex', true); 12 | mongoose 13 | .connect(database.url, { 14 | useNewUrlParser: true, 15 | useUnifiedTopology: true, 16 | useFindAndModify: false 17 | }) 18 | .then(() => 19 | console.log(`${chalk.green('✓')} ${chalk.blue('MongoDB Connected!')}`) 20 | ) 21 | .catch(err => console.log(err)); 22 | } catch (error) { 23 | return null; 24 | } 25 | }; 26 | 27 | module.exports = setupDB; 28 | -------------------------------------------------------------------------------- /client/app/components/Store/AddToWishList/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * AddToWishList 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import Checkbox from '../../Common/Checkbox'; 10 | import { HeartIcon } from '../../Common/Icon'; 11 | 12 | const AddToWishList = props => { 13 | const { id, liked, enabled, updateWishlist } = props; 14 | 15 | return ( 16 |
17 | } 23 | onChange={(_, value) => { 24 | updateWishlist(value, id); 25 | }} 26 | /> 27 |
28 | ); 29 | }; 30 | 31 | export default AddToWishList; 32 | -------------------------------------------------------------------------------- /client/app/styles/core/_account.scss: -------------------------------------------------------------------------------- 1 | .account { 2 | .info { 3 | margin-bottom: 10px; 4 | @include flex(); 5 | // flex-wrap: wrap; 6 | justify-content: flex-start; 7 | flex-direction: row; 8 | align-items: center; 9 | @include media-breakpoint-down(xs) { 10 | flex-direction: column; 11 | align-items: normal; 12 | } 13 | 14 | .desc { 15 | flex: 1; 16 | @include media-breakpoint-down(xs) { 17 | @include flex(); 18 | justify-content: space-between; 19 | align-items: center; 20 | } 21 | 22 | .provider-email { 23 | text-transform: capitalize; 24 | } 25 | } 26 | 27 | p, 28 | span { 29 | margin: 0; 30 | display: inline; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/app/components/Common/SignupProvider/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * SignupProvider 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import { GoogleIcon, FacebookIcon } from '../Icon'; 10 | import { API_URL } from '../../../constants'; 11 | 12 | const SignupProvider = () => { 13 | return ( 14 |
15 | 16 | 17 | Login with Google 18 | 19 | 20 | 21 | 22 | Login with Facebook 23 | 24 |
25 | ); 26 | }; 27 | 28 | export default SignupProvider; 29 | -------------------------------------------------------------------------------- /client/app/utils/validation.js: -------------------------------------------------------------------------------- 1 | import Validator from 'validatorjs'; 2 | import DOMPurify from 'dompurify'; 3 | 4 | export const allFieldsValidation = (data, rules, options) => { 5 | const validation = new Validator(data, rules, options); 6 | const validationResponse = { isValid: validation.passes() }; 7 | if (!validationResponse.isValid) { 8 | validationResponse.errors = validation.errors.all(); 9 | } 10 | 11 | return validationResponse; 12 | }; 13 | 14 | export const santizeFields = data => { 15 | const fields = { ...data }; 16 | 17 | for (const field in fields) { 18 | if (typeof field === 'string') { 19 | fields[field] = DOMPurify.sanitize(fields[field], { 20 | USE_PROFILES: { html: false } 21 | }); 22 | } 23 | } 24 | return fields; 25 | }; 26 | -------------------------------------------------------------------------------- /server/constants/index.js: -------------------------------------------------------------------------------- 1 | exports.ROLES = { 2 | Admin: 'ROLE ADMIN', 3 | Member: 'ROLE MEMBER', 4 | Merchant: 'ROLE MERCHANT' 5 | }; 6 | 7 | exports.MERCHANT_STATUS = { 8 | Rejected: 'Rejected', 9 | Approved: 'Approved', 10 | Waiting_Approval: 'Waiting Approval' 11 | }; 12 | 13 | exports.CART_ITEM_STATUS = { 14 | Processing: 'Processing', 15 | Shipped: 'Shipped', 16 | Delivered: 'Delivered', 17 | Cancelled: 'Cancelled', 18 | Not_processed: 'Not processed' 19 | }; 20 | 21 | exports.REVIEW_STATUS = { 22 | Rejected: 'Rejected', 23 | Approved: 'Approved', 24 | Waiting_Approval: 'Waiting Approval' 25 | }; 26 | 27 | exports.EMAIL_PROVIDER = { 28 | Email: 'Email', 29 | Google: 'Google', 30 | Facebook: 'Facebook' 31 | }; 32 | 33 | exports.JWT_COOKIE = 'x-jwt-cookie'; 34 | -------------------------------------------------------------------------------- /client/app/containers/BrandsPage/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * BrandsPage 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import { connect } from 'react-redux'; 10 | 11 | import actions from '../../actions'; 12 | 13 | import BrandList from '../../components/Store/BrandList'; 14 | 15 | class BrandsPage extends React.PureComponent { 16 | componentDidMount() { 17 | this.props.fetchStoreBrands(); 18 | } 19 | 20 | render() { 21 | const { brands } = this.props; 22 | 23 | return ( 24 |
25 | 26 |
27 | ); 28 | } 29 | } 30 | 31 | const mapStateToProps = state => { 32 | return { 33 | brands: state.brand.storeBrands 34 | }; 35 | }; 36 | 37 | export default connect(mapStateToProps, actions)(BrandsPage); 38 | -------------------------------------------------------------------------------- /client/app/components/Manager/UserRole/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * UserRole 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import { ROLES } from '../../../constants'; 10 | import Badge from '../../Common/Badge'; 11 | 12 | const UserRole = props => { 13 | const { className, user } = props; 14 | 15 | return ( 16 | <> 17 | {user.role === ROLES.Admin ? ( 18 | 19 | Admin 20 | 21 | ) : user.role === ROLES.Merchant ? ( 22 | 23 | Merchant 24 | 25 | ) : ( 26 | Member 27 | )} 28 | 29 | ); 30 | }; 31 | 32 | UserRole.defaultProps = { 33 | className: '' 34 | }; 35 | 36 | export default UserRole; 37 | -------------------------------------------------------------------------------- /client/app/components/Manager/SubPage/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * SubPage 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import Button from '../../Common/Button'; 10 | 11 | const SubPage = props => { 12 | const { title, actionTitle, handleAction, children } = props; 13 | 14 | return ( 15 |
16 |
17 |

{title}

18 | {actionTitle && ( 19 |
20 |
27 | )} 28 |
29 |
{children}
30 |
31 | ); 32 | }; 33 | 34 | export default SubPage; 35 | -------------------------------------------------------------------------------- /client/app/containers/Address/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Address constants 4 | * 5 | */ 6 | 7 | export const FETCH_ADDRESS = 'src/Address/FETCH_ADDRESS'; 8 | export const FETCH_ADDRESSES = 'src/Address/FETCH_ADDRESSES'; 9 | export const ADDRESS_CHANGE = 'src/Address/ADDRESS_CHANGE'; 10 | export const ADDRESS_EDIT_CHANGE = 'src/Address/ADDRESS_EDIT_CHANGE'; 11 | export const SET_ADDRESS_FORM_ERRORS = 'src/Address/SET_ADDRESS_FORM_ERRORS'; 12 | export const SET_ADDRESS_FORM_EDIT_ERRORS = 13 | 'src/Address/SET_ADDRESS_FORM_EDIT_ERRORS'; 14 | export const RESET_ADDRESS = 'src/Address/RESET_ADDRESS'; 15 | export const ADD_ADDRESS = 'src/Address/ADD_ADDRESS'; 16 | export const REMOVE_ADDRESS = 'src/Address/REMOVE_ADDRESS'; 17 | export const SET_ADDRESS_LOADING = 'src/Address/SET_ADDRESS_LOADING'; 18 | export const ADDRESS_SELECT = 'src/Brand/BRAND_SELECT'; 19 | -------------------------------------------------------------------------------- /client/app/containers/Authentication/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Authentication 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import { connect } from 'react-redux'; 10 | import { Redirect } from 'react-router-dom'; 11 | 12 | import actions from '../../actions'; 13 | 14 | export default function (ComposedComponent) { 15 | class Authentication extends React.PureComponent { 16 | render() { 17 | const { authenticated } = this.props; 18 | 19 | if (!authenticated) { 20 | return ; 21 | } else { 22 | return ; 23 | } 24 | } 25 | } 26 | 27 | const mapStateToProps = state => { 28 | return { 29 | authenticated: state.authentication.authenticated 30 | }; 31 | }; 32 | 33 | return connect(mapStateToProps, actions)(Authentication); 34 | } 35 | -------------------------------------------------------------------------------- /server/routes/api/newsletter.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const mailchimp = require('../../services/mailchimp'); 5 | const mailgun = require('../../services/mailgun'); 6 | 7 | router.post('/subscribe', async (req, res) => { 8 | const email = req.body.email; 9 | 10 | if (!email) { 11 | return res.status(400).json({ error: 'You must enter an email address.' }); 12 | } 13 | 14 | const result = await mailchimp.subscribeToNewsletter(email); 15 | 16 | if (result.status === 400) { 17 | return res.status(400).json({ error: result.title }); 18 | } 19 | 20 | await mailgun.sendEmail(email, 'newsletter-subscription'); 21 | 22 | res.status(200).json({ 23 | success: true, 24 | message: 'You have successfully subscribed to the newsletter' 25 | }); 26 | }); 27 | 28 | module.exports = router; 29 | -------------------------------------------------------------------------------- /client/app/containers/Brand/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Brand constants 4 | * 5 | */ 6 | 7 | export const FETCH_BRANDS = 'src/Brand/FETCH_BRANDS'; 8 | export const FETCH_STORE_BRANDS = 'src/Brand/FETCH_STORE_BRANDS'; 9 | export const FETCH_BRAND = 'src/Brand/FETCH_BRAND'; 10 | export const BRAND_CHANGE = 'src/Brand/BRAND_CHANGE'; 11 | export const BRAND_EDIT_CHANGE = 'src/Brand/BRAND_EDIT_CHANGE'; 12 | export const SET_BRAND_FORM_ERRORS = 'src/Brand/SET_BRAND_FORM_ERRORS'; 13 | export const SET_BRAND_FORM_EDIT_ERRORS = 14 | 'src/Brand/SET_BRAND_FORM_EDIT_ERRORS'; 15 | export const RESET_BRAND = 'src/Brand/RESET_BRAND'; 16 | export const ADD_BRAND = 'src/Brand/ADD_BRAND'; 17 | export const REMOVE_BRAND = 'src/Brand/REMOVE_BRAND'; 18 | export const FETCH_BRANDS_SELECT = 'src/Brand/FETCH_BRANDS_SELECT'; 19 | export const SET_BRANDS_LOADING = 'src/Brand/SET_BRANDS_LOADING'; 20 | -------------------------------------------------------------------------------- /client/public/images/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /client/app/components/Manager/CategoryList/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * CategoryList 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import { Link } from 'react-router-dom'; 10 | 11 | const CategoryList = props => { 12 | const { categories } = props; 13 | 14 | return ( 15 |
16 | {categories.map((category, index) => ( 17 | 22 |
23 |

{category.name}

24 |
25 |

{category.description}

26 | 27 | ))} 28 |
29 | ); 30 | }; 31 | 32 | export default CategoryList; 33 | -------------------------------------------------------------------------------- /client/app/containers/Newsletter/reducer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Newsletter reducer 4 | * 5 | */ 6 | 7 | import { 8 | NEWSLETTER_CHANGE, 9 | SET_NEWSLETTER_FORM_ERRORS, 10 | NEWSLETTER_RESET 11 | } from './constants'; 12 | 13 | const initialState = { 14 | email: '', 15 | formErrors: {} 16 | }; 17 | 18 | const newsletterReducer = (state = initialState, action) => { 19 | switch (action.type) { 20 | case NEWSLETTER_CHANGE: 21 | return { 22 | ...state, 23 | email: action.payload 24 | }; 25 | case SET_NEWSLETTER_FORM_ERRORS: 26 | return { 27 | ...state, 28 | formErrors: action.payload 29 | }; 30 | case NEWSLETTER_RESET: 31 | return { 32 | ...state, 33 | email: '', 34 | formErrors: {} 35 | }; 36 | default: 37 | return state; 38 | } 39 | }; 40 | 41 | export default newsletterReducer; 42 | -------------------------------------------------------------------------------- /client/app/containers/Support/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Support 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | import { connect } from 'react-redux'; 9 | 10 | import actions from '../../actions'; 11 | 12 | import { default as SupportManager } from '../../components/Manager/Support'; 13 | 14 | class Support extends React.PureComponent { 15 | render() { 16 | const { user } = this.props; 17 | 18 | return ( 19 |
20 |

Support

21 |
22 |
23 | 24 |
25 |
26 | ); 27 | } 28 | } 29 | 30 | const mapStateToProps = state => { 31 | return { 32 | user: state.account.user, 33 | resetFormData: state.resetPassword.resetFormData, 34 | formErrors: state.resetPassword.formErrors 35 | }; 36 | }; 37 | 38 | export default connect(mapStateToProps, actions)(Support); 39 | -------------------------------------------------------------------------------- /client/app/components/Common/DropdownConfirm/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * DropdownConfirm 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import { 10 | UncontrolledButtonDropdown, 11 | DropdownMenu, 12 | DropdownToggle 13 | } from 'reactstrap'; 14 | 15 | const DropdownConfirm = props => { 16 | const { className, label, children } = props; 17 | 18 | return ( 19 |
20 | 21 | 22 |
23 | {label} 24 | 25 |
26 |
27 | {children} 28 |
29 |
30 | ); 31 | }; 32 | 33 | DropdownConfirm.defaultProps = { 34 | label: '' 35 | }; 36 | 37 | export default DropdownConfirm; 38 | -------------------------------------------------------------------------------- /client/app/components/Manager/DisabledAccount/Merchant.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * DisabledMerchantAccount 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | const DisabledMerchantAccount = props => { 10 | const { user } = props; 11 | 12 | return ( 13 |
17 |

Hi, {user.firstName}

18 |
19 |
Unfortunately it seems your account has been disabled.
20 |

21 | Please contact admin to request access again. 22 |

23 |
24 | 25 | Call us 951-999-9999 26 |
27 |
28 |
29 | ); 30 | }; 31 | 32 | export default DisabledMerchantAccount; 33 | -------------------------------------------------------------------------------- /client/app/contexts/Socket/provider.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | import io from 'socket.io-client'; 4 | 5 | import SocketContext from './context'; 6 | import { SOCKET_URL } from '../../constants'; 7 | 8 | const SocketProvider = ({ children }) => { 9 | const [socket, setSocket] = useState(null); 10 | 11 | const connect = () => { 12 | const token = localStorage.getItem('token'); 13 | const sk = io(SOCKET_URL, { 14 | autoConnect: false 15 | }); 16 | 17 | if (token) { 18 | sk.auth = { token }; 19 | sk.connect(); 20 | sk.auth; 21 | setSocket(sk); 22 | } 23 | }; 24 | 25 | const disconnect = () => { 26 | if (socket) { 27 | socket.close(); 28 | } 29 | }; 30 | 31 | return ( 32 | 33 | {children} 34 | 35 | ); 36 | }; 37 | 38 | export default SocketProvider; 39 | -------------------------------------------------------------------------------- /client/app/containers/Notification/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Notification 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import { connect } from 'react-redux'; 10 | import Notifications from 'react-notification-system-redux'; 11 | 12 | import actions from '../../actions'; 13 | 14 | class Notification extends React.PureComponent { 15 | componentDidMount() {} 16 | 17 | render() { 18 | const { notifications } = this.props; 19 | 20 | const style = { 21 | NotificationItem: { 22 | DefaultStyle: { 23 | margin: '10px 5px 2px 1px' 24 | }, 25 | 26 | success: { 27 | color: 'red' 28 | } 29 | } 30 | }; 31 | return ; 32 | } 33 | } 34 | 35 | const mapStateToProps = state => { 36 | return { 37 | notifications: state.notifications 38 | }; 39 | }; 40 | 41 | export default connect(mapStateToProps, actions)(Notification); 42 | -------------------------------------------------------------------------------- /client/app/containers/Category/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Category constants 4 | * 5 | */ 6 | 7 | export const FETCH_CATEGORIES = 'src/Category/FETCH_CATEGORIES'; 8 | export const FETCH_STORE_CATEGORIES = 'src/Category/FETCH_STORE_CATEGORIES'; 9 | export const FETCH_CATEGORY = 'src/Category/FETCH_CATEGORY'; 10 | export const CATEGORY_CHANGE = 'src/Category/CATEGORY_CHANGE'; 11 | export const CATEGORY_EDIT_CHANGE = 'src/Category/CATEGORY_EDIT_CHANGE'; 12 | export const SET_CATEGORY_FORM_ERRORS = 'src/Category/SET_CATEGORY_FORM_ERRORS'; 13 | export const SET_CATEGORY_FORM_EDIT_ERRORS = 14 | 'src/Category/SET_CATEGORY_FORM_EDIT_ERRORS'; 15 | export const RESET_CATEGORY = 'src/Category/RESET_CATEGORY'; 16 | export const CATEGORY_SELECT = 'src/Category/CATEGORY_SELECT'; 17 | export const ADD_CATEGORY = 'src/Category/ADD_CATEGORY'; 18 | export const REMOVE_CATEGORY = 'src/Category/REMOVE_CATEGORY'; 19 | export const SET_CATEGORIES_LOADING = 'src/Product/SET_CATEGORIES_LOADING'; 20 | -------------------------------------------------------------------------------- /client/app/containers/Merchant/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Merchant constants 4 | * 5 | */ 6 | 7 | export const FETCH_MERCHANTS = 'src/Merchant/FETCH_MERCHANTS'; 8 | export const REMOVE_MERCHANT = 'src/Merchant/REMOVE_MERCHANT'; 9 | export const SET_ADVANCED_FILTERS = 'src/Merchant/SET_ADVANCED_FILTERS'; 10 | export const FETCH_SEARCHED_MERCHANTS = 'src/Merchant/FETCH_SEARCHED_MERCHANTS'; 11 | export const MERCHANT_CHANGE = 'src/Merchant/MERCHANT_CHANGE'; 12 | export const SET_MERCHANT_FORM_ERRORS = 'src/Merchant/SET_MERCHANT_FORM_ERRORS'; 13 | export const SET_MERCHANTS_LOADING = 'src/Merchant/SET_MERCHANTS_LOADING'; 14 | export const SET_MERCHANTS_SUBMITTING = 'src/Merchant/SET_MERCHANTS_SUBMITTING'; 15 | export const RESET_MERCHANT = 'src/Merchant/RESET_MERCHANT'; 16 | export const SIGNUP_CHANGE = 'src/Merchant/SIGNUP_CHANGE'; 17 | export const SET_SIGNUP_FORM_ERRORS = 'src/Merchant/SET_SIGNUP_FORM_ERRORS'; 18 | export const SIGNUP_RESET = 'src/Merchant/SIGNUP_RESET'; 19 | -------------------------------------------------------------------------------- /client/app/components/Common/CartIcon/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * CartIcon 4 | * 5 | */ 6 | 7 | import React from 'react'; 8 | 9 | import { BagIcon } from '../Icon'; 10 | import Button from '../Button'; 11 | 12 | const CartIcon = props => { 13 | const { className, onClick, cartItems } = props; 14 | 15 | const Icon = ( 16 | 17 | 18 | {cartItems.length > 0 && ( 19 | 20 | {cartItems.length >= 99 ? '99+' : cartItems.length} 21 | 22 | )} 23 | 24 | ); 25 | 26 | const items = cartItems.length; 27 | 28 | return ( 29 |