├── jest.config.js ├── .yarnrc ├── src ├── contexts │ ├── index.ts │ └── auth │ │ └── index.tsx ├── services │ ├── index.ts │ ├── socketType.ts │ └── socketClient.ts ├── components │ ├── custom-hooks │ │ ├── index.ts │ │ └── useDebounce.tsx │ ├── design-system │ │ ├── index.ts │ │ ├── pagination │ │ │ ├── styles.ts │ │ │ └── index.tsx │ │ ├── map │ │ │ └── index.tsx │ │ └── modal │ │ │ └── index.tsx │ ├── index.ts │ └── layout │ │ ├── index.tsx │ │ ├── footer │ │ └── index.tsx │ │ └── header │ │ └── navbar.tsx ├── global-states │ ├── reducers │ │ ├── auth │ │ │ ├── index.ts │ │ │ └── authReducer.ts │ │ ├── index.ts │ │ ├── admin │ │ │ ├── index.ts │ │ │ └── userReducer.ts │ │ └── rootReducer.ts │ ├── actions │ │ ├── auth │ │ │ ├── index.ts │ │ │ └── authActionCreators.ts │ │ ├── index.ts │ │ └── admin │ │ │ ├── index.ts │ │ │ ├── usersActionCreators.ts │ │ │ └── productsActionCreators.ts │ ├── store │ │ ├── index.ts │ │ ├── configureStoreTem.js │ │ └── configureStore.ts │ └── index.ts ├── types │ ├── index.ts │ ├── admin │ │ ├── index.ts │ │ ├── products │ │ │ ├── index.ts │ │ │ └── _prototype.ts │ │ └── users │ │ │ ├── index.ts │ │ │ ├── _prototype.ts │ │ │ └── actionsType.tsx │ └── auth │ │ ├── index.ts │ │ ├── _prototype.ts │ │ └── actionsType.tsx ├── utils │ ├── index.ts │ ├── functions │ │ └── helpers.ts │ ├── schemaValidation │ │ ├── product.ts │ │ └── auth.ts │ └── api │ │ └── lib │ │ └── axiosConfig.ts ├── page-components │ ├── admin-page │ │ ├── index.ts │ │ ├── checkout-success-page │ │ │ └── index.tsx │ │ └── products │ │ │ └── products.tsx │ ├── index.tsx │ │ └── cart-page.tsx │ ├── verify-email-page │ │ └── index.tsx │ ├── forget-password-page │ │ └── index.tsx │ ├── login-page │ │ └── index.tsx │ ├── change-password-page │ │ └── index.tsx │ └── home-page │ │ └── index.tsx ├── pages │ ├── api │ │ └── hello.ts │ ├── login │ │ └── index.tsx │ ├── admin │ │ ├── users │ │ │ ├── users-ui.tsx │ │ │ ├── add-user.tsx │ │ │ ├── users-table.tsx │ │ │ └── [userId].tsx │ │ └── products │ │ │ ├── index.tsx │ │ │ ├── add-product.tsx │ │ │ └── [productId].tsx │ ├── signup │ │ └── index.tsx │ ├── checkout-success │ │ └── index.tsx │ ├── forget-password │ │ └── index.tsx │ ├── profile │ │ └── [userId].tsx │ ├── verify-email │ │ └── index.tsx │ ├── products │ │ └── [productId].tsx │ ├── reset-password │ │ └── index.tsx │ ├── index.tsx │ ├── 404.tsx │ ├── cart │ │ └── index.tsx │ ├── order │ │ └── index.tsx │ ├── checkout │ │ └── index.tsx │ ├── _document.tsx │ └── _app.tsx ├── constants │ └── index.ts └── styles │ └── globals.css ├── postcss.config.js ├── public ├── avatar │ ├── tem2.png │ ├── tem3.png │ ├── about1.jpg │ ├── avatar.avif │ ├── avatrsm.avif │ └── tem-img.png ├── icons │ ├── 344403.png │ ├── favicon.ico │ └── favicons.ico └── products │ └── img1.webp ├── __mocks__ └── fileMock.js ├── .prettierrc ├── next-env.d.ts ├── jest.setup.js ├── __tests__ └── index.test.jsx ├── .eslintignore ├── .gitignore ├── .vscode └── settings.json ├── .prettierignore ├── tailwind.config.js ├── jsconfig.json ├── tsconfig.json ├── next.config.js ├── LICENSE ├── package.json ├── .eslintrc.json └── README.md /jest.config.js: -------------------------------------------------------------------------------- 1 | // TODO 2 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | network-timeout 500000 -------------------------------------------------------------------------------- /src/contexts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth'; 2 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './socketClient'; 2 | -------------------------------------------------------------------------------- /src/components/custom-hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useDebounce'; 2 | -------------------------------------------------------------------------------- /src/global-states/reducers/auth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './authReducer'; 2 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './admin'; 2 | export * from './auth'; 3 | -------------------------------------------------------------------------------- /src/global-states/actions/auth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './authActionCreators'; 2 | -------------------------------------------------------------------------------- /src/types/admin/index.ts: -------------------------------------------------------------------------------- 1 | export * from './products'; 2 | export * from './users'; 3 | -------------------------------------------------------------------------------- /src/global-states/actions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './admin'; 2 | export * from './auth'; 3 | -------------------------------------------------------------------------------- /src/types/auth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './_prototype'; 2 | export * from './actionsType'; 3 | -------------------------------------------------------------------------------- /src/global-states/reducers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './admin'; 2 | export * from './rootReducer'; 3 | -------------------------------------------------------------------------------- /src/types/admin/products/index.ts: -------------------------------------------------------------------------------- 1 | export * from './_prototype'; 2 | export * from './actionsType'; 3 | -------------------------------------------------------------------------------- /src/types/admin/users/index.ts: -------------------------------------------------------------------------------- 1 | export * from './_prototype'; 2 | export * from './actionsType'; 3 | -------------------------------------------------------------------------------- /src/global-states/store/index.ts: -------------------------------------------------------------------------------- 1 | export * from './configureStore'; 2 | export * from './configureStoreTem'; 3 | -------------------------------------------------------------------------------- /src/global-states/index.ts: -------------------------------------------------------------------------------- 1 | export * from './actions'; 2 | export * from './reducers'; 3 | export * from './store'; 4 | -------------------------------------------------------------------------------- /src/global-states/reducers/admin/index.ts: -------------------------------------------------------------------------------- 1 | export * from './productReducer'; 2 | export * from './userReducer'; 3 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/avatar/tem2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saddamarbaa/Ecommerce-website-next.js-typeScript/HEAD/public/avatar/tem2.png -------------------------------------------------------------------------------- /public/avatar/tem3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saddamarbaa/Ecommerce-website-next.js-typeScript/HEAD/public/avatar/tem3.png -------------------------------------------------------------------------------- /src/components/design-system/index.ts: -------------------------------------------------------------------------------- 1 | export * from './map'; 2 | export * from './modal'; 3 | export * from './pagination'; 4 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './custom-hooks'; 2 | export * from './design-system'; 3 | export * from './layout'; 4 | -------------------------------------------------------------------------------- /public/avatar/about1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saddamarbaa/Ecommerce-website-next.js-typeScript/HEAD/public/avatar/about1.jpg -------------------------------------------------------------------------------- /public/icons/344403.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saddamarbaa/Ecommerce-website-next.js-typeScript/HEAD/public/icons/344403.png -------------------------------------------------------------------------------- /public/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saddamarbaa/Ecommerce-website-next.js-typeScript/HEAD/public/icons/favicon.ico -------------------------------------------------------------------------------- /src/global-states/actions/admin/index.ts: -------------------------------------------------------------------------------- 1 | export * from './productsActionCreators'; 2 | export * from './usersActionCreators'; 3 | -------------------------------------------------------------------------------- /public/avatar/avatar.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saddamarbaa/Ecommerce-website-next.js-typeScript/HEAD/public/avatar/avatar.avif -------------------------------------------------------------------------------- /public/avatar/avatrsm.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saddamarbaa/Ecommerce-website-next.js-typeScript/HEAD/public/avatar/avatrsm.avif -------------------------------------------------------------------------------- /public/avatar/tem-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saddamarbaa/Ecommerce-website-next.js-typeScript/HEAD/public/avatar/tem-img.png -------------------------------------------------------------------------------- /public/icons/favicons.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saddamarbaa/Ecommerce-website-next.js-typeScript/HEAD/public/icons/favicons.ico -------------------------------------------------------------------------------- /public/products/img1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saddamarbaa/Ecommerce-website-next.js-typeScript/HEAD/public/products/img1.webp -------------------------------------------------------------------------------- /__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | // TODO 2 | 3 | // Read more at "Handling stylesheets and image imports" on https://nextjs.org/docs/testing 4 | 5 | module.exports = 'test-file-stub'; 6 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api/lib/axiosConfig'; 2 | export * from './functions/helpers'; 3 | export * from './schemaValidation/auth'; 4 | export * from './schemaValidation/product'; 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "auto", 3 | "printWidth": 100, 4 | "tabWidth": 2, 5 | "trailingComma": "es5", 6 | "singleQuote": true, 7 | "tailwindConfig": "./tailwind.config.js" 8 | } 9 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /src/page-components/admin-page/index.ts: -------------------------------------------------------------------------------- 1 | export * from './products/add-product'; 2 | export * from './products/products'; 3 | export * from './users/add-user'; 4 | export * from './users/edit-user'; 5 | export * from './users/users-table'; 6 | export * from './users/users-ui'; 7 | -------------------------------------------------------------------------------- /src/page-components/index.tsx/cart-page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function CartPageComponent() { 4 | return ( 5 |
6 |

Cart Page

7 |
8 | ); 9 | } 10 | 11 | export default CartPageComponent; 12 | -------------------------------------------------------------------------------- /src/pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiResponse } from 'next'; 3 | 4 | type Data = { 5 | name: string; 6 | }; 7 | 8 | export default function handler(res: NextApiResponse) { 9 | res.status(200).json({ name: 'John Doe' }); 10 | } 11 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | // Optional: configure or set up a testing framework before each test. 2 | // If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js` 3 | 4 | // Used for __tests__/testing-library.js 5 | // Learn more: https://github.com/testing-library/jest-dom 6 | 7 | // eslint-disable-next-line import/no-extraneous-dependencies 8 | import '@testing-library/jest-dom/extend-expect'; 9 | -------------------------------------------------------------------------------- /__tests__/index.test.jsx: -------------------------------------------------------------------------------- 1 | // TODO 2 | // import { render, screen } from '@testing-library/react'; 3 | // import Home from '@/pages/index'; 4 | 5 | // describe('Home', () => { 6 | // it('renders a heading', () => { 7 | // render(); 8 | 9 | // const heading = screen.getByRole('heading', { 10 | // name: /welcome to next\.js!/i, 11 | // }); 12 | 13 | // expect(heading).toBeInTheDocument(); 14 | // }); 15 | // }); 16 | -------------------------------------------------------------------------------- /src/pages/login/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | 4 | import LogInPageComponent from '@/page-components/login-page'; 5 | 6 | function LogInScreen() { 7 | return ( 8 | <> 9 | 10 | Login to the website 11 | 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | export default LogInScreen; 19 | -------------------------------------------------------------------------------- /src/pages/admin/users/users-ui.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | 4 | import AdminUsersPageComponent from '@/page-components/admin-page/users/users-ui'; 5 | 6 | function AdminUserPage() { 7 | return ( 8 | <> 9 | 10 | Users 11 | 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | export default AdminUserPage; 19 | -------------------------------------------------------------------------------- /src/pages/signup/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | 4 | import SignUpComponent from '@/page-components/signup-page'; 5 | 6 | function SignUpScreen() { 7 | return ( 8 | <> 9 | 10 | Signup to the website 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | export default SignUpScreen; 20 | -------------------------------------------------------------------------------- /src/pages/admin/products/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | 4 | import AdminProducts from '@/page-components/admin-page/products/products'; 5 | 6 | function AdminProductsPage() { 7 | return ( 8 | <> 9 | 10 | Products 11 | 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | export default AdminProductsPage; 19 | -------------------------------------------------------------------------------- /src/pages/admin/users/add-user.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | 4 | import AddUserPageComponent from '@/page-components/admin-page/users/add-user'; 5 | 6 | function AddUserPage() { 7 | return ( 8 | <> 9 | 10 | Add User 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | export default AddUserPage; 20 | -------------------------------------------------------------------------------- /src/services/socketType.ts: -------------------------------------------------------------------------------- 1 | // Define the interface for the SocketClient 2 | export interface SocketClient { 3 | emit(event: string, message: string): void; 4 | addHandler(event: string, handler?: any): void; 5 | removeHandler(event: string, handler?: any): void; 6 | connect(): void; 7 | disconnect(callback?: () => unknown): void; 8 | on( 9 | event: 'connect' | 'disconnect' | 'reconnect' | 'error' | 'connect_error', 10 | handler: (arg: any) => void 11 | ): void; 12 | } 13 | -------------------------------------------------------------------------------- /src/pages/admin/products/add-product.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | 4 | import AddProductPageComponent from '@/page-components/admin-page/products/add-product'; 5 | 6 | function AddProductPage() { 7 | return ( 8 | <> 9 | 10 | Add Product 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | export default AddProductPage; 20 | -------------------------------------------------------------------------------- /src/pages/admin/users/users-table.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | 4 | import AdminUsersTablePageComponent from '@/page-components/admin-page/users/users-table'; 5 | 6 | function AdminUserTablePage() { 7 | return ( 8 | <> 9 | 10 | Users 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | export default AdminUserTablePage; 20 | -------------------------------------------------------------------------------- /src/pages/checkout-success/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | 4 | import CheckoutSuccessPageComponent from '@/page-components/admin-page/checkout-success-page'; 5 | 6 | function LogInScreen() { 7 | return ( 8 | <> 9 | 10 | checkout success 11 | 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | export default LogInScreen; 19 | -------------------------------------------------------------------------------- /src/pages/forget-password/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | 4 | import ForgetPasswordPageComponent from '@/page-components/forget-password-page'; 5 | 6 | function ForgetPasswordScreen() { 7 | return ( 8 | <> 9 | 10 | Forgot your password? 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | export default ForgetPasswordScreen; 20 | -------------------------------------------------------------------------------- /src/pages/profile/[userId].tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | import { useRouter } from 'next/router'; 4 | 5 | import ProfilePageComponent from '@/page-components/profile-page'; 6 | 7 | function ProfilePage() { 8 | const router = useRouter(); 9 | const { userId } = router.query; 10 | return ( 11 | <> 12 | 13 | Users 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | export default ProfilePage; 22 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "colorize.languages": [ 3 | "css", 4 | "sass", 5 | "scss", 6 | "less", 7 | "postcss", 8 | "sss", 9 | "stylus", 10 | "xml", 11 | "svg", 12 | "json", 13 | "ts", 14 | "js", 15 | "tsx", 16 | "jsx" 17 | ], 18 | "editor.formatOnPaste": true, 19 | "editor.formatOnSave": true, 20 | "editor.defaultFormatter": "esbenp.prettier-vscode", 21 | "editor.codeActionsOnSave": { 22 | "source.fixAll.eslint": true, 23 | "source.fixAll.format": true 24 | }, 25 | "cSpell.words": ["ENDPOIN", "semibold"] 26 | } 27 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | -------------------------------------------------------------------------------- /src/global-states/store/configureStoreTem.js: -------------------------------------------------------------------------------- 1 | import reducers from 'global-states/reducers/rootReducer'; 2 | import { applyMiddleware, createStore } from 'redux'; 3 | import { composeWithDevTools } from 'redux-devtools-extension'; 4 | import thunk from 'redux-thunk'; 5 | /** 6 | * Prepare the Redux Store 7 | */ 8 | 9 | const composedMiddlewares = applyMiddleware(thunk); 10 | 11 | const storeEnhancers = composeWithDevTools({ 12 | name: 'React-node-test', 13 | })(composedMiddlewares); 14 | 15 | export const makeStore = () => createStore(reducers, storeEnhancers); 16 | export default makeStore; 17 | -------------------------------------------------------------------------------- /src/pages/admin/users/[userId].tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | import { useRouter } from 'next/router'; 4 | 5 | import EditUsersPageComponent from '@/page-components/admin-page/users/edit-user'; 6 | 7 | function EditUserPage() { 8 | const router = useRouter(); 9 | const { userId } = router.query; 10 | return ( 11 | <> 12 | 13 | Users 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | export default EditUserPage; 22 | -------------------------------------------------------------------------------- /src/pages/verify-email/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import { useRouter } from 'next/router'; 3 | 4 | import VerifyEmailPageComponent from '@/page-components/verify-email-page'; 5 | 6 | function VerifyEmailScreen() { 7 | const router = useRouter(); 8 | const { token, id } = router.query; 9 | 10 | return ( 11 | <> 12 | 13 | Verify Email 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | export default VerifyEmailScreen; 22 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'jit', 3 | content: ['./src/**/*.{js,ts,jsx,tsx}'], 4 | important: true, 5 | darkMode: 'class', // or 'media' or 'class' 6 | theme: { 7 | screens: { 8 | xs: '320px', 9 | sm: '640px', 10 | md: '768px', 11 | lg: '1024px', 12 | xl: '1280px', 13 | xxl: '1536px', 14 | }, 15 | extend: { 16 | boxShadow: { 17 | xs: '0 0 0 1px rgba(0, 0, 0, 0.05)', 18 | outline: '0 0 0 3px rgba(66, 153, 225, 0.5)', 19 | }, 20 | }, 21 | }, 22 | variants: { 23 | extend: {}, 24 | }, 25 | plugins: [], 26 | }; 27 | -------------------------------------------------------------------------------- /src/pages/products/[productId].tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | import { useRouter } from 'next/router'; 4 | 5 | import ProductDetailPageComponent from '@/page-components/product-detail-page'; 6 | 7 | function ProductDetailPage() { 8 | const router = useRouter(); 9 | const { productId } = router.query; 10 | return ( 11 | <> 12 | 13 | Product detail 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | export default ProductDetailPage; 23 | -------------------------------------------------------------------------------- /src/pages/reset-password/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import { useRouter } from 'next/router'; 3 | 4 | import ChangePasswordPageComponent from '@/page-components/change-password-page'; 5 | 6 | function ChangePasswordScreen() { 7 | const router = useRouter(); 8 | const { token, id } = router.query; 9 | 10 | return ( 11 | <> 12 | 13 | Change Your Password 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | export default ChangePasswordScreen; 23 | -------------------------------------------------------------------------------- /src/pages/admin/products/[productId].tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | import { useRouter } from 'next/router'; 4 | 5 | import EditProductPageComponent from '@/page-components/admin-page/products/edit-product'; 6 | 7 | function EditProductPage() { 8 | const router = useRouter(); 9 | const { productId } = router.query; 10 | return ( 11 | <> 12 | 13 | Update Product 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | export default EditProductPage; 22 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Head from 'next/head'; 4 | 5 | import { ReducerType } from '@/global-states'; 6 | // eslint-disable-next-line import/no-named-as-default 7 | import HomePageComponent from '@/page-components/home-page/index'; 8 | 9 | function HomePage() { 10 | return ( 11 | <> 12 | 13 | Products 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | const mapStateToProps = (state: ReducerType) => ({ 22 | store: state, 23 | }); 24 | 25 | const mapDispatchToProps = {}; 26 | 27 | export default connect(mapStateToProps, mapDispatchToProps)(HomePage); 28 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "allowSyntheticDefaultImports": true, 6 | "jsx": "react", 7 | "noImplicitAny": false, 8 | "baseUrl": "./src", 9 | "paths": { 10 | "@/*": ["./*"], 11 | "pages/*": ["./pages/*"], 12 | "components/*": ["./components/*"], 13 | "page-components/*": ["./page-components/*"], 14 | "utils/*": ["./utils/*"], 15 | "layouts/*": ["./components/layouts/*"], 16 | "styles/*": ["./styles/*"], 17 | "designsystem/*": ["./designsystem/*"], 18 | "types/*": ["./types/*"], 19 | "tests/*": ["./tests/*"], 20 | "services/*": ["./services/*"], 21 | "contexts/*": ["./contexts/*"], 22 | "constants/*": ["./constants/*"], 23 | "global-states/*": ["./global-states/*"] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/custom-hooks/useDebounce.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export function useDebounce(value: string, delay: number) { 4 | // State and setters for debounced value 5 | const [debouncedValue, setDebouncedValue] = useState(value); 6 | 7 | useEffect( 8 | () => { 9 | // Update debounced value after delay 10 | const handler = setTimeout(() => { 11 | setDebouncedValue(value); 12 | }, delay); 13 | 14 | // Cancel the timeout if value changes (also on delay change or unmount) 15 | // This is how we prevent debounced value from updating if value is changed ... 16 | // .. within the delay period. Timeout gets cleared and restarted. 17 | return () => { 18 | clearTimeout(handler); 19 | }; 20 | }, 21 | [value, delay] // Only re-call effect if value or delay changes 22 | ); 23 | 24 | return debouncedValue; 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "target": "esnext", 5 | "module": "esnext", 6 | "jsx": "preserve", 7 | "baseUrl": "./src", 8 | "paths": { 9 | "@/*": ["./*"] 10 | }, 11 | "moduleResolution": "node", 12 | "strict": true, 13 | "allowJs": true, 14 | "allowSyntheticDefaultImports": true, 15 | "esModuleInterop": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "isolatedModules": true, 18 | "noEmit": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "preserveConstEnums": true, 22 | "removeComments": false, 23 | "resolveJsonModule": true, 24 | "skipLibCheck": true, 25 | "sourceMap": true, 26 | "incremental": true 27 | }, 28 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "src/pages/checkout-success/index.tsx"], 29 | "exclude": ["node_modules"] 30 | } 31 | -------------------------------------------------------------------------------- /src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link'; 3 | 4 | function ErrorPage() { 5 | return ( 6 |
7 |
8 |

404

9 |
10 | We re sorry. The page you requested could not be found. Please go back to the homepage or 11 | contact us 12 |
13 |
14 | 15 | 16 | Go back 17 | 18 | 19 |
20 |
21 |
22 | ); 23 | } 24 | 25 | export default ErrorPage; 26 | -------------------------------------------------------------------------------- /src/components/design-system/pagination/styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@mui/styles'; 2 | 3 | export default makeStyles(() => ({ 4 | root: { 5 | '& .MuiPagination-ul': { 6 | '& > li': { 7 | marginRight: '1.1rem', 8 | '& .Mui-selected': { 9 | background: '#2c7be5 !important', 10 | color: '#fff !important', 11 | borderColor: '#dee2e6 !important', 12 | }, 13 | }, 14 | '& button': { 15 | fontWeight: 'bold', 16 | paddingLeft: ' 1.2rem !important', 17 | paddingRight: ' 1.2rem !important', 18 | display: 'block', 19 | border: '1px solid #19D5C6', 20 | // marginBottom: '1rem', 21 | 22 | '&:hover': { 23 | background: '#2c7be5', 24 | color: '#fff !important', 25 | borderColor: '#dee2e6 !important', 26 | }, 27 | '& .Mui-selected': { 28 | background: '#2c7be5', 29 | }, 30 | }, 31 | }, 32 | }, 33 | })); 34 | -------------------------------------------------------------------------------- /src/global-states/reducers/rootReducer.ts: -------------------------------------------------------------------------------- 1 | import { HYDRATE } from 'next-redux-wrapper'; 2 | import { combineReducers } from 'redux'; 3 | 4 | import { productReducer, userReducer } from './admin'; 5 | import { authReducer } from './auth'; 6 | 7 | export const rootReducer = combineReducers({ 8 | auth: authReducer, 9 | users: userReducer, 10 | products: productReducer, 11 | }); 12 | 13 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 14 | // @ts-ignore 15 | export const masterReducer = (state, action) => { 16 | if (action.type && action.type === HYDRATE) { 17 | // console.log('action.type', action.payload.products); 18 | const nextState = { 19 | ...state, // use previous state 20 | ...action.payload, // apply delta from hydration 21 | }; 22 | return nextState; 23 | } 24 | 25 | return rootReducer(state, action); 26 | }; 27 | 28 | // RootState[type] 29 | export type ReducerType = ReturnType; 30 | 31 | export default masterReducer; 32 | -------------------------------------------------------------------------------- /src/components/design-system/pagination/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/require-default-props */ 2 | import * as React from 'react'; 3 | import Pagination from '@mui/material/Pagination'; 4 | import PaginationItem from '@mui/material/PaginationItem'; 5 | import Stack from '@mui/material/Stack'; 6 | 7 | import useStyles from './styles'; 8 | 9 | type PaginationPropsType = { 10 | page?: number; 11 | handleChange: (event: any, value: number) => void; 12 | totalPages?: number; 13 | }; 14 | export function PaginationComponent({ page, handleChange, totalPages }: PaginationPropsType) { 15 | const classes = useStyles(); 16 | 17 | return ( 18 | 19 | } 26 | /> 27 | 28 | ); 29 | } 30 | 31 | export default PaginationComponent; 32 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = () => ({ 2 | typescript: { 3 | // !! WARN !! 4 | // Dangerously allow production builds to successfully complete even if 5 | // your project has type errors. 6 | // !! WARN !! 7 | ignoreBuildErrors: true, 8 | }, 9 | reactStrictMode: true, 10 | serverRuntimeConfig: { 11 | AUTH_COOKIE_NAME: process.env.AUTH_COOKIE_NAME, 12 | CONSOLE_BACKEND_ENDPOINT: process.env.CONSOLE_BACKEND_ENDPOINT, 13 | CONSOLE_BACKEND_IMG_ENDPOIN: process.env.CONSOLE_BACKEND_IMG_ENDPOIN, 14 | NEXT_PUBLIC_MAP_BOX_ACCESS_TOKEN: process.env.NEXT_PUBLIC_MAP_BOX_ACCESS_TOKEN, 15 | }, 16 | publicRuntimeConfig: { 17 | APP_ID: process.env.APP_ID, 18 | CONSOLE_BACKEND_ENDPOINT: process.env.CONSOLE_BACKEND_ENDPOINT, 19 | CONSOLE_BACKEND_IMG_ENDPOIN: process.env.CONSOLE_BACKEND_IMG_ENDPOIN, 20 | }, 21 | images: { 22 | domains: [ 23 | 'saddam-rest-api.herokuapp.com', 24 | 'localhost', 25 | 'lh3.googleusercontent.com', 26 | 'flagcdn.com', 27 | 'upload.wikimedia.org', 28 | 'res.cloudinary.com', 29 | ], 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 saddamarbaa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/contexts/auth/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-function */ 2 | import React, { createContext, ReactNode, useContext, useState } from 'react'; 3 | 4 | type authContextType = { 5 | user: boolean; 6 | login: () => void; 7 | logout: () => void; 8 | }; 9 | 10 | const authContextDefaultValues: authContextType = { 11 | user: false, 12 | login: () => {}, 13 | logout: () => {}, 14 | }; 15 | 16 | const AuthContext = createContext(authContextDefaultValues); 17 | 18 | export function useAuth() { 19 | return useContext(AuthContext); 20 | } 21 | 22 | type Props = { 23 | children: ReactNode; 24 | }; 25 | 26 | export default function AuthProvider({ children }: Props) { 27 | const [user, setUser] = useState(false); 28 | 29 | const login = () => { 30 | setUser(true); 31 | }; 32 | 33 | const logout = () => { 34 | setUser(false); 35 | }; 36 | 37 | // eslint-disable-next-line react/jsx-no-constructed-context-values 38 | const value = { 39 | user, 40 | login, 41 | logout, 42 | }; 43 | 44 | return {children}; 45 | } 46 | -------------------------------------------------------------------------------- /src/components/design-system/map/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactMapGL, { Marker } from 'react-map-gl'; 3 | 4 | import 'mapbox-gl/dist/mapbox-gl.css'; 5 | 6 | type MapProps = { 7 | latitude: number; 8 | longitude: number; 9 | zoom: number; 10 | }; 11 | 12 | type PropsType = { 13 | latitude: number; 14 | longitude: number; 15 | }; 16 | 17 | export function Map({ latitude, longitude }: PropsType) { 18 | const [viewport, setViewport] = React.useState({ 19 | latitude, 20 | longitude, 21 | zoom: 11, 22 | }); 23 | 24 | React.useEffect(() => { 25 | setViewport((prevState) => ({ 26 | ...prevState, 27 | latitude, 28 | longitude, 29 | })); 30 | }, [latitude, longitude]); 31 | 32 | return ( 33 |
34 | 40 | 41 | 42 |
43 | ); 44 | } 45 | 46 | export default Map; 47 | -------------------------------------------------------------------------------- /src/pages/cart/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { ReducerType } from 'global-states'; 4 | import Head from 'next/head'; 5 | 6 | import CartPageComponent from '@/page-components/cart-page'; 7 | import Login from '@/pages/login'; 8 | import { _authPrototypeReducerState as ReducerState } from '@/types'; 9 | 10 | interface MapStateProps { 11 | auth: ReducerState; 12 | } 13 | 14 | type PropsType = MapStateProps; 15 | 16 | function OrderPage({ auth }: PropsType) { 17 | const { isAuthenticated } = auth; 18 | 19 | useEffect(() => { 20 | const redirectToLogin = () => { 21 | if (!isAuthenticated) { 22 | return ; 23 | } 24 | }; 25 | 26 | redirectToLogin(); 27 | }, [isAuthenticated]); 28 | 29 | if (!isAuthenticated) { 30 | return ; 31 | } 32 | 33 | return ( 34 | <> 35 | 36 | Your Cart 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | 44 | const mapStateToProps = (state: ReducerType) => ({ 45 | auth: state.auth, 46 | }); 47 | 48 | const mapDispatchToProps = {}; 49 | 50 | export default connect(mapStateToProps, mapDispatchToProps)(OrderPage); 51 | -------------------------------------------------------------------------------- /src/pages/order/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Head from 'next/head'; 4 | 5 | import { ReducerType } from '@/global-states'; 6 | import OrderPageComponent from '@/page-components/order-page'; 7 | import Login from '@/pages/login'; 8 | import { _authPrototypeReducerState as ReducerState } from '@/types'; 9 | 10 | interface MapStateProps { 11 | auth: ReducerState; 12 | } 13 | 14 | type PropsType = MapStateProps; 15 | function OrderPage({ auth }: PropsType) { 16 | const { isAuthenticated } = auth; 17 | 18 | useEffect(() => { 19 | const redirectToLogin = () => { 20 | if (!isAuthenticated) { 21 | return ; 22 | } 23 | }; 24 | 25 | redirectToLogin(); 26 | }, [isAuthenticated]); 27 | 28 | if (!isAuthenticated) { 29 | return ; 30 | } 31 | 32 | return ( 33 | <> 34 | 35 | Your orders 36 | 37 | 38 | 39 | 40 | ); 41 | } 42 | 43 | const mapStateToProps = (state: ReducerType) => ({ 44 | auth: state.auth, 45 | }); 46 | 47 | const mapDispatchToProps = {}; 48 | 49 | export default connect(mapStateToProps, mapDispatchToProps)(OrderPage); 50 | -------------------------------------------------------------------------------- /src/pages/checkout/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Head from 'next/head'; 4 | 5 | import { ReducerType } from '@/global-states'; 6 | import CheckoutPageComponent from '@/page-components/checkout-page'; 7 | import Login from '@/pages/login'; 8 | import { _authPrototypeReducerState as ReducerState } from '@/types'; 9 | 10 | interface MapStateProps { 11 | auth: ReducerState; 12 | } 13 | 14 | type PropsType = MapStateProps; 15 | function CheckoutPage({ auth }: PropsType) { 16 | const { isAuthenticated } = auth; 17 | 18 | useEffect(() => { 19 | const redirectToLogin = () => { 20 | if (!isAuthenticated) { 21 | return ; 22 | } 23 | }; 24 | 25 | redirectToLogin(); 26 | }, [isAuthenticated]); 27 | 28 | if (!isAuthenticated) { 29 | return ; 30 | } 31 | 32 | return ( 33 | <> 34 | 35 | checkout 36 | 37 | 38 | 39 | 40 | ); 41 | } 42 | 43 | const mapStateToProps = (state: ReducerType) => ({ 44 | auth: state.auth, 45 | }); 46 | 47 | const mapDispatchToProps = {}; 48 | 49 | export default connect(mapStateToProps, mapDispatchToProps)(CheckoutPage); 50 | -------------------------------------------------------------------------------- /src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | // this file allow us to add general structure of the page 2 | 3 | import Document, { Head, Html, Main, NextScript } from 'next/document'; 4 | 5 | class MyDocument extends Document { 6 | render() { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 24 | 25 | 26 | 27 |
28 | 29 | {/* // for add Portal */} 30 |
31 | } 51 | > 52 | 53 | 54 | 55 | 56 | {/* {isMounted && ( 57 | 58 | 59 | 60 | 61 | 62 | )} */} 63 | 64 | 65 | 66 | ); 67 | }); 68 | -------------------------------------------------------------------------------- /src/page-components/admin-page/checkout-success-page/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Alert } from '@mui/material'; 4 | import { useRouter } from 'next/router'; 5 | 6 | import { addOrders, ReducerType, restAddOrders } from '@/global-states'; 7 | import { _productPrototypeReducerState as ReducerProductState } from '@/types'; 8 | 9 | interface MapDispatchProps { 10 | restAddOrders: () => void; 11 | addOrders: (data: any) => void; 12 | } 13 | 14 | interface MapStateProps { 15 | listState: ReducerProductState; 16 | } 17 | 18 | type PropsType = MapDispatchProps & MapStateProps; 19 | 20 | export function CheckoutSuccessPageComponent({ 21 | listState, 22 | // authState, 23 | restAddOrders, 24 | addOrders, 25 | }: PropsType) { 26 | const { 27 | // cart, 28 | addOrderIsSuccess, 29 | addOrderIsError, 30 | addOrderMessage, 31 | } = listState; 32 | 33 | const router = useRouter(); 34 | 35 | useEffect(() => { 36 | const data = { 37 | paymentInfo: 'stripe', 38 | orderStatus: 'pending', 39 | textAmount: 10, 40 | shippingAmount: 10, 41 | shippingInfo: { 42 | address: '2636 test address', 43 | phoneNo: '+087666554488843322', 44 | zipCode: 'codej', 45 | status: 'done', 46 | country: 'test country ', 47 | street: 'test street', 48 | city: 'test city', 49 | }, 50 | // orderItems: cart.map(({ product, quantity }) => ({ 51 | // product: product?._id, 52 | // quantity, 53 | // })), 54 | }; 55 | addOrders(data); 56 | }, []); 57 | 58 | useEffect(() => { 59 | if (addOrderIsSuccess) { 60 | restAddOrders(); 61 | router.push('/order'); 62 | } 63 | }, [addOrderIsSuccess]); 64 | 65 | return ( 66 |
67 | 68 | {addOrderMessage} 69 | 70 |
71 | Payment checkout success 72 |
73 |
74 | ); 75 | } 76 | 77 | const mapStateToProps = (state: ReducerType) => ({ 78 | authState: state.auth, 79 | listState: state.products, 80 | }); 81 | 82 | const mapDispatchToProps = { 83 | restAddOrders, 84 | addOrders, 85 | }; 86 | 87 | export default connect(mapStateToProps, mapDispatchToProps)(CheckoutSuccessPageComponent); 88 | -------------------------------------------------------------------------------- /src/utils/schemaValidation/product.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from 'yup'; 2 | 3 | export const addProductSchemaValidation = Yup.object().shape({ 4 | name: Yup.string() 5 | .required('Name is required') 6 | .min(3, 'Name must be at least 3 characters') 7 | .max(100, 'Name must not exceed 100 characters'), 8 | brand: Yup.string() 9 | .required('Brand is required') 10 | .min(3, 'Brand must be at least 3 characters') 11 | .max(100, 'Brand must not exceed 100 characters'), 12 | description: Yup.string() 13 | .required('Description is required') 14 | .min(15, 'Description must be at least 15 characters') 15 | .max(500, 'Description must not exceed 500 characters'), 16 | price: Yup.string() 17 | .required('Price is required') 18 | .matches(/^[0-9-.]+$/, 'Please enter valid number '), 19 | // productImage: Yup.mixed() 20 | // .test('required', 'Image is required', (value: any) => value && value.length) 21 | // .test('fileSize', 'File Size is too large', (value) => { 22 | // const sizeInBytes = 1024 * 1024 * 10; // accept files up 10 mgb 23 | // return value && value[0] && value[0].size <= sizeInBytes; 24 | // }) 25 | // .test('type', 'We only support jpeg, jpg, png, webp', (value) => { 26 | // const SUPPORTED_FORMATS = ['image/jpg', 'image/jpeg', 'image/png', 'image/webp']; 27 | // return value && value[0] && SUPPORTED_FORMATS.includes(value[0].type.toLowerCase()); 28 | // }), 29 | category: Yup.string().required('Category is required please select one'), 30 | stock: Yup.string() 31 | .required('Stock Info is required') 32 | .min(3, 'Stock Info must be at least 3 characters') 33 | .max(100, 'Stock Info must not exceed 100 characters'), 34 | }); 35 | 36 | export const updateProductSchemaValidation = Yup.object().shape({ 37 | name: Yup.string() 38 | .required('Name is required') 39 | .min(3, 'Name must be at least 3 characters') 40 | .max(100, 'Name must not exceed 100 characters'), 41 | brand: Yup.string() 42 | .required('Brand is required') 43 | .min(3, 'Brand must be at least 3 characters') 44 | .max(100, 'Brand must not exceed 100 characters'), 45 | description: Yup.string() 46 | .required('Description is required') 47 | .min(15, 'Description must be at least 15 characters') 48 | .max(500, 'Description must not exceed 500 characters'), 49 | price: Yup.string() 50 | .required('Price is required') 51 | .matches(/^[0-9-.]+$/, 'Please enter valid number '), 52 | category: Yup.string().required('Category is required please select one'), 53 | stock: Yup.string() 54 | .min(3, 'Stock Info must be at least 3 characters') 55 | .max(100, 'Stock Info must not exceed 100 characters'), 56 | }); 57 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | import { ProductType } from '@/types'; 2 | 3 | export const defaultValues: ProductType = { 4 | name: '', 5 | price: '', 6 | brand: '', 7 | description: '', 8 | productImage: '', 9 | category: 'All Products', 10 | stock: 'in stock - order soon', 11 | }; 12 | 13 | export const months = [ 14 | { value: '01', label: 'January' }, 15 | { value: '02', label: 'February' }, 16 | { value: '03', label: 'March' }, 17 | { value: '04', label: 'April' }, 18 | { value: '05', label: 'May' }, 19 | { value: '06', label: 'June' }, 20 | { value: '07', label: 'July' }, 21 | { value: '08', label: 'August' }, 22 | { value: '09', label: 'September' }, 23 | { value: '10', label: 'October' }, 24 | { value: '11', label: 'November' }, 25 | { value: '12', label: 'December' }, 26 | ]; 27 | 28 | export const days = [ 29 | { value: '01', label: '1' }, 30 | { value: '02', label: '2' }, 31 | { value: '03', label: '3' }, 32 | { value: '04', label: '4' }, 33 | { value: '05', label: '5' }, 34 | { value: '06', label: '6' }, 35 | { value: '07', label: '7' }, 36 | { value: '08', label: '8' }, 37 | { value: '09', label: '9' }, 38 | { value: '10', label: '10' }, 39 | { value: '11', label: '11' }, 40 | { value: '12', label: '12' }, 41 | { value: '13', label: '13' }, 42 | { value: '14', label: '14' }, 43 | { value: '15', label: '15' }, 44 | { value: '16', label: '16' }, 45 | { value: '17', label: '17' }, 46 | { value: '18', label: '18' }, 47 | { value: '19', label: '19' }, 48 | { value: '20', label: '20' }, 49 | { value: '21', label: '21' }, 50 | { value: '22', label: '22' }, 51 | { value: '23', label: '23' }, 52 | { value: '24', label: '24' }, 53 | { value: '25', label: '25' }, 54 | { value: '26', label: '26' }, 55 | { value: '27', label: '27' }, 56 | { value: '28', label: '28' }, 57 | { value: '29', label: '29' }, 58 | { value: '30', label: '30' }, 59 | { value: '31', label: '31' }, 60 | ]; 61 | 62 | export const productCategory = [ 63 | { value: 'all products', label: 'All Products' }, 64 | { value: 'books', label: 'Books' }, 65 | { value: 'sports', label: 'Sports' }, 66 | { value: 'electronics', label: 'Electronics' }, 67 | { value: 'football', label: 'Football' }, 68 | { value: 'personal computers', label: 'Computers' }, 69 | { value: "women's clothing", label: "women's clothing" }, 70 | { value: "women's shoes", label: "women's shoes" }, 71 | { value: "men's clothing", label: "men's clothing" }, 72 | { value: "men's shoes", label: "men's shoes" }, 73 | { value: 'toys', label: 'Toys' }, 74 | ]; 75 | 76 | export const authorizationRoles = [ 77 | { value: 'user', label: 'User' }, 78 | { value: 'admin', label: 'Admin' }, 79 | { value: 'manger', label: 'Manger' }, 80 | { value: 'moderator', label: 'Moderator' }, 81 | { value: 'supervisor', label: 'Supervisor' }, 82 | { value: 'client', label: 'Client' }, 83 | { value: 'guide', label: 'Guide' }, 84 | ]; 85 | -------------------------------------------------------------------------------- /src/types/auth/_prototype.ts: -------------------------------------------------------------------------------- 1 | export type UserType = { 2 | _id?: string; 3 | name: string; 4 | surname: string; 5 | email: string; 6 | password?: string; 7 | confirmPassword?: string; 8 | role?: string; 9 | acceptTerms: boolean; 10 | gender: string; 11 | month?: string; 12 | day?: number; 13 | year?: string; 14 | familyName?: string; 15 | mobileNumber?: string; 16 | bio?: string; 17 | favoriteAnimal?: string; 18 | nationality?: string; 19 | companyName?: string; 20 | profileImage?: any; 21 | jobTitle?: string; 22 | status?: string; 23 | isVerified?: boolean; 24 | isDeleted?: boolean; 25 | address?: string; 26 | dateOfBirth?: string; 27 | createdAt?: string; 28 | updatedAt?: string; 29 | emailVerificationLinkToken?: string; 30 | token?: string; 31 | accessToken?: string; 32 | refreshToken?: string; 33 | }; 34 | 35 | export type LoginRequestType = { 36 | email: string; 37 | password: string; 38 | }; 39 | 40 | export type ForgetPasswordEmailRequestType = { 41 | email: string; 42 | }; 43 | 44 | export type ResetPasswordRequestType = { 45 | email?: string; 46 | password: string; 47 | confirmPassword: string; 48 | acceptTerms?: boolean; 49 | userId?: string | string[]; 50 | token?: string | string[]; 51 | }; 52 | 53 | export type VerifyEmailRequestType = { 54 | userId?: string | string[]; 55 | token?: string | string[]; 56 | }; 57 | 58 | export interface _authPrototypeReducerState { 59 | loginUser: null | UserType; 60 | loginUserIsLoading: boolean; 61 | loginUserIsSuccess: boolean; 62 | loginUserIsError: boolean; 63 | loginMessage: string; 64 | 65 | confirmEmailIsLoading: boolean; 66 | confirmEmailIsSuccess: boolean; 67 | confirmEmailIsError: boolean; 68 | confirmEmailIsMessage: string; 69 | 70 | emailVerificationLinkToken: string; 71 | token?: string; 72 | accessToken?: string; 73 | refreshToken?: string; 74 | signUpUserIsLoading: boolean; 75 | signUpUserIsSuccess: boolean; 76 | signUpUserIsError: boolean; 77 | signUpUserMessage: string; 78 | 79 | isAuthenticated: boolean; 80 | isADmin: boolean | string; 81 | 82 | forgetPasswordIsLoading: boolean; 83 | forgetPasswordIsSuccess: boolean; 84 | forgetPasswordIsError: boolean; 85 | forgetPasswordMessage: string; 86 | 87 | restPasswordIsLoading: boolean; 88 | restPasswordIsSuccess: boolean; 89 | restPasswordIsError: boolean; 90 | restPasswordMessage: string; 91 | 92 | updateProfileIsLoading: boolean; 93 | updateProfileIsSuccess: boolean; 94 | updateProfileIsError: boolean; 95 | updateProfileMessage: string; 96 | } 97 | 98 | export interface AuthResponseType { 99 | data: { 100 | accessToken: string; 101 | refreshToken: string; 102 | user: UserType; 103 | }; 104 | success: string; 105 | error: string; 106 | message: string; 107 | status: boolean; 108 | } 109 | -------------------------------------------------------------------------------- /src/services/socketClient.ts: -------------------------------------------------------------------------------- 1 | import openSocket, { Socket } from 'socket.io-client'; 2 | 3 | import { SocketClient } from './socketType'; 4 | 5 | // eslint-disable-next-line import/order 6 | import { getHostUrl } from '@/utils'; 7 | 8 | const urlString = getHostUrl() as string; // 'http://localhost:8000/api/v1'; 9 | const urlParts = urlString.split('/'); 10 | const SOCKET_SERVER_URL = `${urlParts[0]}//${urlParts[2]}`; // "http://localhost:8000" 11 | 12 | // Function to create a SocketClient instance 13 | export const createSocketClient = (): SocketClient => { 14 | // Create a new socket instance with the given options 15 | const socket: Socket = openSocket( 16 | process.env.NODE_ENV === 'development' ? SOCKET_SERVER_URL : SOCKET_SERVER_URL, 17 | { 18 | reconnection: true, 19 | reconnectionDelay: 3000, 20 | reconnectionDelayMax: 7000, 21 | reconnectionAttempts: Infinity, 22 | autoConnect: true, 23 | timeout: 10000, 24 | transports: ['polling', 'websocket'], 25 | } 26 | ); 27 | 28 | // Log when the socket connects 29 | socket.on('connect', () => { 30 | console.log('[SOCKET] connected'); 31 | }); 32 | 33 | // Log when the socket disconnects 34 | socket.on('disconnect', () => { 35 | console.log('[SOCKET] disconnect'); 36 | }); 37 | 38 | // Log when the socket reconnects 39 | socket.on('reconnect', () => { 40 | console.log('[SOCKET] reconnect'); 41 | }); 42 | 43 | // Log when there is an error 44 | socket.on('error', (err: any) => { 45 | console.log('[SOCKET] received socket error', err); 46 | }); 47 | 48 | // Log when there is a connection error 49 | socket.on('connect_error', (err: any) => { 50 | console.log('[SOCKET] received socket connect error', err); 51 | }); 52 | 53 | // Open the socket 54 | socket.open(); 55 | 56 | // Define the functions of the SocketClient interface 57 | const emit = (event: string, message: string) => { 58 | console.log(`[SOCKET] emit to "${event}" with message ${message}`); 59 | socket.emit(event, message); 60 | }; 61 | 62 | const connect = () => { 63 | socket.connect(); 64 | }; 65 | 66 | const disconnect = (callback?: () => any) => { 67 | socket.disconnect(); 68 | if (callback) callback(); 69 | }; 70 | 71 | const addHandler = (event: string, handler: any) => { 72 | console.log('[SOCKET] registerHandler >', event.replace(/_/g, ' ')); 73 | socket.on(event, handler); 74 | }; 75 | 76 | const removeHandler = (event: string, handler?: any) => { 77 | console.log('[SOCKET] removeHandler >', event.replace(/_/g, ' ')); 78 | if (!handler) { 79 | socket.off(event); 80 | } else { 81 | socket.off(event, handler); 82 | } 83 | }; 84 | 85 | const on = (event: string, handler: any) => { 86 | console.log('[SOCKET] on >', event.replace(/_/g, ' ')); 87 | socket.on(event, handler); 88 | }; 89 | 90 | // Return an object with the SocketClient interface functions 91 | return { 92 | emit, 93 | addHandler, 94 | removeHandler, 95 | connect, 96 | disconnect, 97 | on, 98 | }; 99 | }; 100 | 101 | // Export the createSocketClient function as default 102 | export default createSocketClient; 103 | -------------------------------------------------------------------------------- /src/components/layout/footer/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function Footer() { 4 | return ( 5 |
6 |
7 |
8 |
9 |

Company

10 |

About us

11 |

Our offerings

12 |

Newsroom

13 |

Investors

14 |

Blog

15 |

Careers

16 |

AI

17 |

Gift

18 |

cards

19 |
20 |
21 |

Products

22 |

Ride

23 |

Drive

24 |

Blog

25 |

for Business

26 |

Loremipsum

27 |

Loremipsum

28 |

Loremipsum

29 |

Loremipsum

30 |

Loremipsum

31 |
32 |
33 |

Global citizenship

34 |

Safety

35 |

Diversity and Inclusion

36 |

Loremipsum

37 |

Loremipsum

38 |

Loremipsum

39 |

Loremipsum

40 |

Loremipsum

41 |

Loremipsum

42 |

Loremipsum

43 |

Loremipsum

44 |
45 | 46 |
47 |

Travel

48 |

Airports

49 |

Cities

50 |

Loremipsum

51 |

Loremipsum

52 |

Loremipsum

53 |

Loremipsum

54 |

Loremipsum

55 |

Loremipsum

56 |

Loremipsum

57 |
58 |
59 |
60 |

Service Time: 09:00-18:00

61 |

Copyright © JSM SHOPE 2022. All rights reserved

62 |
63 |
64 |
65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ecommerce-website", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next -p 50050", 7 | "build": "next build", 8 | "export": "next build && next export", 9 | "start": "next start -p 50050", 10 | "lint": "next lint", 11 | "check-types": "tsc --pretty --noEmit", 12 | "check-format": "prettier --check .", 13 | "check-lint": "eslint . --ext ts --ext tsx --ext js", 14 | "format": "prettier --write .", 15 | "test-all": "yarn run check-format && yarn run check-lint && yarn run check-types && yarn run build", 16 | "prepare": "husky install" 17 | }, 18 | "dependencies": { 19 | "@badrap/bar-of-progress": "^0.1.2", 20 | "@emotion/react": "^11.8.2", 21 | "@emotion/styled": "^11.8.1", 22 | "@mui/icons-material": "^5.5.1", 23 | "@mui/material": "^5.5.1", 24 | "@mui/styles": "^5.5.1", 25 | "@reduxjs/toolkit": "^1.8.1", 26 | "axios": "^0.26.1", 27 | "mapbox-gl": "^2.8.2", 28 | "moment": "^2.29.3", 29 | "next": "latest", 30 | "next-redux-wrapper": "^7.0.5", 31 | "next-themes": "^0.1.1", 32 | "react": "^17.0.2", 33 | "react-dom": "^17.0.2", 34 | "react-hook-form": "^7.28.0", 35 | "react-map-gl": "^7.0.11", 36 | "react-number-format": "^4.9.3", 37 | "react-rating-stars-component": "^2.2.0", 38 | "react-redux": "^7.2.8", 39 | "redux": "^4.1.2", 40 | "redux-devtools-extension": "^2.13.9", 41 | "redux-logger": "^3.0.6", 42 | "redux-persist": "^6.0.0", 43 | "redux-thunk": "^2.4.1", 44 | "resolve": "^1.22.0", 45 | "socket.io-client": "^4.6.0", 46 | "tailwind-scrollbar-hide": "^1.1.7", 47 | "tailwindcss": "^3.0.24", 48 | "uuid": "^8.3.2", 49 | "yup": "^0.32.11" 50 | }, 51 | "devDependencies": { 52 | "@heroicons/react": "^1.0.6", 53 | "@hookform/resolvers": "^2.8.8", 54 | "@testing-library/jest-dom": "^5.16.4", 55 | "@testing-library/react": "^13.1.1", 56 | "@testing-library/user-event": "^14.1.0", 57 | "@types/jest": "^27.4.1", 58 | "@types/mocha": "^9.1.0", 59 | "@types/node": "^17.0.24", 60 | "@types/react": "^18.0.5", 61 | "@types/react-redux": "^7.1.24", 62 | "@types/redux-logger": "^3.0.9", 63 | "@types/socket.io-client": "^3.0.0", 64 | "@types/uuid": "^8.3.4", 65 | "@typescript-eslint/eslint-plugin": "^5.19.0", 66 | "@typescript-eslint/parser": "^5.19.0", 67 | "autoprefixer": "^10.4.0", 68 | "babel-jest": "^27.5.1", 69 | "eslint": "^8.13.0", 70 | "eslint-config-airbnb": "^19.0.4", 71 | "eslint-config-prettier": "^8.5.0", 72 | "eslint-import-resolver-alias": "^1.1.2", 73 | "eslint-import-resolver-typescript": "^2.7.1", 74 | "eslint-plugin-import": "^2.26.0", 75 | "eslint-plugin-jsx-a11y": "^6.5.1", 76 | "eslint-plugin-prettier": "^4.0.0", 77 | "eslint-plugin-react": "^7.29.4", 78 | "eslint-plugin-simple-import-sort": "^7.0.0", 79 | "eslint-plugin-tailwindcss": "^3.5.0", 80 | "eslint-plugin-testing-library": "^5.3.1", 81 | "husky": "^7.0.0", 82 | "identity-obj-proxy": "^3.0.0", 83 | "jest": "^27.5.1", 84 | "postcss": "^8.4.5", 85 | "prettier": "^2.6.2", 86 | "prettier-plugin-tailwindcss": "^0.1.1", 87 | "typescript": "4.5.4" 88 | }, 89 | "repository": "https://github.com/saddamarbaa/Ecommerce-website-next.js-typeScript", 90 | "author": "Saddam Arbaa" 91 | } 92 | -------------------------------------------------------------------------------- /src/utils/api/lib/axiosConfig.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; 2 | 3 | const onRequest = (config: AxiosRequestConfig): AxiosRequestConfig => { 4 | if (process.env.NODE_ENV !== 'production') { 5 | console.info(`[=== Axios REQUEST ===>] [${JSON.stringify(config)}]`); 6 | } 7 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 8 | // @ts-ignore 9 | // eslint-disable-next-line no-param-reassign 10 | config.headers.common.Authorization = `Bearer ${JSON.parse(localStorage.getItem('authToken'))}`; 11 | return config; 12 | }; 13 | 14 | const onRequestError = (error: AxiosError): Promise => { 15 | if (process.env.NODE_ENV !== 'production') { 16 | console.error('[=== Axios ERROR REQUEST ===>]', error, error.response); 17 | } 18 | return Promise.reject(error.response || error.message || error); 19 | }; 20 | 21 | const onResponse = (response: AxiosResponse) => { 22 | if (process.env.NODE_ENV !== 'production') { 23 | console.log('=== Axios SUCCESS RESPONSE data ===>', response.data); 24 | console.log('=== Axios SUCCESS RESPONSE status code ===>', response.status); 25 | console.log('=== Axios SUCCESS RESPONSE status code ===>', response.statusText); 26 | console.log('=== Axios SUCCESS RESPONSE status headers ===>', response.headers); 27 | console.log('=== Axios SUCCESS RESPONSE status config ===>', response.config); 28 | } 29 | console.info(`[=== Axios SUCCESS RESPONSE ===>] [${JSON.stringify(response)}]`); 30 | return Promise.resolve(response.data || response); 31 | }; 32 | 33 | const onResponseError = (error: AxiosError): Promise => { 34 | if (process.env.NODE_ENV !== 'production') { 35 | if (error.response) { 36 | // The request was made and the server responded with a status code 37 | // that falls out of the range of 2xx 38 | console.log('=== Axios ERROR RESPONSE ===>', error, error.response); 39 | console.log('=== Axios ERROR RESPONSE data ===>', error?.response?.data); 40 | console.log('=== Axios ERROR RESPONSE status ===>', error?.response?.status); 41 | console.log('=== Axios ERROR RESPONSE headers ===>', error?.response?.headers); 42 | } else if (error.request) { 43 | // The request was made but no response was received 44 | // `error.request` is an instance of XMLHttpRequest in the browser and an instance of 45 | // http.ClientRequest in node.js 46 | console.log('=== Axios Request ERROR ===>', error.request); 47 | } else { 48 | // Something happened in setting up the request that triggered an Error 49 | console.log( 50 | '=== Something happened in setting up the request that triggered an Error ===>', 51 | error.message 52 | ); 53 | } 54 | } 55 | 56 | console.log('=== Axios ERROR RESPONSE ===>', error, error.response); 57 | return Promise.reject(error.response || error.message); 58 | }; 59 | 60 | export function apiRequests(config: AxiosRequestConfig) { 61 | // init axios instance 62 | const axiosInstance = axios.create({}); 63 | 64 | // Add interceptor 65 | // intercept requests or responses before they are handled by then or catch. 66 | 67 | axiosInstance.interceptors.request.use(onRequest, onRequestError); 68 | axiosInstance.interceptors.response.use(onResponse, onResponseError); 69 | 70 | return axiosInstance(config); 71 | } 72 | 73 | export const pureAxios = axios.create(); 74 | -------------------------------------------------------------------------------- /src/global-states/actions/admin/usersActionCreators.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from 'redux'; 2 | 3 | import { UsersActionType, UserType } from '@/types'; 4 | import { apiRequests, getHostUrl } from '@/utils'; 5 | 6 | export const getUsers = (payload: string) => async (dispatch: Dispatch) => { 7 | dispatch({ type: UsersActionType.GET_USER_LIST_LOADING }); 8 | try { 9 | const response = await apiRequests({ 10 | method: 'get', 11 | url: `${getHostUrl()}${payload}`, 12 | }); 13 | dispatch({ 14 | type: UsersActionType.GET_USER_LIST_SUCCESS, 15 | payload: response, 16 | }); 17 | } catch (error: any) { 18 | dispatch({ 19 | type: UsersActionType.GET_USER_LIST_FAILED, 20 | payload: { error: error?.data?.message || error.statusText || error }, 21 | }); 22 | } 23 | }; 24 | 25 | export const resGetUserList = () => async (dispatch: Dispatch) => { 26 | dispatch({ type: UsersActionType.GET_USER_LIST_REST }); 27 | }; 28 | 29 | export const createUser = (user: UserType) => async (dispatch: Dispatch) => { 30 | dispatch({ type: UsersActionType.POST_USER_LOADING }); 31 | try { 32 | const response = await apiRequests({ 33 | method: 'post', 34 | url: `${getHostUrl()}/admin/users/add`, 35 | data: user, 36 | }); 37 | dispatch({ 38 | type: UsersActionType.POST_USER_SUCCESS, 39 | payload: response, 40 | }); 41 | } catch (error: any) { 42 | dispatch({ 43 | type: UsersActionType.POST_USER_FAILED, 44 | payload: { error: error?.data?.message || error.statusText || error }, 45 | }); 46 | } 47 | }; 48 | 49 | export const restCreateUser = () => async (dispatch: Dispatch) => { 50 | dispatch({ type: UsersActionType.POST_USER_REST }); 51 | }; 52 | 53 | export const deleteUser = (id: string) => async (dispatch: Dispatch) => { 54 | dispatch({ type: UsersActionType.DELETE_USER_LOADING }); 55 | try { 56 | const response = await apiRequests({ 57 | method: 'delete', 58 | url: `${getHostUrl()}/admin/users/remove/${id}`, 59 | }); 60 | 61 | dispatch({ type: UsersActionType.DELETE_USER_SUCCESS, payload: response }); 62 | } catch (error: any) { 63 | dispatch({ 64 | type: UsersActionType.DELETE_USER_FAILED, 65 | payload: { error: error?.data?.message || error.statusText || error }, 66 | }); 67 | } 68 | }; 69 | 70 | export const restDeleteUser = () => async (dispatch: Dispatch) => { 71 | dispatch({ type: UsersActionType.DELETE_USER_REST }); 72 | }; 73 | 74 | export const getIndividualUser = 75 | (id: string | string[], isAdmin?: boolean) => async (dispatch: any) => { 76 | dispatch({ type: UsersActionType.GET_INDIVIDUAL_USER_LOADING }); 77 | try { 78 | const response = await apiRequests({ 79 | method: 'get', 80 | url: isAdmin ? `${getHostUrl()}/admin/users/${id}` : `${getHostUrl()}/auth/me`, 81 | }); 82 | dispatch({ 83 | type: UsersActionType.GET_INDIVIDUAL_USER_SUCCESS, 84 | payload: response, 85 | }); 86 | } catch (error: any) { 87 | dispatch({ 88 | type: UsersActionType.GET_INDIVIDUAL_USER_FAILED, 89 | payload: { error: error?.data?.message || error.statusText || error }, 90 | }); 91 | } 92 | }; 93 | 94 | export const restGetIndividualUser = () => async (dispatch: Dispatch) => { 95 | dispatch({ type: UsersActionType.GET_INDIVIDUAL_USER_REST }); 96 | }; 97 | 98 | export const updateUser = 99 | (user: UserType, id: string | string[], isAdmin?: boolean) => async (dispatch: Dispatch) => { 100 | dispatch({ type: UsersActionType.UPDATE_USER_LOADING }); 101 | try { 102 | const response = await apiRequests({ 103 | method: 'put', 104 | url: isAdmin ? `${getHostUrl()}/admin/users/update/${id}` : `${getHostUrl()}/auth/${id}`, 105 | data: user, 106 | }); 107 | dispatch({ 108 | type: UsersActionType.UPDATE_USER_SUCCESS, 109 | payload: response, 110 | }); 111 | } catch (error: any) { 112 | dispatch({ 113 | type: UsersActionType.UPDATE_USER_FAILED, 114 | payload: { error: error?.data?.message || error.statusText || error }, 115 | }); 116 | } 117 | }; 118 | 119 | export const restUpdateUser = () => async (dispatch: Dispatch) => { 120 | dispatch({ type: UsersActionType.UPDATE_USER_REST }); 121 | }; 122 | -------------------------------------------------------------------------------- /src/utils/schemaValidation/auth.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from 'yup'; 2 | 3 | export const signupSchemaValidation = Yup.object().shape({ 4 | name: Yup.string() 5 | .required('Name is required') 6 | .min(3, 'Name must be at least 3 characters') 7 | .max(10, 'Name must not exceed 10 characters'), 8 | surname: Yup.string() 9 | .required('Surname is required') 10 | .min(3, 'Surname must be at least 3 characters') 11 | .max(10, 'Surname must not exceed 10 characters'), 12 | email: Yup.string().required('Email is required').email('Email is invalid'), 13 | password: Yup.string() 14 | .required('Password is required') 15 | .min(6, 'Password must be at least 6 characters') 16 | .max(40, 'Password must not exceed 40 characters'), 17 | confirmPassword: Yup.string() 18 | .required('Confirm Password is required') 19 | .oneOf([Yup.ref('password'), null], 'Confirm Password does not match'), 20 | acceptTerms: Yup.bool().oneOf([true], 'Accept Terms is required'), 21 | jobTitle: Yup.string() 22 | .min(3, 'Job Title must be at least 3 characters') 23 | .max(15, 'Job Title must not exceed 15 characters'), 24 | companyName: Yup.string() 25 | .min(3, 'Company Name cant be smaller than 3 characters') 26 | .max(30, 'Company Name cant be greater than 30 characters'), 27 | favoriteAnimal: Yup.string() 28 | .min(3, 'Favorite Animal cant be smaller than 3 characters') 29 | .max(15, 'Favorite Animal cant be greater than 15 characters'), 30 | bio: Yup.string() 31 | .min(10, 'Bio must be at least 10 characters') 32 | .max(300, 'Bio must not exceed 100 characters'), 33 | profileImage: Yup.mixed() 34 | .test('required', 'Image is required', (value: any) => value && value.length) 35 | .test('fileSize', 'File Size is too large', (value) => { 36 | const sizeInBytes = 1024 * 1024 * 10; // accept files up 10 mgb 37 | return value && value[0] && value[0].size <= sizeInBytes; 38 | }) 39 | .test('type', 'We only support jpeg, jpg, png, webp', (value) => { 40 | const SUPPORTED_FORMATS = ['image/jpg', 'image/jpeg', 'image/png', 'image/webp']; 41 | return value && value[0] && SUPPORTED_FORMATS.includes(value[0].type.toLowerCase()); 42 | }), 43 | }); 44 | 45 | export const LoginSchemaValidation = Yup.object().shape({ 46 | email: Yup.string().required('Email is required').email('Email is invalid'), 47 | password: Yup.string() 48 | .required('Password is required') 49 | .min(6, 'Password must be at least 6 characters') 50 | .max(40, 'Password must not exceed 40 characters'), 51 | acceptTerms: Yup.bool().oneOf([true], 'Accept Terms is required'), 52 | }); 53 | 54 | export const forgotPasswordSchemaValidation = Yup.object().shape({ 55 | email: Yup.string().required('Email is required').email('Email is invalid'), 56 | }); 57 | 58 | export const updatePasswordSchemaValidation = Yup.object().shape({ 59 | firstName: Yup.string(), 60 | email: Yup.string().required('Email is required').email('Email is invalid'), 61 | password: Yup.string() 62 | .required('Password is required') 63 | .min(6, 'Password must be at least 6 characters') 64 | .max(40, 'Password must not exceed 40 characters'), 65 | confirmPassword: Yup.string() 66 | .required('Confirm Password is required') 67 | .oneOf([Yup.ref('password'), null], 'Confirm Password does not match'), 68 | acceptTerms: Yup.bool().oneOf([true], 'Accept Terms is required'), 69 | }); 70 | 71 | export const updateUserSchemaValidation = Yup.object().shape({ 72 | name: Yup.string() 73 | .required('Name is required') 74 | .min(3, 'Name must be at least 3 characters') 75 | .max(10, 'Name must not exceed 10 characters'), 76 | surname: Yup.string() 77 | .required('Surname is required') 78 | .min(3, 'Surname must be at least 3 characters') 79 | .max(10, 'Surname must not exceed 10 characters'), 80 | acceptTerms: Yup.bool().oneOf([true], 'Accept Terms is required'), 81 | jobTitle: Yup.string() 82 | .min(3, 'Job Title must be at least 3 characters') 83 | .max(15, 'Job Title must not exceed 15 characters'), 84 | companyName: Yup.string() 85 | .min(3, 'Company Name cant be smaller than 3 characters') 86 | .max(30, 'Company Name cant be greater than 30 characters'), 87 | favoriteAnimal: Yup.string() 88 | .min(3, 'Favorite Animal cant be smaller than 3 characters') 89 | .max(15, 'Favorite Animal cant be greater than 15 characters'), 90 | bio: Yup.string() 91 | .min(10, 'Bio must be at least 10 characters') 92 | .max(300, 'Bio must not exceed 100 characters'), 93 | }); 94 | -------------------------------------------------------------------------------- /src/types/admin/products/_prototype.ts: -------------------------------------------------------------------------------- 1 | import { UserType } from '../../auth'; 2 | 3 | export interface ReviewsT { 4 | user: string; 5 | name: string; 6 | rating?: number; 7 | comment: string; 8 | } 9 | export interface ProductType { 10 | id?: string; 11 | _id?: string; 12 | name: string; 13 | price: string; 14 | brand?: string; 15 | description: string; 16 | productImage: any; 17 | category: string; 18 | rating?: string; 19 | count?: string; 20 | stock?: string; 21 | user?: UserType; 22 | numberOfReviews?: number; 23 | reviews?: ReviewsT[]; 24 | ratings?: number; 25 | createdAt?: string; 26 | updatedAt?: string; 27 | productImages?: { 28 | url: string; 29 | cloudinary_id: string; 30 | _id: string; 31 | }[]; 32 | } 33 | 34 | export interface CartItemsTpe { 35 | product: ProductType; 36 | _id: string; 37 | quantity: number; 38 | } 39 | 40 | export interface OrderResTpe { 41 | user: UserType; 42 | _id: string; 43 | orderItems: CartItemsTpe[]; 44 | } 45 | 46 | export interface _productPrototypeReducerState { 47 | product: object; 48 | addProductIsLoading: boolean; 49 | addProductIsSuccess: boolean; 50 | addProductIsError: boolean; 51 | addProductMessage: string; 52 | 53 | list: object; 54 | listIsLoading: boolean; 55 | listIsSuccess: boolean; 56 | listIsError: boolean; 57 | listMessage: string; 58 | totalDocs: number; 59 | lastPage: number; 60 | products: ProductType[]; 61 | 62 | productSearchTerm: string; 63 | selectedCategory: string; 64 | limit: number; 65 | page: number; 66 | sort: string; 67 | sortBy: string; 68 | 69 | deleteProductIsPending: boolean; 70 | deleteProductIsSuccess: boolean; 71 | deleteProductIsError: boolean; 72 | deleteProductMessage: string; 73 | 74 | individualProduct: ProductType | null; 75 | getIndividualProductIsPending: boolean; 76 | getIndividualProductIsSuccess: boolean; 77 | getIndividualProductIsError: boolean; 78 | getIndividualProductIsMessage: string; 79 | 80 | updatedProduct: ProductType | null; 81 | updateProductIsPending: boolean; 82 | updateProductIsSuccess: boolean; 83 | updateProductIsError: boolean; 84 | updateProductMessage: string; 85 | 86 | cart: CartItemsTpe[]; 87 | getCartIsPending: boolean; 88 | getCartIsSuccess: boolean; 89 | getCartIsError: boolean; 90 | getCartMessage: string; 91 | 92 | AddToCartIsLoading: boolean; 93 | AddToCartIsSuccess: boolean; 94 | AddToCartIsError: boolean; 95 | AddToCartMessage: string; 96 | 97 | deleteItemFromCartIsLoading: boolean; 98 | deleteItemFromCartIsSuccess: boolean; 99 | deleteItemFromCartIsError: boolean; 100 | deleteItemFromCartMessage: string; 101 | 102 | clearCartIsLoading: boolean; 103 | clearCartIsSuccess: boolean; 104 | clearCartIsError: boolean; 105 | clearCartMessage: string; 106 | 107 | orders: OrderResTpe[]; 108 | getOrderIsPending: boolean; 109 | getOrderIsSuccess: boolean; 110 | getOrderIsError: boolean; 111 | getOrderMessage: string; 112 | 113 | addOrderIsLoading: boolean; 114 | addOrderIsSuccess: boolean; 115 | addOrderIsError: boolean; 116 | addOrderMessage: string; 117 | 118 | clearOrderIsLoading: boolean; 119 | clearOrderIsSuccess: boolean; 120 | clearOrderIsError: boolean; 121 | clearOrderMessage: string; 122 | 123 | clearSingleOrderIsLoading: boolean; 124 | clearSingleIsSuccess: boolean; 125 | clearSingleIsError: boolean; 126 | clearSingleMessage: string; 127 | 128 | deleteReviewIsLoading: boolean; 129 | deleteReviewIsSuccess: boolean; 130 | deleteReviewIsError: boolean; 131 | deleteReviewMessage: string; 132 | } 133 | 134 | export interface ProductsResponseType { 135 | data: { 136 | totalDocs: number; 137 | totalPages: number; 138 | lastPage: number; 139 | count: number; 140 | currentPage: number; 141 | products: ProductType[]; 142 | }; 143 | success: string; 144 | error: string; 145 | message: string; 146 | status: boolean; 147 | } 148 | 149 | export interface ProductResponseType { 150 | data: { 151 | product: ProductType; 152 | }; 153 | success: string; 154 | error: string; 155 | message: string; 156 | status: boolean; 157 | } 158 | 159 | export interface CartIResponseType { 160 | data: { 161 | products: CartItemsTpe[]; 162 | userId: string; 163 | }; 164 | success: string; 165 | error: string; 166 | message: string; 167 | status: boolean; 168 | } 169 | 170 | export interface OrderResponseType { 171 | data: { 172 | orders: OrderResTpe[]; 173 | userId: string; 174 | }; 175 | success: string; 176 | error: string; 177 | message: string; 178 | status: boolean; 179 | } 180 | 181 | export default _productPrototypeReducerState; 182 | -------------------------------------------------------------------------------- /src/types/admin/users/actionsType.tsx: -------------------------------------------------------------------------------- 1 | // REDUX ACTION TYPES 2 | 3 | import { Action } from 'redux'; 4 | 5 | import { UserResponseType, UsersResponseType } from './_prototype'; 6 | 7 | export enum UsersActionType { 8 | GET_USER_LIST_LOADING = 'GET_USER_LIST_LOADING', 9 | GET_USER_LIST_SUCCESS = 'GET_USER_LIST_SUCCESS', 10 | GET_USER_LIST_FAILED = 'GET_USER_LIST_FAILED', 11 | GET_USER_LIST_REST = 'GET_USER_LIST_REST', 12 | GET_INDIVIDUAL_USER_LOADING = 'GET_INDIVIDUAL_USER_LOADING', 13 | GET_INDIVIDUAL_USER_SUCCESS = 'GET_INDIVIDUAL_USER_SUCCESS', 14 | GET_INDIVIDUAL_USER_FAILED = 'GET_INDIVIDUAL_USER_FAILED', 15 | GET_INDIVIDUAL_USER_REST = 'GET_INDIVIDUAL_USER_REST', 16 | POST_USER_LOADING = 'POST_USER_LOADING', 17 | POST_USER_SUCCESS = 'POST_USER_SUCCESS', 18 | POST_USER_FAILED = 'POST_USER_FAILED', 19 | POST_USER_REST = 'POST_USER_REST', 20 | UPDATE_USER_LOADING = 'UPDATE_USER_LOADING', 21 | UPDATE_USER_SUCCESS = 'UPDATE_USER_SUCCESS', 22 | UPDATE_USER_FAILED = 'UPDATE_USER_FAILED', 23 | UPDATE_USER_REST = 'UPDATE_USER_REST', 24 | DELETE_USER_LOADING = 'DELETE_USER_LOADING', 25 | DELETE_USER_SUCCESS = 'DELETE_USER_SUCCESS', 26 | DELETE_USER_FAILED = 'DELETE_USER_FAILED', 27 | DELETE_USER_REST = 'DELETE_USER_REST', 28 | } 29 | 30 | export interface actionGetUsersISPending extends Action { 31 | type: UsersActionType.GET_USER_LIST_LOADING; 32 | } 33 | 34 | export interface actionGetUsersISSuccess extends Action { 35 | type: UsersActionType.GET_USER_LIST_SUCCESS; 36 | payload: UsersResponseType; 37 | } 38 | 39 | export interface actionGetUsersISError extends Action { 40 | type: UsersActionType.GET_USER_LIST_FAILED; 41 | payload: UsersResponseType; 42 | } 43 | 44 | export interface actionGetUsersISRest extends Action { 45 | type: UsersActionType.GET_USER_LIST_REST; 46 | } 47 | 48 | export interface actionGetIndividualUserISPending extends Action { 49 | type: UsersActionType.GET_INDIVIDUAL_USER_LOADING; 50 | } 51 | 52 | export interface actionGetIndividualUserISSuccess extends Action { 53 | type: UsersActionType.GET_INDIVIDUAL_USER_SUCCESS; 54 | payload: UserResponseType; 55 | } 56 | 57 | export interface actionGetIndividualUserISError extends Action { 58 | type: UsersActionType.GET_INDIVIDUAL_USER_FAILED; 59 | payload: UserResponseType; 60 | } 61 | 62 | export interface actionGetIndividualUserISRest extends Action { 63 | type: UsersActionType.GET_INDIVIDUAL_USER_REST; 64 | } 65 | 66 | export interface actionPostUserISPending extends Action { 67 | type: UsersActionType.POST_USER_LOADING; 68 | } 69 | 70 | export interface actionPostUserISSuccess extends Action { 71 | type: UsersActionType.POST_USER_SUCCESS; 72 | payload: UserResponseType; 73 | } 74 | 75 | export interface actionPostUserISError extends Action { 76 | type: UsersActionType.POST_USER_FAILED; 77 | payload: UserResponseType; 78 | } 79 | 80 | export interface actionPostUserISRest extends Action { 81 | type: UsersActionType.POST_USER_REST; 82 | } 83 | 84 | export interface actionDeleteUserISPending extends Action { 85 | type: UsersActionType.DELETE_USER_LOADING; 86 | } 87 | 88 | export interface actionDeleteUserISSuccess extends Action { 89 | type: UsersActionType.DELETE_USER_SUCCESS; 90 | payload: UserResponseType; 91 | } 92 | 93 | export interface actionDeleteUserISError extends Action { 94 | type: UsersActionType.DELETE_USER_FAILED; 95 | payload: UserResponseType; 96 | } 97 | 98 | export interface actionDeleteUserISRest extends Action { 99 | type: UsersActionType.DELETE_USER_REST; 100 | } 101 | 102 | export interface actionUpdateUserISPending extends Action { 103 | type: UsersActionType.UPDATE_USER_LOADING; 104 | } 105 | 106 | export interface actionUpdateUserISSuccess extends Action { 107 | type: UsersActionType.UPDATE_USER_SUCCESS; 108 | payload: UserResponseType; 109 | } 110 | 111 | export interface actionUpdateUserISError extends Action { 112 | type: UsersActionType.UPDATE_USER_FAILED; 113 | payload: UserResponseType; 114 | } 115 | 116 | export interface actionUpdateUserISRest extends Action { 117 | type: UsersActionType.UPDATE_USER_REST; 118 | } 119 | 120 | export type UsersAction = 121 | | actionGetUsersISPending 122 | | actionGetUsersISSuccess 123 | | actionGetUsersISError 124 | | actionGetUsersISRest 125 | | actionGetIndividualUserISPending 126 | | actionGetIndividualUserISSuccess 127 | | actionGetIndividualUserISError 128 | | actionGetIndividualUserISRest 129 | | actionPostUserISPending 130 | | actionPostUserISSuccess 131 | | actionPostUserISError 132 | | actionPostUserISRest 133 | | actionDeleteUserISPending 134 | | actionDeleteUserISSuccess 135 | | actionDeleteUserISError 136 | | actionDeleteUserISRest 137 | | actionUpdateUserISPending 138 | | actionUpdateUserISSuccess 139 | | actionUpdateUserISError 140 | | actionUpdateUserISRest; 141 | -------------------------------------------------------------------------------- /src/global-states/actions/auth/authActionCreators.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from 'redux'; 2 | 3 | import { 4 | AuthenticationActionType, 5 | ForgetPasswordEmailRequestType, 6 | LoginRequestType, 7 | ResetPasswordRequestType, 8 | UserType, 9 | VerifyEmailRequestType, 10 | } from '@/types'; 11 | import { apiRequests, getHostUrl } from '@/utils'; 12 | 13 | // AUTH 14 | export const signUp = (user: UserType) => async (dispatch: Dispatch) => { 15 | dispatch({ type: AuthenticationActionType.AUTH_SIGINUP_LOADING }); 16 | 17 | try { 18 | const response = await apiRequests({ 19 | method: 'post', 20 | url: `${getHostUrl()}/auth/signup`, 21 | data: user, 22 | }); 23 | dispatch({ type: AuthenticationActionType.AUTH_SIGINUP_SUCCESS, payload: response }); 24 | } catch (error: any) { 25 | dispatch({ 26 | type: AuthenticationActionType.AUTH_SIGINUP_FAILED, 27 | payload: { error: error?.data?.message || error.statusText || error }, 28 | }); 29 | } 30 | }; 31 | 32 | export const verifyEmail = (data: VerifyEmailRequestType) => async (dispatch: Dispatch) => { 33 | dispatch({ type: AuthenticationActionType.AUTH_CONFIRM_EMAIL_LOADING }); 34 | try { 35 | const response = await apiRequests({ 36 | method: 'get', 37 | url: `${getHostUrl()}/auth/verify-email/${data.userId}/${data.token}`, 38 | }); 39 | dispatch({ type: AuthenticationActionType.AUTH_CONFIRM_EMAIL_SUCCESS, payload: response }); 40 | } catch (error: any) { 41 | dispatch({ 42 | type: AuthenticationActionType.AUTH_CONFIRM_EMAIL_FAILED, 43 | payload: { error: error?.data?.message || error.statusText || error }, 44 | }); 45 | } 46 | }; 47 | 48 | export const LogIn = (user: LoginRequestType) => async (dispatch: Dispatch) => { 49 | dispatch({ type: AuthenticationActionType.AUTH_LOGIN_LOADING }); 50 | try { 51 | const response = await apiRequests({ 52 | method: 'post', 53 | url: `${getHostUrl()}/auth/login`, 54 | data: user, 55 | }); 56 | dispatch({ type: AuthenticationActionType.AUTH_LOGIN_SUCCESS, payload: response }); 57 | } catch (error: any) { 58 | dispatch({ 59 | type: AuthenticationActionType.AUTH_LOGIN_FAILED, 60 | payload: { error: error?.data?.message || error.statusText || error }, 61 | }); 62 | } 63 | }; 64 | 65 | export const removeAuthenticatedUser = () => async (dispatch: Dispatch) => { 66 | dispatch({ 67 | type: AuthenticationActionType.REMOVE_AUTHENTICATED_USER, 68 | }); 69 | }; 70 | 71 | export const forgetPassword = 72 | (email: ForgetPasswordEmailRequestType) => async (dispatch: Dispatch) => { 73 | dispatch({ type: AuthenticationActionType.AUTH_FORGET_PASSWORD_LOADING }); 74 | try { 75 | const response = await apiRequests({ 76 | method: 'post', 77 | url: `${getHostUrl()}/auth/forget-password`, 78 | data: email, 79 | }); 80 | dispatch({ type: AuthenticationActionType.AUTH_FORGET_PASSWORD_SUCCESS, payload: response }); 81 | } catch (error: any) { 82 | dispatch({ 83 | type: AuthenticationActionType.AUTH_FORGET_PASSWORD_FAILED, 84 | payload: { error: error?.data?.message || error.statusText || error }, 85 | }); 86 | } 87 | }; 88 | 89 | export const restPassword = (data: ResetPasswordRequestType) => async (dispatch: Dispatch) => { 90 | dispatch({ type: AuthenticationActionType.AUTH_REST_PASSWORD_LOADING }); 91 | try { 92 | const response = await apiRequests({ 93 | method: 'post', 94 | url: `${getHostUrl()}/auth/reset-password/${data.userId}/${data.token}`, 95 | data, 96 | }); 97 | dispatch({ type: AuthenticationActionType.AUTH_REST_PASSWORD_SUCCESS, payload: response }); 98 | } catch (error: any) { 99 | dispatch({ 100 | type: AuthenticationActionType.AUTH_REST_PASSWORD_FAILED, 101 | payload: { error: error?.data?.message || error.statusText || error }, 102 | }); 103 | } 104 | }; 105 | 106 | export const restVerifyEmail = () => async (dispatch: Dispatch) => { 107 | dispatch({ type: AuthenticationActionType.AUTH_CONFIRM_EMAIL_REST }); 108 | }; 109 | 110 | export const restPasswordRest = () => async (dispatch: Dispatch) => { 111 | dispatch({ type: AuthenticationActionType.AUTH_REST_PASSWORD_REST }); 112 | }; 113 | 114 | export const restforgetPassword = () => async (dispatch: Dispatch) => { 115 | dispatch({ type: AuthenticationActionType.AUTH_FORGET_PASSWORD_REST }); 116 | }; 117 | 118 | export const restLoginState = () => async (dispatch: Dispatch) => { 119 | dispatch({ type: AuthenticationActionType.AUTH_LOGIN_REST }); 120 | }; 121 | 122 | export const restSignUpState = () => async (dispatch: Dispatch) => { 123 | dispatch({ type: AuthenticationActionType.AUTH_SIGINUP_REST }); 124 | }; 125 | 126 | export const updateProfile = 127 | (user: UserType, id: string | string[]) => async (dispatch: Dispatch) => { 128 | dispatch({ type: AuthenticationActionType.AUTH_UPDATE_PROFILE_LOADING }); 129 | try { 130 | const response = await apiRequests({ 131 | method: 'patch', 132 | url: `${getHostUrl()}/auth/update/${id}`, 133 | data: user, 134 | }); 135 | dispatch({ 136 | type: AuthenticationActionType.AUTH_UPDATE_PROFILE_SUCCESS, 137 | payload: response, 138 | }); 139 | } catch (error: any) { 140 | dispatch({ 141 | type: AuthenticationActionType.AUTH_UPDATE_PROFILE_FAILED, 142 | payload: { error: error?.data?.message || error.statusText || error }, 143 | }); 144 | } 145 | }; 146 | 147 | export const restUpdateProfile = () => async (dispatch: Dispatch) => { 148 | dispatch({ type: AuthenticationActionType.AUTH_UPDATE_PROFILE_REST }); 149 | }; 150 | -------------------------------------------------------------------------------- /src/page-components/verify-email-page/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Alert } from '@mui/material'; 4 | import CircularProgress from '@mui/material/CircularProgress'; 5 | import { useRouter } from 'next/router'; 6 | 7 | import { ReducerType } from '@/global-states'; 8 | import { restVerifyEmail, verifyEmail } from '@/global-states/actions'; 9 | import { 10 | _authPrototypeReducerState as ReducerState, 11 | VerifyEmailRequestType as VerifyEmailType, 12 | } from '@/types'; 13 | 14 | interface OwnProps { 15 | userId: string | string[] | undefined; 16 | token: string | string[] | undefined; 17 | } 18 | 19 | interface MapDispatchProps { 20 | restVerifyEmail: () => void; 21 | verifyEmail: (data: VerifyEmailType) => void; 22 | } 23 | 24 | interface MapStateProps { 25 | authState: ReducerState; 26 | } 27 | 28 | type PropsType = OwnProps & MapDispatchProps & MapStateProps; 29 | 30 | function VerfiyEmailPageComponent({ 31 | userId, 32 | token, 33 | authState, 34 | restVerifyEmail, 35 | verifyEmail, 36 | }: PropsType) { 37 | const router = useRouter(); 38 | const autoScrollToBottomRef = useRef(null); 39 | const [showAlert, setShowAlert] = useState(false); 40 | const [logIn, setLogIn] = useState(false); 41 | 42 | const { 43 | confirmEmailIsLoading, 44 | confirmEmailIsSuccess, 45 | confirmEmailIsError, 46 | confirmEmailIsMessage, 47 | } = authState; 48 | 49 | useEffect(() => { 50 | setLogIn(() => false); 51 | restVerifyEmail(); 52 | }, []); 53 | 54 | useEffect(() => { 55 | if (userId && token) { 56 | const finalData = { 57 | userId, 58 | token, 59 | }; 60 | verifyEmail(finalData); 61 | } 62 | }, [userId, token]); 63 | 64 | useEffect(() => { 65 | window.scrollTo({ 66 | top: 0, 67 | behavior: 'smooth', 68 | }); 69 | // Auto Scroll functionality 70 | autoScrollToBottomRef?.current?.scrollIntoView({ 71 | behavior: 'smooth', 72 | }); 73 | }, []); 74 | 75 | useEffect(() => { 76 | if (confirmEmailIsSuccess) { 77 | const timer = setTimeout(() => { 78 | setShowAlert(() => false); 79 | setLogIn(() => true); 80 | restVerifyEmail(); 81 | }, 5000); 82 | return () => clearTimeout(timer); 83 | } 84 | }, [confirmEmailIsSuccess]); 85 | 86 | useEffect(() => { 87 | if (confirmEmailIsSuccess || confirmEmailIsError) { 88 | setShowAlert(() => true); 89 | if (confirmEmailIsError) { 90 | const timer = setTimeout(() => { 91 | setShowAlert(() => false); 92 | setLogIn(() => false); 93 | restVerifyEmail(); 94 | }, 30000); 95 | return () => clearTimeout(timer); 96 | } 97 | } 98 | }, [confirmEmailIsSuccess, confirmEmailIsError]); 99 | 100 | if (logIn) { 101 | router.replace('/login'); 102 | } 103 | 104 | return ( 105 |
106 |
107 |
113 | {confirmEmailIsLoading && ( 114 |
115 | 116 |
117 | )} 118 | {showAlert && (confirmEmailIsError || confirmEmailIsSuccess) && ( 119 | setShowAlert(false)} 123 | > 124 | {confirmEmailIsMessage} 125 | 126 | )} 127 | 128 | {!userId && !token && !confirmEmailIsError && !confirmEmailIsSuccess && ( 129 | 130 | Auth Failed (Invalid Credentials) 131 | 132 | )} 133 | 134 |
135 |
136 |
137 | 144 |
145 |
146 |
147 |
148 | 149 |
150 | Conditions of Use 151 | Privacy Notice 152 | Help 153 |
154 |
155 | 156 | © 2004-2021, saddamarbaa.com, Inc. or its affiliates 157 | 158 |
159 |
160 |
161 | ); 162 | } 163 | 164 | const mapStateToProps = (state: ReducerType) => ({ 165 | authState: state.auth, 166 | }); 167 | 168 | const mapDispatchToProps = { 169 | restVerifyEmail, 170 | verifyEmail, 171 | }; 172 | 173 | export default connect(mapStateToProps, mapDispatchToProps)(VerfiyEmailPageComponent); 174 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | body { 7 | @apply min-h-screen; 8 | } 9 | html { 10 | font-family: 'Nunito Sans', sans-serif; 11 | } 12 | } 13 | 14 | @layer components { 15 | .customButton { 16 | @apply cursor-pointer rounded-full border px-4 py-2 hover:shadow-lg; 17 | } 18 | 19 | .customlink { 20 | @apply text-[1.2rem] text-[#fff] no-underline transition duration-200 hover:text-[#ffeb3b] hover:underline active:text-[#ffeb3b]; 21 | } 22 | 23 | .custom-span { 24 | @apply cursor-pointer text-[#0066c0] transition duration-100 hover:underline; 25 | } 26 | } 27 | 28 | .btn { 29 | display: inline-block; 30 | padding: 0.25rem 1.2rem; 31 | text-decoration: none; 32 | font: inherit; 33 | border: 1px solid #00695c; 34 | color: #00695c; 35 | background: white; 36 | border-radius: 3px; 37 | cursor: pointer; 38 | transition: 0.3s; 39 | } 40 | 41 | .btn:hover, 42 | .btn:active { 43 | background-color: #00695c; 44 | color: white; 45 | } 46 | 47 | .btn.danger { 48 | color: red; 49 | border-color: red; 50 | } 51 | 52 | .btn.danger:hover, 53 | .btn.danger:active { 54 | background: red; 55 | color: white; 56 | } 57 | 58 | .control { 59 | margin-bottom: 2rem; 60 | color: #1c1e21; 61 | } 62 | 63 | .control label { 64 | display: block; 65 | font-weight: bold; 66 | margin-bottom: 0.5rem; 67 | font-size: 14px; 68 | font-weight: bold; 69 | color: #222; 70 | } 71 | 72 | .control label#filePicker-label, 73 | .control textarea, 74 | .control input { 75 | display: inline-flex; 76 | font-size: 1rem; 77 | height: 2.7rem; 78 | justify-content: flex-start; 79 | line-height: normal; 80 | padding: 1rem; 81 | align-items: center; 82 | background-color: #fff; 83 | width: 100%; 84 | border-radius: 4px; 85 | box-shadow: 0 1px 0 rgb(255 255 255 / 50%), 0 1px 0 rgb(0 0 0 / 7%) inset; 86 | border: 1px solid #a6a6a6; 87 | border-top-color: #949494; 88 | transition: 0.4s; 89 | } 90 | 91 | .control textarea { 92 | height: 9.688rem; 93 | resize: none; 94 | } 95 | 96 | .control textarea:hover, 97 | .control input:hover { 98 | outline: none; 99 | border: 1px solid rgb(220, 227, 232); 100 | } 101 | 102 | .control textarea:hover, 103 | .control input:focus, 104 | .control input:active { 105 | outline: 0 !important; 106 | border-color: #00695c; 107 | box-shadow: 0 0 3px 2px rgb(14 118 168/ 50%); 108 | } 109 | 110 | .control label#touched.invalid, 111 | .control textarea.is-invalid, 112 | .control textarea.touched.invalid, 113 | .control input.is-invalid, 114 | .control input.touched.invalid { 115 | border-color: red; 116 | background: #ffc2c2; 117 | box-shadow: none; 118 | transition: 0.3s; 119 | } 120 | 121 | .actions { 122 | margin-top: 1.5rem; 123 | } 124 | p.option { 125 | border-bottom: 1px solid #a6a6a6; 126 | text-align: center; 127 | font-weight: bold; 128 | margin-top: 1.813rem !important; 129 | font-size: 12.5px !important; 130 | line-height: 1.5 !important ; 131 | } 132 | 133 | .month-container, 134 | .day-container, 135 | .year-container { 136 | flex: 1; 137 | display: flex; 138 | } 139 | 140 | /* Reset Select */ 141 | .select select { 142 | cursor: pointer; 143 | flex: 1; 144 | position: relative; 145 | overflow: hidden; 146 | transition: 0.3s; 147 | border-radius: 0.25em; 148 | color: gray; 149 | height: 2.4rem; 150 | border-radius: 4px; 151 | box-shadow: 0 1px 0 rgb(255 255 255 / 50%), 0 1px 0 rgb(0 0 0 / 7%) inset; 152 | border: 1px solid #dadde1; 153 | border-top-color: #dadde1; 154 | width: 100%; 155 | padding: 0.4rem 0; 156 | background-color: #fff; 157 | } 158 | .select select:hover { 159 | outline: none; 160 | border: 1px solid rgb(220, 227, 232); 161 | } 162 | .select select:focus, 163 | .select select:active { 164 | outline: 0 !important; 165 | border-color: #00695c; 166 | box-shadow: 0 0 3px 2px rgb(14 118 168/ 50%); 167 | } 168 | 169 | .select select.is-invalid { 170 | border-color: red; 171 | transition: 0.3s; 172 | } 173 | 174 | .rest-btn { 175 | margin-top: 1.5rem; 176 | border: 1px solid; 177 | border-color: #3b0062; 178 | background: rgba(59, 0, 98, 0.1); 179 | color: #3b0062; 180 | transition: 0.2s; 181 | } 182 | 183 | .rest-btn:hover { 184 | background: rgba(59, 0, 98, 0.2); 185 | } 186 | 187 | .error { 188 | width: 100%; 189 | color: red; 190 | color: #ff9700; 191 | font-size: 14px; 192 | font-weight: bold; 193 | margin-bottom: 5px; 194 | } 195 | 196 | .footer-link:hover { 197 | cursor: pointer; 198 | text-decoration: underline; 199 | } 200 | 201 | .control label#is-invalid { 202 | cursor: pointer; 203 | display: inline-flex; 204 | font-size: 1rem; 205 | height: 2.7rem; 206 | justify-content: flex-start; 207 | line-height: normal; 208 | padding: 1rem; 209 | align-items: center; 210 | background-color: #fff; 211 | width: 100%; 212 | border-radius: 4px; 213 | box-shadow: 0 1px 0 rgb(255 255 255 / 50%), 0 1px 0 rgb(0 0 0 / 7%) inset; 214 | border: 1px solid #a6a6a6; 215 | border-top-color: #949494; 216 | transition: 0.4s; 217 | border-color: red; 218 | background: #ffc2c2; 219 | box-shadow: none; 220 | transition: 0.3s; 221 | } 222 | 223 | #custom-button { 224 | width: 100%; 225 | display: block; 226 | cursor: pointer; 227 | padding: 7px; 228 | border: 1px solid; 229 | background: #f0c14b; 230 | border-color: #a88734 #9c7e31 #846a29; 231 | color: #111; 232 | border-radius: 3px; 233 | cursor: pointer; 234 | text-align: center; 235 | transition: 0.4s; 236 | } 237 | 238 | #custom-button:hover { 239 | background: #f4d078; 240 | background: -webkit-linear-gradient(top, #f7dfa5, #f0c14b); 241 | background: linear-gradient(to bottom, #f7dfa5, #f0c14b); 242 | } 243 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es2021": true, 6 | "node": true, 7 | "jest": true 8 | }, 9 | "extends": [ 10 | "eslint:recommended", 11 | "plugin:react/recommended", 12 | "plugin:@typescript-eslint/recommended", 13 | "airbnb", 14 | "plugin:import/errors", 15 | "plugin:import/warnings", 16 | "plugin:import/typescript", 17 | "prettier" 18 | ], 19 | "parser": "@typescript-eslint/parser", 20 | "parserOptions": { 21 | "ecmaFeatures": { 22 | "jsx": true 23 | }, 24 | "ecmaVersion": 2021, 25 | "sourceType": "module" 26 | }, 27 | "plugins": [ 28 | "react", 29 | "@typescript-eslint", 30 | "simple-import-sort", 31 | "import", 32 | "jsx-a11y", 33 | "eslint-plugin-react", 34 | "tailwindcss", 35 | "testing-library", 36 | "prettier" 37 | ], 38 | "rules": { 39 | "prettier/prettier": ["error"], 40 | 41 | // Based Rule 42 | "jsx-a11y/label-has-associated-control": "off", 43 | "import/no-cycle": "off", 44 | "consistent-return": "off", 45 | "default-param-last": "off", 46 | "import/extensions": [ 47 | "error", 48 | "ignorePackages", 49 | { 50 | "js": "never", 51 | "mjs": "never", 52 | "jsx": "never", 53 | "ts": "never", 54 | "tsx": "never" 55 | } 56 | ], 57 | "import/no-extraneous-dependencies": [ 58 | "error", 59 | { 60 | "devDependencies": false, 61 | "optionalDependencies": false, 62 | "peerDependencies": false 63 | } 64 | ], 65 | "import/prefer-default-export": "off", 66 | "import/no-unresolved": [ 67 | 0, 68 | { 69 | "commonjs": true, 70 | "amd": true 71 | } 72 | ], 73 | "import/named": 2, 74 | "import/namespace": 2, 75 | "import/default": 2, 76 | "import/export": 2, 77 | 78 | "import/first": "error", 79 | "import/newline-after-import": "error", 80 | "import/no-duplicates": "error", 81 | "simple-import-sort/imports": [ 82 | "error", 83 | { 84 | "groups": [ 85 | // Node.js builtins 86 | [ 87 | "^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)" 88 | ], 89 | // Packages. `react` related packages come first 90 | ["^react", "^@?\\w"], 91 | // Side effect imports 92 | ["^\\u0000"], 93 | // Internal packages 94 | ["^(src|internals)(/.*|$)"], 95 | // Parent imports. Put `..` last 96 | ["^\\.\\.(?!/?$)", "^\\.\\./?$"], 97 | // Other relative imports. Put same-folder imports and `.` last 98 | ["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"], 99 | // Style imports 100 | ["^.+\\.s?css$"] 101 | ] 102 | } 103 | ], 104 | "simple-import-sort/exports": "error", 105 | "tailwindcss/classnames-order": [ 106 | "warn", 107 | { 108 | "officialSorting": true 109 | } 110 | ], 111 | "no-shadow": "off", 112 | "no-console": "warn", 113 | "no-nested-ternary": 0, 114 | "no-underscore-dangle": 0, 115 | "no-use-before-define": 0, 116 | "@typescript-eslint/no-unused-vars": [ 117 | "warn", 118 | { 119 | "argsIgnorePattern": "^_", 120 | "varsIgnorePattern": "^_" 121 | } 122 | ], 123 | "@typescript-eslint/camelcase": "off", 124 | "@typescript-eslint/no-use-before-define": "warn", 125 | "@typescript-eslint/no-explicit-any": "off", 126 | "jsx-a11y/control-has-associated-label": "off", 127 | 128 | // Next JS Based Rules 129 | // Next.js overrides 130 | "jsx-a11y/click-events-have-key-events": "off", 131 | "jsx-a11y/no-static-element-interactions": "off", 132 | "jsx-a11y/href-no-hash": 0, 133 | "jsx-a11y/anchor-is-valid": [ 134 | "error", 135 | { 136 | "components": ["Link"], 137 | "specialLink": ["hrefLeft", "hrefRight"], 138 | "aspects": ["invalidHref", "preferButton"] 139 | } 140 | ], 141 | "arrow-parens": ["error", "always"], 142 | "quotes": [ 143 | "error", 144 | "single", 145 | { 146 | "avoidEscape": true, 147 | "allowTemplateLiterals": false 148 | } 149 | ], 150 | 151 | "require-jsdoc": "off", 152 | "react/react-in-jsx-scope": "off", 153 | 154 | // React Based Rules 155 | "react/jsx-filename-extension": [ 156 | "error", 157 | { 158 | "extensions": [".js", ".jsx", ".tsx"] 159 | } 160 | ], 161 | "react/jsx-props-no-spreading": "off" 162 | }, 163 | "globals": { 164 | "React": "writable" 165 | }, 166 | "settings": { 167 | "import/parsers": { 168 | "@typescript-eslint/parser": [".ts", ".tsx"] 169 | }, 170 | "import/resolver": { 171 | "node": { 172 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 173 | }, 174 | "alias": { 175 | "map": [ 176 | ["components", "./src/components"], 177 | ["page-components", "./src/page-components"], 178 | ["utils", "./src/utils"], 179 | ["services", "./src/services"], 180 | ["layouts", "./src/components/layouts"], 181 | ["tests", "./src/tests"], 182 | ["types", "./src/types"], 183 | ["global-states", "./src/global-states"], 184 | ["constants", "./src/constants"], 185 | ["contexts", "./src/contexts"] 186 | ], 187 | "extensions": [".ts", ".js", ".jsx", ".tsx", ".json"] 188 | }, 189 | "typescript": { 190 | "alwaysTryTypes": true // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist` 191 | } 192 | }, 193 | "import/extensions": [".js", ".mjs", ".jsx", ".ts", ".tsx"], 194 | "react": { 195 | // Tells eslint-plugin-react to automatically detect the version of React to use 196 | "version": "detect" 197 | // "version": "latest" 198 | } 199 | }, 200 | "overrides": [ 201 | // Only uses Testing Library lint rules in test files 202 | { 203 | "files": ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"], 204 | "extends": ["plugin:testing-library/react"] 205 | }, 206 | { 207 | "files": [".eslintrc.json", "*.config.js"], 208 | "parserOptions": { 209 | "sourceType": "script" 210 | }, 211 | "env": { 212 | "node": true 213 | } 214 | } 215 | ] 216 | } 217 | -------------------------------------------------------------------------------- /src/types/auth/actionsType.tsx: -------------------------------------------------------------------------------- 1 | import { Action } from 'redux'; 2 | 3 | import { AuthResponseType } from './_prototype'; 4 | 5 | export enum AuthenticationActionType { 6 | AUTH_LOGIN_LOADING = 'AUTH_LOGIN_LOADING', 7 | AUTH_LOGIN_SUCCESS = 'AUTH_LOGIN_SUCCESS', 8 | AUTH_LOGIN_FAILED = 'AUTH_LOGIN_FAILED', 9 | AUTH_LOGIN_REST = 'AUTH_LOGIN_REST', 10 | AUTH_FORGET_PASSWORD_LOADING = 'AUTH_FORGET_PASSWORD_LOADING', 11 | AUTH_FORGET_PASSWORD_SUCCESS = 'AUTH_FORGET_PASSWORD_SUCCESS', 12 | AUTH_FORGET_PASSWORD_FAILED = 'AUTH_FORGET_PASSWORD_FAILED', 13 | AUTH_FORGET_PASSWORD_REST = 'AUTH_FORGET_PASSWORD_REST', 14 | AUTH_REST_PASSWORD_LOADING = 'AUTH_REST_PASSWORD_LOADING', 15 | AUTH_REST_PASSWORD_SUCCESS = 'AUTH_REST_PASSWORD_SUCCESS', 16 | AUTH_REST_PASSWORD_FAILED = 'AUTH_REST_PASSWORD_FAILED', 17 | AUTH_REST_PASSWORD_REST = 'AUTH_REST_PASSWORD_REST', 18 | AUTH_CONFIRM_EMAIL_LOADING = 'AUTH_CONFIRM_EMAIL_LOADING', 19 | AUTH_CONFIRM_EMAIL_SUCCESS = 'AUTH_CONFIRM_EMAIL_SUCCESS', 20 | AUTH_CONFIRM_EMAIL_FAILED = 'AUTH_CONFIRM_EMAIL_FAILED', 21 | AUTH_CONFIRM_EMAIL_REST = 'AUTH_CONFIRM_EMAIL_REST', 22 | AUTH_SIGINUP_LOADING = 'AUTH_SIGINUP_LOADING ', 23 | AUTH_SIGINUP_SUCCESS = 'AUTH_SIGINUP_SUCCESS', 24 | AUTH_SIGINUP_FAILED = 'AUTH_SIGINUP_FAILED', 25 | AUTH_SIGINUP_REST = 'AUTH_SIGINUP_REST', 26 | IS_AUTHENTICATED_SUCCESS = 'IS_AUTHENTICATED_SUCCESS', 27 | REMOVE_AUTHENTICATED_USER = 'REMOVE_AUTHENTICATED_USER', 28 | IS_ADMIN = 'IS_ADMIN', 29 | HYDRATE = 'HYDRATE', 30 | AUTH_UPDATE_PROFILE_LOADING = 'AUTH_UPDATE_PROFILE_LOADING ', 31 | AUTH_UPDATE_PROFILE_SUCCESS = 'AUTH_UPDATE_PROFILE_SUCCESS', 32 | AUTH_UPDATE_PROFILE_FAILED = 'AUTH_UPDATE_PROFILE_FAILED', 33 | AUTH_UPDATE_PROFILE_REST = 'AUTH_UPDATE_PROFILE_REST', 34 | } 35 | 36 | export interface actionAuthLoginIsPending extends Action { 37 | type: AuthenticationActionType.AUTH_LOGIN_LOADING; 38 | } 39 | 40 | export interface actionAuthLoginIsSuccess extends Action { 41 | type: AuthenticationActionType.AUTH_LOGIN_SUCCESS; 42 | payload: AuthResponseType; 43 | } 44 | 45 | export interface actionAuthLoginIsError extends Action { 46 | type: AuthenticationActionType.AUTH_LOGIN_FAILED; 47 | payload: AuthResponseType; 48 | } 49 | 50 | export interface actionAuthLoginIsRest extends Action { 51 | type: AuthenticationActionType.AUTH_LOGIN_REST; 52 | } 53 | 54 | export interface actionAuthUpdateProfileIsPending extends Action { 55 | type: AuthenticationActionType.AUTH_UPDATE_PROFILE_LOADING; 56 | } 57 | 58 | export interface actionAuthUpdateProfileIsSuccess extends Action { 59 | type: AuthenticationActionType.AUTH_UPDATE_PROFILE_SUCCESS; 60 | payload: AuthResponseType; 61 | } 62 | 63 | export interface actionAuthUpdateProfileIsError extends Action { 64 | type: AuthenticationActionType.AUTH_UPDATE_PROFILE_FAILED; 65 | payload: AuthResponseType; 66 | } 67 | 68 | export interface actionAuthUpdateProfileIsRest extends Action { 69 | type: AuthenticationActionType.AUTH_UPDATE_PROFILE_REST; 70 | } 71 | export interface actionForgotPasswordIsPending extends Action { 72 | type: AuthenticationActionType.AUTH_FORGET_PASSWORD_LOADING; 73 | } 74 | 75 | export interface actionForgotPasswordIsSuccess extends Action { 76 | type: AuthenticationActionType.AUTH_FORGET_PASSWORD_SUCCESS; 77 | payload: AuthResponseType; 78 | } 79 | 80 | export interface actionForgotPasswordIsError extends Action { 81 | type: AuthenticationActionType.AUTH_FORGET_PASSWORD_FAILED; 82 | payload: AuthResponseType; 83 | } 84 | 85 | export interface actionForgotPasswordIsRest extends Action { 86 | type: AuthenticationActionType.AUTH_FORGET_PASSWORD_REST; 87 | } 88 | 89 | export interface actionRestPasswordIsPending extends Action { 90 | type: AuthenticationActionType.AUTH_REST_PASSWORD_LOADING; 91 | } 92 | 93 | export interface actionRestPasswordIsSuccess extends Action { 94 | type: AuthenticationActionType.AUTH_REST_PASSWORD_SUCCESS; 95 | payload: AuthResponseType; 96 | } 97 | 98 | export interface actionRestPasswordIsError extends Action { 99 | type: AuthenticationActionType.AUTH_REST_PASSWORD_FAILED; 100 | payload: AuthResponseType; 101 | } 102 | 103 | export interface actionRestPasswordIsRest extends Action { 104 | type: AuthenticationActionType.AUTH_REST_PASSWORD_REST; 105 | } 106 | 107 | export interface actionConfirmEmailIsPending extends Action { 108 | type: AuthenticationActionType.AUTH_CONFIRM_EMAIL_LOADING; 109 | } 110 | 111 | export interface actionConfirmEmailIsSuccess extends Action { 112 | type: AuthenticationActionType.AUTH_CONFIRM_EMAIL_SUCCESS; 113 | payload: AuthResponseType; 114 | } 115 | 116 | export interface actionConfirmEmailIsError extends Action { 117 | type: AuthenticationActionType.AUTH_CONFIRM_EMAIL_FAILED; 118 | payload: AuthResponseType; 119 | } 120 | 121 | export interface actionConfirmEmailIsRest extends Action { 122 | type: AuthenticationActionType.AUTH_CONFIRM_EMAIL_REST; 123 | } 124 | 125 | export interface actionSignUpIsPending extends Action { 126 | type: AuthenticationActionType.AUTH_SIGINUP_LOADING; 127 | } 128 | 129 | export interface actionSignUpIsSuccess extends Action { 130 | type: AuthenticationActionType.AUTH_SIGINUP_SUCCESS; 131 | payload: AuthResponseType; 132 | } 133 | 134 | export interface actionSignUpIsError extends Action { 135 | type: AuthenticationActionType.AUTH_SIGINUP_FAILED; 136 | payload: AuthResponseType; 137 | } 138 | 139 | export interface actionSignUpIsRest extends Action { 140 | type: AuthenticationActionType.AUTH_SIGINUP_REST; 141 | } 142 | 143 | export interface actionIsAuthenticatedSuccess extends Action { 144 | type: AuthenticationActionType.IS_AUTHENTICATED_SUCCESS; 145 | } 146 | 147 | export interface actionRemoveAuthenticatedUser extends Action { 148 | type: AuthenticationActionType.REMOVE_AUTHENTICATED_USER; 149 | } 150 | 151 | export interface actionIsADmin extends Action { 152 | type: AuthenticationActionType.IS_ADMIN; 153 | payload: boolean | string; 154 | } 155 | 156 | export type AuthenticationAction = 157 | | actionAuthLoginIsPending 158 | | actionAuthLoginIsSuccess 159 | | actionAuthLoginIsError 160 | | actionAuthLoginIsRest 161 | | actionForgotPasswordIsPending 162 | | actionForgotPasswordIsSuccess 163 | | actionForgotPasswordIsError 164 | | actionForgotPasswordIsRest 165 | | actionRestPasswordIsPending 166 | | actionRestPasswordIsSuccess 167 | | actionRestPasswordIsError 168 | | actionRestPasswordIsRest 169 | | actionConfirmEmailIsPending 170 | | actionConfirmEmailIsSuccess 171 | | actionConfirmEmailIsError 172 | | actionConfirmEmailIsRest 173 | | actionSignUpIsPending 174 | | actionSignUpIsSuccess 175 | | actionSignUpIsError 176 | | actionSignUpIsRest 177 | | actionIsAuthenticatedSuccess 178 | | actionIsADmin 179 | | actionRemoveAuthenticatedUser 180 | | actionAuthUpdateProfileIsRest 181 | | actionAuthUpdateProfileIsError 182 | | actionAuthUpdateProfileIsSuccess 183 | | actionAuthUpdateProfileIsPending; 184 | -------------------------------------------------------------------------------- /src/page-components/forget-password-page/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react'; 2 | import { useForm } from 'react-hook-form'; 3 | import { connect } from 'react-redux'; 4 | import { yupResolver } from '@hookform/resolvers/yup'; 5 | import { Alert } from '@mui/material'; 6 | import CircularProgress from '@mui/material/CircularProgress'; 7 | 8 | import { ReducerType } from '@/global-states'; 9 | import { forgetPassword, restforgetPassword } from '@/global-states/actions'; 10 | import LogInComponent from '@/pages/login/index'; 11 | import { 12 | _authPrototypeReducerState as ReducerState, 13 | ForgetPasswordEmailRequestType as forgetPasswordEmailType, 14 | } from '@/types'; 15 | import { forgotPasswordSchemaValidation } from '@/utils'; 16 | 17 | // props from connect mapDispatchToProps 18 | interface MapDispatchProps { 19 | restforgetPassword: () => void; 20 | forgetPassword: (email: forgetPasswordEmailType) => void; 21 | } 22 | 23 | // props from connect mapStateToProps 24 | interface MapStateProps { 25 | authState: ReducerState; 26 | } 27 | 28 | type PropsType = MapDispatchProps & MapStateProps; 29 | 30 | function ForgetPasswordPageComponent({ restforgetPassword, forgetPassword, authState }: PropsType) { 31 | const autoScrollToBottomRef = useRef(null); 32 | const [showAlert, setShowAlert] = useState(false); 33 | const [logIn, setLogIn] = useState(false); 34 | 35 | const { 36 | forgetPasswordIsLoading, 37 | forgetPasswordIsSuccess, 38 | forgetPasswordIsError, 39 | forgetPasswordMessage, 40 | } = authState; 41 | 42 | const { 43 | register, 44 | handleSubmit, 45 | reset, 46 | formState: { errors }, 47 | } = useForm({ 48 | resolver: yupResolver(forgotPasswordSchemaValidation), 49 | }); 50 | 51 | useEffect(() => { 52 | setLogIn(() => false); 53 | restforgetPassword(); 54 | }, []); 55 | 56 | useEffect(() => { 57 | window.scrollTo({ 58 | top: 0, 59 | behavior: 'smooth', 60 | }); 61 | // Auto Scroll functionality 62 | autoScrollToBottomRef?.current?.scrollIntoView({ 63 | behavior: 'smooth', 64 | }); 65 | }, []); 66 | 67 | useEffect(() => { 68 | if (forgetPasswordIsSuccess) { 69 | const timer = setTimeout(() => { 70 | setShowAlert(() => false); 71 | setLogIn(() => true); 72 | }, 9000); 73 | return () => clearTimeout(timer); 74 | } 75 | }, [forgetPasswordIsSuccess]); 76 | 77 | useEffect(() => { 78 | if (forgetPasswordIsSuccess || forgetPasswordIsError) { 79 | setShowAlert(() => true); 80 | if (forgetPasswordIsError) { 81 | const timer = setTimeout(() => { 82 | setShowAlert(() => false); 83 | setLogIn(() => false); 84 | restforgetPassword(); 85 | }, 8000); 86 | return () => clearTimeout(timer); 87 | } 88 | } 89 | }, [forgetPasswordIsSuccess, forgetPasswordIsError]); 90 | 91 | if (logIn) { 92 | return ; 93 | } 94 | 95 | const onSubmit = (data: forgetPasswordEmailType) => { 96 | const _data = { email: data.email }; 97 | forgetPassword(_data); 98 | 99 | reset(); 100 | }; 101 | 102 | return ( 103 |
104 |
105 |
111 | {forgetPasswordIsLoading && ( 112 |
113 | 114 |
115 | )} 116 | 117 | {showAlert && (forgetPasswordIsError || forgetPasswordIsSuccess) && ( 118 | setShowAlert(false)} 122 | > 123 | {forgetPasswordMessage} 124 | 125 | )} 126 |
127 |
128 |

129 | Forgot your password?{' '} 130 |

131 | 132 |

133 | Don't fret! Just type in your email and we will send you a code to reset your 134 | password 135 |

136 |
137 | 138 |
139 |
140 | {!errors.email && } 141 | 142 | {errors.email &&

{errors.email.message}

} 143 | 144 | 150 |
151 |
152 | 158 | 165 |
166 |
167 |
168 |
169 | 170 |
171 | Conditions of Use 172 | Privacy Notice 173 | Help 174 |
175 |
176 | 177 | © 2004-2021, saddamarbaa.com, Inc. or its affiliates 178 | 179 |
180 |
181 |
182 | ); 183 | } 184 | 185 | const mapStateToProps = (state: ReducerType) => ({ 186 | authState: state.auth, 187 | }); 188 | 189 | const mapDispatchToProps = { 190 | restforgetPassword, 191 | forgetPassword, 192 | }; 193 | 194 | export default connect(mapStateToProps, mapDispatchToProps)(ForgetPasswordPageComponent); 195 | -------------------------------------------------------------------------------- /src/global-states/reducers/admin/userReducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | _usersPrototypeReducerState as UsersReducerState, 3 | UsersAction, 4 | UsersActionType, 5 | } from 'types'; 6 | 7 | const initialState: UsersReducerState = { 8 | list: {}, 9 | users: [], 10 | listIsLoading: false, 11 | listIsSuccess: false, 12 | listIsError: false, 13 | listMessage: '', 14 | totalDocs: 0, 15 | lastPage: 0, 16 | 17 | emailVerificationLinkToken: '', 18 | token: '', 19 | accessToken: '', 20 | refreshToken: '', 21 | postUserIsPending: false, 22 | postUserIsSuccess: false, 23 | postUserIsError: false, 24 | postUserMessage: '', 25 | 26 | deleteUserIsPending: false, 27 | deleteUserIsSuccess: false, 28 | deleteUserIsError: false, 29 | deleteUserMessage: '', 30 | 31 | individualUser: null, 32 | getIndividualUserIsPending: false, 33 | getIndividualUserIsSuccess: false, 34 | getIndividualUserIsError: false, 35 | getIndividualUserIsMessage: '', 36 | 37 | updatedUser: null, 38 | updateUserIsPending: false, 39 | updateUserIsSuccess: false, 40 | updateUserIsError: false, 41 | updateUserMessage: '', 42 | }; 43 | 44 | export function userReducer(state = initialState, action: UsersAction) { 45 | switch (action?.type) { 46 | case UsersActionType.GET_USER_LIST_LOADING: 47 | return { 48 | ...state, 49 | listIsLoading: true, 50 | listIsSuccess: false, 51 | listIsError: false, 52 | listMessage: 'loading', 53 | }; 54 | case UsersActionType.GET_USER_LIST_SUCCESS: 55 | return { 56 | ...state, 57 | users: action.payload.data.users || [], 58 | totalDocs: action.payload.data.totalDocs || 0, 59 | lastPage: action.payload.data.lastPage || 0, 60 | list: action.payload || {}, 61 | listIsLoading: false, 62 | listIsSuccess: true, 63 | listIsError: false, 64 | listMessage: action.payload.message || 'Success', 65 | }; 66 | case UsersActionType.GET_USER_LIST_FAILED: 67 | return { 68 | ...state, 69 | users: [], 70 | list: {}, 71 | listIsLoading: false, 72 | listIsSuccess: false, 73 | listIsError: true, 74 | totalDocs: 0, 75 | listMessage: action.payload.message || action.payload.error || 'Error', 76 | }; 77 | case UsersActionType.GET_USER_LIST_REST: 78 | return { 79 | ...state, 80 | users: [], 81 | list: {}, 82 | listIsLoading: false, 83 | listIsSuccess: false, 84 | listIsError: false, 85 | listMessage: '', 86 | totalDocs: 0, 87 | }; 88 | case UsersActionType.GET_INDIVIDUAL_USER_LOADING: 89 | return { 90 | ...state, 91 | getIndividualUserIsPending: true, 92 | getIndividualUserIsSuccess: false, 93 | getIndividualUserIsError: false, 94 | getIndividualUserIsMessage: 'loading', 95 | }; 96 | case UsersActionType.GET_INDIVIDUAL_USER_SUCCESS: 97 | return { 98 | ...state, 99 | individualUser: action.payload.data.user || null, 100 | getIndividualUserIsPending: false, 101 | getIndividualUserIsSuccess: true, 102 | getIndividualUserIsError: false, 103 | getIndividualUserIsMessage: action.payload.message || action.payload.error || 'Error', 104 | }; 105 | case UsersActionType.GET_INDIVIDUAL_USER_FAILED: 106 | return { 107 | ...state, 108 | individualUser: null, 109 | getIndividualUserIsPending: false, 110 | getIndividualUserIsSuccess: false, 111 | getIndividualUserIsError: true, 112 | getIndividualUserIsMessage: action.payload.message || action.payload.error || 'Error', 113 | }; 114 | case UsersActionType.GET_INDIVIDUAL_USER_REST: 115 | return { 116 | ...state, 117 | individualUser: null, 118 | getIndividualUserIsPending: false, 119 | getIndividualUserIsSuccess: false, 120 | getIndividualUserIsError: false, 121 | getIndividualUserIsMessage: '', 122 | }; 123 | case UsersActionType.POST_USER_LOADING: 124 | return { 125 | ...state, 126 | postUserIsPending: true, 127 | postUserIsSuccess: false, 128 | postUserIsError: false, 129 | postUserMessage: 'PENDING', 130 | }; 131 | case UsersActionType.POST_USER_SUCCESS: 132 | return { 133 | ...state, 134 | emailVerificationLinkToken: action.payload.data.user.emailVerificationLinkToken || '', 135 | token: action.payload.data.user.token || '', 136 | accessToken: action.payload.data.user.accessToken || '', 137 | refreshToken: action.payload.data.user.refreshToken || '', 138 | postUserIsPending: false, 139 | postUserIsSuccess: true, 140 | postUserIsError: false, 141 | postUserMessage: action.payload.message || 'Success', 142 | }; 143 | case UsersActionType.POST_USER_FAILED: 144 | return { 145 | ...state, 146 | emailVerificationLinkToken: '', 147 | token: '', 148 | accessToken: '', 149 | refreshToken: '', 150 | postUserIsPending: false, 151 | postUserIsSuccess: false, 152 | postUserIsError: true, 153 | postUserMessage: action.payload.message || action.payload.error || 'Error', 154 | }; 155 | case UsersActionType.POST_USER_REST: 156 | return { 157 | ...state, 158 | emailVerificationLinkToken: '', 159 | token: '', 160 | accessToken: '', 161 | refreshToken: '', 162 | postUserIsPending: false, 163 | postUserIsSuccess: false, 164 | postUserIsError: false, 165 | postUserMessage: '', 166 | }; 167 | case UsersActionType.UPDATE_USER_LOADING: 168 | return { 169 | ...state, 170 | updateUserIsPending: true, 171 | updateUserIsSuccess: false, 172 | updateUserIsError: false, 173 | updateUserMessage: 'PENDING', 174 | }; 175 | case UsersActionType.UPDATE_USER_SUCCESS: 176 | return { 177 | ...state, 178 | updatedUser: action.payload.data.user || null, 179 | updateUserIsPending: false, 180 | updateUserIsSuccess: true, 181 | updateUserIsError: false, 182 | updateUserMessage: action.payload.message || 'Success', 183 | }; 184 | case UsersActionType.UPDATE_USER_FAILED: 185 | return { 186 | ...state, 187 | updatedUser: null, 188 | updateUserIsPending: false, 189 | updateUserIsSuccess: false, 190 | updateUserIsError: true, 191 | updateUserMessage: action.payload.message || action.payload.error || 'Error', 192 | }; 193 | case UsersActionType.UPDATE_USER_REST: 194 | return { 195 | ...state, 196 | updatedUser: null, 197 | updateUserIsPending: false, 198 | updateUserIsSuccess: false, 199 | updateUserIsError: false, 200 | updateUserMessage: '', 201 | }; 202 | case UsersActionType.DELETE_USER_LOADING: 203 | return { 204 | ...state, 205 | deleteUserIsPending: true, 206 | deleteUserIsSuccess: false, 207 | deleteUserIsError: false, 208 | deleteUserMessage: 'PENDING', 209 | }; 210 | case UsersActionType.DELETE_USER_SUCCESS: 211 | return { 212 | ...state, 213 | deleteUserIsPending: false, 214 | deleteUserIsSuccess: true, 215 | deleteUserIsError: false, 216 | deleteUserMessage: action.payload.message || 'Success', 217 | }; 218 | case UsersActionType.DELETE_USER_FAILED: 219 | return { 220 | ...state, 221 | deleteUserIsPending: false, 222 | deleteUserIsSuccess: false, 223 | deleteUserIsError: true, 224 | deleteUserMessage: action.payload.message || action.payload.error || 'Error', 225 | }; 226 | case UsersActionType.DELETE_USER_REST: 227 | return { 228 | ...state, 229 | deleteUserIsPending: false, 230 | deleteUserIsSuccess: false, 231 | deleteUserIsError: false, 232 | deleteUserMessage: '', 233 | }; 234 | 235 | default: 236 | return state; 237 | } 238 | } 239 | 240 | export default userReducer; 241 | -------------------------------------------------------------------------------- /src/page-components/login-page/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react'; 2 | import { useForm } from 'react-hook-form'; 3 | import { connect } from 'react-redux'; 4 | import { yupResolver } from '@hookform/resolvers/yup'; 5 | import { Alert } from '@mui/material'; 6 | import CircularProgress from '@mui/material/CircularProgress'; 7 | import { useRouter } from 'next/router'; 8 | 9 | import { LogIn, ReducerType, restLoginState } from '@/global-states'; 10 | import SignUpComponent from '@/page-components/signup-page'; 11 | import ForgetPasswordComponent from '@/pages/forget-password'; 12 | import { _authPrototypeReducerState as ReducerState, LoginRequestType as LoginType } from '@/types'; 13 | import { LoginSchemaValidation } from '@/utils'; 14 | 15 | interface MapDispatchProps { 16 | restLoginState: () => void; 17 | LogIn: (data: LoginType) => void; 18 | } 19 | 20 | interface MapStateProps { 21 | authState: ReducerState; 22 | } 23 | 24 | type PropsType = MapDispatchProps & MapStateProps; 25 | 26 | function LogInPageComponent({ LogIn, restLoginState, authState }: PropsType) { 27 | const autoScrollToBottomRef = useRef(null); 28 | const router = useRouter(); 29 | const [signIn, setSignIn] = useState(false); 30 | const [forgetPassword, setForgetPassword] = useState(false); 31 | const [showAlert, setShowAlert] = useState(false); 32 | 33 | const { loginUserIsLoading, loginUserIsSuccess, loginUserIsError, loginMessage } = authState; 34 | 35 | const { 36 | register, 37 | handleSubmit, 38 | reset, 39 | formState: { errors }, 40 | } = useForm({ 41 | resolver: yupResolver(LoginSchemaValidation), 42 | }); 43 | 44 | // Auto Scroll functionality 45 | useEffect(() => { 46 | window.scrollTo({ 47 | top: 0, 48 | behavior: 'smooth', 49 | }); 50 | // Auto Scroll functionality 51 | autoScrollToBottomRef?.current?.scrollIntoView({ 52 | behavior: 'smooth', 53 | }); 54 | }, []); 55 | 56 | useEffect(() => { 57 | restLoginState(); 58 | }, []); 59 | 60 | useEffect(() => { 61 | if (loginUserIsSuccess) { 62 | reset(); 63 | router.push('/'); 64 | } 65 | 66 | if (loginUserIsSuccess || loginUserIsError) { 67 | setShowAlert(() => true); 68 | let timer: any; 69 | if (loginUserIsSuccess) { 70 | timer = setTimeout(() => { 71 | setShowAlert(() => false); 72 | restLoginState(); 73 | }, 5000); 74 | } else { 75 | timer = setTimeout(() => { 76 | setShowAlert(() => false); 77 | restLoginState(); 78 | }, 9000); 79 | } 80 | 81 | return () => clearTimeout(timer); 82 | } 83 | }, [loginUserIsSuccess, loginUserIsError]); 84 | 85 | useEffect(() => { 86 | const redirectToSignUp = () => { 87 | if (signIn) { 88 | return ; 89 | } 90 | }; 91 | 92 | redirectToSignUp(); 93 | }, [signIn]); 94 | 95 | useEffect(() => { 96 | const redirectToForgetPassword = () => { 97 | if (forgetPassword) { 98 | return ; 99 | } 100 | }; 101 | 102 | redirectToForgetPassword(); 103 | }, [forgetPassword]); 104 | 105 | if (signIn) { 106 | return ; 107 | } 108 | 109 | if (forgetPassword) { 110 | return ; 111 | } 112 | 113 | const onSubmit = (data: LoginType) => { 114 | // console.log(JSON.stringify(data, null, 2)); 115 | 116 | const finalData = { 117 | email: data.email, 118 | password: data.password, 119 | }; 120 | 121 | LogIn(finalData); 122 | }; 123 | 124 | return ( 125 |
126 |
127 |
133 | {loginUserIsLoading && ( 134 |
135 | 136 |
137 | )} 138 | {showAlert && (loginUserIsSuccess || loginUserIsError) && ( 139 |
143 | restLoginState()} 147 | > 148 | {loginMessage} 149 | 150 |
151 | )} 152 |
153 |
154 |
155 |
156 | {!errors.email && } 157 | {errors.email &&

{errors.email.message}

} 158 | 164 |
165 |
166 | {!errors.password && ( 167 | 168 | )} 169 |

{errors.password?.message}

170 | 176 |
177 | 178 |
179 | 185 |
186 |
187 | 188 |

Or

189 | 190 |
191 | 198 | 205 |
206 |
207 |
208 |
209 | 210 |
211 | Conditions of Use 212 | Privacy Notice 213 | Help 214 |
215 |
216 | 217 | © 2004-2021, saddamarbaa.com, Inc. or its affiliates 218 | 219 |
220 |
221 |
222 | ); 223 | } 224 | 225 | const mapStateToProps = (state: ReducerType) => ({ 226 | authState: state.auth, 227 | }); 228 | 229 | const mapDispatchToProps = { 230 | LogIn, 231 | restLoginState, 232 | }; 233 | 234 | export default connect(mapStateToProps, mapDispatchToProps)(LogInPageComponent); 235 | -------------------------------------------------------------------------------- /src/page-components/change-password-page/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react'; 2 | import { useForm } from 'react-hook-form'; 3 | import { connect } from 'react-redux'; 4 | import { yupResolver } from '@hookform/resolvers/yup'; 5 | import { Alert } from '@mui/material'; 6 | import CircularProgress from '@mui/material/CircularProgress'; 7 | import { useRouter } from 'next/router'; 8 | 9 | import { ReducerType } from '@/global-states'; 10 | import { restPassword, restPasswordRest } from '@/global-states/actions'; 11 | import { 12 | _authPrototypeReducerState as ReducerState, 13 | ResetPasswordRequestType as ResetPasswordType, 14 | } from '@/types'; 15 | import { updatePasswordSchemaValidation } from '@/utils'; 16 | 17 | interface OwnProps { 18 | userId: string | string[] | undefined; 19 | token: string | string[] | undefined; 20 | } 21 | 22 | interface MapDispatchProps { 23 | restPasswordRest: () => void; 24 | restPassword: (email: ResetPasswordType) => void; 25 | } 26 | 27 | interface MapStateProps { 28 | authState: ReducerState; 29 | } 30 | 31 | type PropsType = OwnProps & MapDispatchProps & MapStateProps; 32 | 33 | function ChangePasswordPageComponent({ 34 | restPassword, 35 | restPasswordRest, 36 | authState, 37 | userId, 38 | token, 39 | }: PropsType) { 40 | const router = useRouter(); 41 | const autoScrollToBottomRef = useRef(null); 42 | const [showAlert, setShowAlert] = useState(false); 43 | const [logIn, setLogIn] = useState(false); 44 | const { restPasswordIsLoading, restPasswordIsSuccess, restPasswordIsError, restPasswordMessage } = 45 | authState; 46 | 47 | const { 48 | register, 49 | handleSubmit, 50 | reset, 51 | formState: { errors }, 52 | } = useForm({ 53 | resolver: yupResolver(updatePasswordSchemaValidation), 54 | }); 55 | 56 | useEffect(() => { 57 | setLogIn(() => false); 58 | window.scrollTo({ 59 | top: 0, 60 | behavior: 'smooth', 61 | }); 62 | // Auto Scroll functionality 63 | autoScrollToBottomRef?.current?.scrollIntoView({ 64 | behavior: 'smooth', 65 | }); 66 | 67 | restPasswordRest(); 68 | }, []); 69 | 70 | useEffect(() => { 71 | if (restPasswordIsSuccess) { 72 | const timer = setTimeout(() => { 73 | reset(); 74 | setShowAlert(() => false); 75 | setLogIn(() => true); 76 | }, 5000); 77 | return () => clearTimeout(timer); 78 | } 79 | }, [restPasswordIsSuccess]); 80 | 81 | useEffect(() => { 82 | if (restPasswordIsSuccess || restPasswordIsError) { 83 | setShowAlert(() => true); 84 | if (restPasswordIsError) { 85 | const timer = setTimeout(() => { 86 | setShowAlert(() => false); 87 | setLogIn(() => false); 88 | restPasswordRest(); 89 | }, 8000); 90 | return () => clearTimeout(timer); 91 | } 92 | } 93 | }, [restPasswordIsError, restPasswordIsSuccess]); 94 | 95 | if (logIn) { 96 | router.replace('/login'); 97 | } 98 | 99 | const onSubmit = (data: ResetPasswordType) => { 100 | if (data.email && data.password && data.confirmPassword && userId && token) { 101 | const finalData = { 102 | email: data.email, 103 | password: data.password, 104 | confirmPassword: data.confirmPassword, 105 | userId, 106 | token, 107 | }; 108 | 109 | restPassword(finalData); 110 | } 111 | }; 112 | 113 | return ( 114 |
115 |
116 |
122 | {restPasswordIsLoading && ( 123 |
124 | 125 |
126 | )} 127 | 128 | {showAlert && (restPasswordIsError || restPasswordIsSuccess) && ( 129 | setShowAlert(false)} 133 | > 134 | {restPasswordMessage} 135 | 136 | )} 137 | 138 |
139 |
140 |

141 | Reset your password 142 |

143 |
144 | 145 |
146 |
147 | {errors.email &&

{errors.email?.message}

} 148 | 155 |
156 | 157 |
158 |

{errors.password?.message}

159 | 166 |
167 | 168 |
169 |

{errors.confirmPassword?.message}

170 | 177 |
178 | 179 |

180 | {errors.acceptTerms?.message} 181 |

182 |

183 | 189 | By clicking Sign Up, you agree to the 190 | (saddams.com) 191 | 192 | User Agreements,{' '} 193 | 194 | 195 | Privacy Policy,{' '} 196 | 197 | and{' '} 198 | 199 | {' '} 200 | Cookie Policy. 201 | 202 | . You may receive SMS notifications from me and can opt out at any time. 203 |

204 | 205 |
206 | 212 |
213 |
214 |
215 |
216 | 217 |
218 | Conditions of Use 219 | Privacy Notice 220 | Help 221 |
222 |
223 | 224 | © 2004-2021, saddamarbaa.com, Inc. or its affiliates 225 | 226 |
227 |
228 |
229 | ); 230 | } 231 | 232 | const mapStateToProps = (state: ReducerType) => ({ 233 | authState: state.auth, 234 | }); 235 | 236 | const mapDispatchToProps = { 237 | restPassword, 238 | restPasswordRest, 239 | }; 240 | 241 | export default connect(mapStateToProps, mapDispatchToProps)(ChangePasswordPageComponent); 242 | -------------------------------------------------------------------------------- /src/components/layout/header/navbar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import MenuIcon from '@mui/icons-material/Menu'; 4 | import SearchIcon from '@mui/icons-material/Search'; 5 | import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; 6 | import Avatar from '@mui/material/Avatar'; 7 | import Box from '@mui/material/Box'; 8 | import IconButton from '@mui/material/IconButton'; 9 | import Menu from '@mui/material/Menu'; 10 | import MenuItem from '@mui/material/MenuItem'; 11 | import Tooltip from '@mui/material/Tooltip'; 12 | import Typography from '@mui/material/Typography'; 13 | import Link from 'next/link'; 14 | import { useRouter } from 'next/router'; 15 | 16 | import { productCategory } from '@/constants'; 17 | import { 18 | getCart, 19 | handleProductSearchTerm, 20 | handleSelectedCategory, 21 | ReducerType, 22 | removeAuthenticatedUser, 23 | restGetCart, 24 | } from '@/global-states'; 25 | import { 26 | _authPrototypeReducerState as ReducerState, 27 | _productPrototypeReducerState as ReducerProductState, 28 | } from '@/types'; 29 | import { removedUserFromLocalStorage } from '@/utils'; 30 | 31 | interface MapDispatchProps { 32 | removeAuthenticatedUser: () => void; 33 | handleProductSearchTerm: (payload: string) => void; 34 | handleSelectedCategory: (payload: string) => void; 35 | getCart: () => void; 36 | restGetCart: () => void; 37 | } 38 | 39 | interface MapStateProps { 40 | authState: ReducerState; 41 | listState: ReducerProductState; 42 | } 43 | 44 | type PropsType = MapDispatchProps & MapStateProps; 45 | 46 | function Navbar({ 47 | authState, 48 | listState, 49 | removeAuthenticatedUser, 50 | handleProductSearchTerm, 51 | handleSelectedCategory, 52 | getCart, 53 | restGetCart, 54 | }: PropsType) { 55 | const { 56 | productSearchTerm, 57 | cart, 58 | // getCartIsPending, 59 | // getCartIsSuccess, 60 | // getCartIsError, 61 | // getCartMessage, 62 | AddToCartIsSuccess, 63 | clearCartIsSuccess, 64 | addOrderIsSuccess, 65 | } = listState; 66 | 67 | const { isAuthenticated, isADmin, loginUser } = authState; 68 | 69 | const [anchorElUser, setAnchorElUser] = React.useState(null); 70 | const router = useRouter(); 71 | 72 | const signedOutHandler = () => { 73 | removeAuthenticatedUser(); 74 | removedUserFromLocalStorage(); 75 | }; 76 | 77 | const userProfileHandler = () => { 78 | router.push(`/profile/${loginUser?._id}`); 79 | }; 80 | 81 | const settings = [ 82 | { 83 | value: 'Update Profile', 84 | handleClick: userProfileHandler, 85 | }, 86 | { value: 'Logout', handleClick: signedOutHandler }, 87 | ]; 88 | 89 | const handleOpenUserMenu = (event: React.MouseEvent) => { 90 | setAnchorElUser(event.currentTarget); 91 | }; 92 | 93 | const handleCloseUserMenu = () => { 94 | setAnchorElUser(null); 95 | }; 96 | 97 | const getTotalItems = () => cart?.reduce((acc, obj) => acc + obj.quantity, 0); 98 | 99 | useEffect(() => { 100 | restGetCart(); 101 | }, []); 102 | 103 | useEffect(() => { 104 | getCart(); 105 | }, [AddToCartIsSuccess, clearCartIsSuccess, addOrderIsSuccess]); 106 | 107 | return ( 108 |
109 | 239 |
240 | ); 241 | } 242 | 243 | const mapStateToProps = (state: ReducerType) => ({ 244 | authState: state.auth, 245 | listState: state.products, 246 | }); 247 | 248 | const mapDispatchToProps = { 249 | removeAuthenticatedUser, 250 | handleProductSearchTerm, 251 | handleSelectedCategory, 252 | getCart, 253 | restGetCart, 254 | }; 255 | 256 | export default connect(mapStateToProps, mapDispatchToProps)(Navbar); 257 | -------------------------------------------------------------------------------- /src/page-components/home-page/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Alert } from '@mui/material'; 4 | import CircularProgress from '@mui/material/CircularProgress'; 5 | import Image from 'next/image'; 6 | import Link from 'next/link'; 7 | import { v4 as uuidv4 } from 'uuid'; 8 | 9 | import { PaginationComponent as Pagination } from '@/components'; 10 | import { useDebounce } from '@/components/custom-hooks'; 11 | import { 12 | addProductToCart, 13 | getProducts, 14 | handleProductSearchTerm, 15 | handleSelectedCategory, 16 | handleUpdatePageNumber, 17 | ReducerType, 18 | } from '@/global-states'; 19 | import { _productPrototypeReducerState as ReducerState, ProductType } from '@/types'; 20 | import { truncate } from '@/utils/functions/helpers'; 21 | 22 | // props from connect mapDispatchToProps 23 | interface MapDispatchProps { 24 | getProducts: (filteredUrl: string) => void; 25 | // handleProductSearchTerm: (payload: string) => void; 26 | handleSelectedCategory: (payload: string) => void; 27 | handleUpdatePageNumber: (payload: number) => void; 28 | addProductToCart: (payload: string) => void; 29 | } 30 | 31 | // props from connect mapStateToProps 32 | interface MapStateProps { 33 | listState: ReducerState; 34 | } 35 | 36 | type PropsType = MapDispatchProps & MapStateProps; 37 | 38 | export function HomePageComponent({ 39 | getProducts, 40 | addProductToCart, 41 | handleUpdatePageNumber, 42 | listState, 43 | handleSelectedCategory, 44 | }: PropsType) { 45 | const { 46 | // list, 47 | listIsLoading, 48 | // listIsSuccess, 49 | listIsError, 50 | listMessage, 51 | // totalDocs, 52 | lastPage, 53 | products, 54 | productSearchTerm, 55 | selectedCategory, 56 | limit, 57 | page, 58 | sortBy, 59 | sort, 60 | } = listState; 61 | 62 | // Debounce search term so that it only gives us latest value ... 63 | // ... if searchTerm has not been updated within last 200ms 64 | // As a result the API call should only fire once user stops typing 65 | const debouncedSearchTerm = useDebounce(productSearchTerm, 200); 66 | 67 | useEffect(() => { 68 | if (selectedCategory !== 'All Products') { 69 | handleSelectedCategory('All Products'); 70 | // handleProductSearchTerm(''); 71 | // handleUpdatePageNumber(1); 72 | } 73 | }, []); 74 | 75 | useEffect(() => { 76 | let filteredUrl = `/products?page=${page}&limit=${limit}`; 77 | if (debouncedSearchTerm) { 78 | // filteredUrl = `/products?page=${page}&limit=${limit}&sortBy=${sortBy}&OrderBy=${sort}&filterBy=category&category=${selectedCategory}&search=${debouncedSearchTerm}`; 79 | filteredUrl = `/products?limit=${limit}&search=${debouncedSearchTerm}`; 80 | } 81 | if (selectedCategory && selectedCategory.toLocaleLowerCase() !== 'all products') { 82 | filteredUrl = `/products?limit=${limit}&search=${debouncedSearchTerm}&category=${selectedCategory}`; 83 | } 84 | getProducts(filteredUrl); 85 | }, [page, limit, sortBy, sort, debouncedSearchTerm, selectedCategory]); 86 | 87 | const handleChange = (_event: React.MouseEvent, value: number) => { 88 | handleUpdatePageNumber(value); 89 | }; 90 | 91 | return ( 92 |
93 | {lastPage > 0 ? ( 94 |
95 | 96 |
97 | ) : null} 98 |
99 | {!products.length && ( 100 |
101 | {listIsLoading && ( 102 |
103 | 104 |
105 | )} 106 | {!listIsError && !listIsLoading && ( 107 |

108 | {' '} 109 | No Products found 110 |

111 | )} 112 | {listIsError && ( 113 | 114 | {listMessage} 115 | 116 | )} 117 |
118 | )} 119 |
120 |
121 | {products.length > 0 && 122 | products.map((product: ProductType, index: number) => ( 123 |
127 | 128 | 129 |
130 | {product.category} 131 |
132 |
133 | {product?.productImages && product?.productImages?.length > 0 ? ( 134 | {product.name} 141 | ) : null} 142 |
143 | 144 |
145 |

{truncate(product.name, 30)}

146 |
147 |
148 | {product.ratings ? ( 149 |
150 | {product.ratings && 151 | Array(Math.ceil(product.ratings)) 152 | .fill(product.ratings) 153 | .map(() => ( 154 | 158 | ✶ 159 | 160 | ))}{' '} 161 |

162 | {' '} 163 | 164 | {product.ratings} 165 | 166 | {product.numberOfReviews} 167 |

168 |
169 | ) : ( 170 |
-
171 | )} 172 |
173 |
174 | {truncate(product.description, 119)} 175 |
176 |
177 | $ {product.price} 178 | {!(index % 2) && Save 5%} 179 |
180 |
181 | {product.stock ? product.stock : 'In Stock - order soon.'} 182 |
183 |
184 | 185 |
186 | 187 | 188 | 195 | 196 | 197 |
198 | 210 |
211 |
212 |
213 | ))} 214 |
215 | {lastPage > 0 && ( 216 |
217 | 218 |
219 | )} 220 |
221 | ); 222 | } 223 | 224 | const mapStateToProps = (state: ReducerType) => ({ 225 | listState: state.products, 226 | }); 227 | 228 | const mapDispatchToProps = { 229 | getProducts, 230 | addProductToCart, 231 | handleProductSearchTerm, 232 | handleSelectedCategory, 233 | handleUpdatePageNumber, 234 | }; 235 | 236 | export default connect(mapStateToProps, mapDispatchToProps)(HomePageComponent); 237 | -------------------------------------------------------------------------------- /src/global-states/reducers/auth/authReducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | _authPrototypeReducerState as AuthReducerState, 3 | AuthenticationAction, 4 | AuthenticationActionType, 5 | } from 'types'; 6 | import { saveUserInLocalStorage } from 'utils'; 7 | 8 | const initialState: AuthReducerState = { 9 | loginUser: null, 10 | loginUserIsLoading: false, 11 | loginUserIsSuccess: false, 12 | loginUserIsError: false, 13 | loginMessage: '', 14 | 15 | emailVerificationLinkToken: '', 16 | token: '', 17 | accessToken: '', 18 | refreshToken: '', 19 | signUpUserIsLoading: false, 20 | signUpUserIsSuccess: false, 21 | signUpUserIsError: false, 22 | signUpUserMessage: '', 23 | 24 | isAuthenticated: false, 25 | isADmin: false, 26 | 27 | confirmEmailIsLoading: false, 28 | confirmEmailIsSuccess: false, 29 | confirmEmailIsError: false, 30 | confirmEmailIsMessage: '', 31 | 32 | forgetPasswordIsLoading: false, 33 | forgetPasswordIsSuccess: false, 34 | forgetPasswordIsError: false, 35 | forgetPasswordMessage: '', 36 | 37 | restPasswordIsLoading: false, 38 | restPasswordIsSuccess: false, 39 | restPasswordIsError: false, 40 | restPasswordMessage: '', 41 | 42 | updateProfileIsLoading: false, 43 | updateProfileIsSuccess: false, 44 | updateProfileIsError: false, 45 | updateProfileMessage: '', 46 | }; 47 | 48 | export function authReducer( 49 | state: AuthReducerState = initialState, 50 | action: AuthenticationAction 51 | ): AuthReducerState { 52 | switch (action?.type) { 53 | case AuthenticationActionType.IS_AUTHENTICATED_SUCCESS: 54 | return { 55 | ...state, 56 | isAuthenticated: true, 57 | }; 58 | case AuthenticationActionType.REMOVE_AUTHENTICATED_USER: 59 | return { 60 | ...state, 61 | isAuthenticated: false, 62 | isADmin: false, 63 | }; 64 | case AuthenticationActionType.AUTH_SIGINUP_LOADING: 65 | return { 66 | ...state, 67 | signUpUserIsLoading: true, 68 | signUpUserIsSuccess: false, 69 | signUpUserIsError: false, 70 | signUpUserMessage: 'PENDING', 71 | }; 72 | case AuthenticationActionType.AUTH_SIGINUP_SUCCESS: 73 | return { 74 | ...state, 75 | emailVerificationLinkToken: action.payload.data.user.emailVerificationLinkToken || '', 76 | token: action.payload.data.user.token || '', 77 | accessToken: action.payload.data.user.accessToken || '', 78 | refreshToken: action.payload.data.user.refreshToken || '', 79 | signUpUserIsLoading: false, 80 | signUpUserIsSuccess: true, 81 | signUpUserIsError: false, 82 | signUpUserMessage: action.payload.message || 'Success', 83 | }; 84 | case AuthenticationActionType.AUTH_SIGINUP_FAILED: 85 | return { 86 | ...state, 87 | emailVerificationLinkToken: '', 88 | token: '', 89 | accessToken: '', 90 | refreshToken: '', 91 | signUpUserIsLoading: false, 92 | signUpUserIsSuccess: false, 93 | signUpUserIsError: true, 94 | signUpUserMessage: action.payload.message || action.payload.error || 'Error', 95 | }; 96 | case AuthenticationActionType.AUTH_SIGINUP_REST: 97 | return { 98 | ...state, 99 | emailVerificationLinkToken: '', 100 | token: '', 101 | accessToken: '', 102 | refreshToken: '', 103 | signUpUserIsLoading: false, 104 | signUpUserIsSuccess: false, 105 | signUpUserIsError: false, 106 | signUpUserMessage: '', 107 | 108 | isAuthenticated: false, 109 | isADmin: false, 110 | }; 111 | case AuthenticationActionType.AUTH_LOGIN_LOADING: 112 | return { 113 | ...state, 114 | loginUserIsLoading: true, 115 | loginUserIsSuccess: false, 116 | loginUserIsError: false, 117 | loginMessage: 'PENDING', 118 | }; 119 | case AuthenticationActionType.AUTH_LOGIN_SUCCESS: 120 | saveUserInLocalStorage(action.payload.data?.accessToken || ''); 121 | return { 122 | ...state, 123 | isADmin: action.payload.data?.user?.role || false, 124 | loginUser: action.payload.data.user || null, 125 | loginUserIsLoading: false, 126 | loginUserIsSuccess: true, 127 | loginUserIsError: false, 128 | loginMessage: action.payload.message || 'Success', 129 | isAuthenticated: true, 130 | }; 131 | case AuthenticationActionType.AUTH_LOGIN_FAILED: 132 | return { 133 | ...state, 134 | loginUser: null, 135 | loginUserIsLoading: false, 136 | loginUserIsSuccess: false, 137 | loginUserIsError: true, 138 | loginMessage: action.payload.message || action.payload.error || 'Error', 139 | isAuthenticated: false, 140 | isADmin: false, 141 | }; 142 | case AuthenticationActionType.AUTH_LOGIN_REST: 143 | return { 144 | ...state, 145 | loginUser: null, 146 | loginUserIsLoading: false, 147 | loginUserIsSuccess: false, 148 | loginUserIsError: false, 149 | loginMessage: '', 150 | isAuthenticated: false, 151 | isADmin: false, 152 | }; 153 | case AuthenticationActionType.AUTH_CONFIRM_EMAIL_LOADING: 154 | return { 155 | ...state, 156 | confirmEmailIsLoading: true, 157 | confirmEmailIsSuccess: false, 158 | confirmEmailIsError: false, 159 | confirmEmailIsMessage: 'PENDING', 160 | }; 161 | case AuthenticationActionType.AUTH_CONFIRM_EMAIL_SUCCESS: 162 | return { 163 | ...state, 164 | confirmEmailIsLoading: false, 165 | confirmEmailIsSuccess: true, 166 | confirmEmailIsError: false, 167 | confirmEmailIsMessage: action.payload.message || 'Success', 168 | }; 169 | case AuthenticationActionType.AUTH_CONFIRM_EMAIL_FAILED: 170 | return { 171 | ...state, 172 | confirmEmailIsLoading: false, 173 | confirmEmailIsSuccess: false, 174 | confirmEmailIsError: true, 175 | confirmEmailIsMessage: action.payload.message || action.payload.error || 'Error', 176 | }; 177 | case AuthenticationActionType.AUTH_CONFIRM_EMAIL_REST: 178 | return { 179 | ...state, 180 | confirmEmailIsLoading: false, 181 | confirmEmailIsSuccess: false, 182 | confirmEmailIsError: false, 183 | confirmEmailIsMessage: '', 184 | }; 185 | case AuthenticationActionType.AUTH_REST_PASSWORD_LOADING: 186 | return { 187 | ...state, 188 | restPasswordIsLoading: true, 189 | restPasswordIsSuccess: false, 190 | restPasswordIsError: false, 191 | restPasswordMessage: 'PENDING', 192 | }; 193 | case AuthenticationActionType.AUTH_REST_PASSWORD_SUCCESS: 194 | return { 195 | ...state, 196 | restPasswordIsLoading: false, 197 | restPasswordIsSuccess: true, 198 | restPasswordIsError: false, 199 | restPasswordMessage: action.payload.message || 'Success', 200 | }; 201 | case AuthenticationActionType.AUTH_REST_PASSWORD_FAILED: 202 | return { 203 | ...state, 204 | restPasswordIsLoading: false, 205 | restPasswordIsSuccess: false, 206 | restPasswordIsError: true, 207 | restPasswordMessage: action.payload.message || action.payload.error || 'Error', 208 | }; 209 | case AuthenticationActionType.AUTH_REST_PASSWORD_REST: 210 | return { 211 | ...state, 212 | restPasswordIsLoading: false, 213 | restPasswordIsSuccess: false, 214 | restPasswordIsError: false, 215 | restPasswordMessage: '', 216 | }; 217 | case AuthenticationActionType.AUTH_FORGET_PASSWORD_LOADING: 218 | return { 219 | ...state, 220 | forgetPasswordIsLoading: true, 221 | forgetPasswordIsSuccess: false, 222 | forgetPasswordIsError: false, 223 | forgetPasswordMessage: 'PENDING', 224 | }; 225 | case AuthenticationActionType.AUTH_FORGET_PASSWORD_SUCCESS: 226 | return { 227 | ...state, 228 | forgetPasswordIsLoading: false, 229 | forgetPasswordIsSuccess: true, 230 | forgetPasswordIsError: false, 231 | forgetPasswordMessage: action.payload.message || 'Success', 232 | }; 233 | case AuthenticationActionType.AUTH_FORGET_PASSWORD_FAILED: 234 | return { 235 | ...state, 236 | forgetPasswordIsLoading: false, 237 | forgetPasswordIsSuccess: false, 238 | forgetPasswordIsError: true, 239 | forgetPasswordMessage: action.payload.message || action.payload.error || 'Error', 240 | }; 241 | case AuthenticationActionType.AUTH_FORGET_PASSWORD_REST: 242 | return { 243 | ...state, 244 | forgetPasswordIsLoading: false, 245 | forgetPasswordIsSuccess: false, 246 | forgetPasswordIsError: false, 247 | forgetPasswordMessage: '', 248 | }; 249 | case AuthenticationActionType.AUTH_UPDATE_PROFILE_LOADING: 250 | return { 251 | ...state, 252 | 253 | updateProfileIsLoading: true, 254 | updateProfileIsSuccess: false, 255 | updateProfileIsError: false, 256 | updateProfileMessage: 'PENDING', 257 | }; 258 | case AuthenticationActionType.AUTH_UPDATE_PROFILE_SUCCESS: 259 | return { 260 | ...state, 261 | updateProfileIsLoading: false, 262 | updateProfileIsSuccess: true, 263 | updateProfileIsError: false, 264 | loginUser: action.payload.data.user || null, 265 | updateProfileMessage: action.payload.message || 'Success', 266 | }; 267 | case AuthenticationActionType.AUTH_UPDATE_PROFILE_FAILED: 268 | return { 269 | ...state, 270 | updateProfileIsLoading: false, 271 | updateProfileIsSuccess: false, 272 | updateProfileIsError: true, 273 | updateProfileMessage: action.payload.message || action.payload.error || 'Error', 274 | }; 275 | case AuthenticationActionType.AUTH_UPDATE_PROFILE_REST: 276 | return { 277 | ...state, 278 | updateProfileIsLoading: false, 279 | updateProfileIsSuccess: false, 280 | updateProfileIsError: false, 281 | updateProfileMessage: '', 282 | }; 283 | default: 284 | return state; 285 | } 286 | } 287 | 288 | export default authReducer; 289 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Saddam-ecommerce 2 | 3 | Building ecommerce website with React Js + Next Js + TypeScript + Node Js + Express + MongoDB + Material-UI + Tailwind-CSS 4 | ⚡️ Made with developer experience first ESLint + Prettier + VSCode setup with complete user authentication (a mobile-friendly). 5 | 6 | # Table of contents 7 | 8 | - [Author](#Author) 9 | - [Demo](#Demo) 10 | - [Technologies](#Technologies) 11 | - [Optimizations](#Optimizations) 12 | - [Contributing](#Contributing) 13 | - [Status](#status) 14 | - [Features](#Features) 15 | - [Inspiration](#inspiration) 16 | - [Related Projects](#Related_Projects) 17 | - [Support](#Support) 18 | - [Feedback](#Feedback) 19 | - [Run Locally](#Run_Locally) 20 | - [Screenshots](#Screenshots) 21 | 22 | # Author 23 | 24 | ### @Saddam Arbaa 25 | 26 | # Technologies 27 | 28 | **Client:** 29 | 30 | - React Js 31 | - Next Js 32 | - TypeScript 33 | - Redux 34 | - Tailwind CSS 35 | - Material-UI 36 | - Material-UI 37 | - Vercel Hosting 38 | 39 | **Server:** 40 | 41 | - Node.js 42 | - Express 43 | - MongoDB 44 | - JSON Web Token (JWT) 45 | - bcryptjs 46 | - Heroku Hosting 47 | 48 | # Demo 49 | 50 | ### Live Demo 51 | 52 | ### Back-End API REPO 53 | 54 | ### LIVE API Demo 55 | 56 | #### Testing Email: testverstion@gmail.com 57 | 58 | #### Testing Password: 12345test 59 | 60 | # Optimizations 61 | 62 | - Next' Js Image component 63 | - Next' Js file-system based router 64 | - Next' Js Server-side rendering 65 | - Memoization (useMemo, Memo) 66 | - Function components 67 | - React hooks 68 | - React useEffect cleanup 69 | - TypeScript 70 | 71 | # Features 72 | 73 | ##### (Users) 74 | 75 | - Complete user authentication 76 | - Users can sign in 77 | - Users can sign out 78 | - Users can verify email 79 | - Users can Change Password 80 | - View all products 81 | - View products detail 82 | - Filter products by category 83 | - Search for products 84 | - Add products to their basket 85 | - Checkout total payment 86 | - Checkout order page 87 | - Products pagination 88 | - Stripe Checkout/Payments (TODO) 89 | 90 | ##### (Admin) 91 | 92 | - Complete Admin Authorization 93 | - Add products 94 | - Update products 95 | - Delete products 96 | - Limit Products 97 | - Add Users 98 | - Update Users 99 | - Delete Users 100 | - Update User Role 101 | 102 | # Contributing 103 | 104 | Contributions are always welcome! 105 | 106 | # Related_Projects 107 | 108 | ### Amazon Clone App built with React Js + TypeScript + Redux + Styled Components 109 | 110 | ### Github Repo 111 | 112 | ### LIVE DEMO 113 | 114 | ### Airbnb Clone App built with React Js + Next Js + TypeScript + Redux + Tailwind CSS 115 | 116 | ### Github Repo 117 | 118 | ### LIVE DEMO 119 | 120 | ### Netflix Clone App built with React Js + TypeScript + Redux + Stripe Checkout/Payments + Vercel Hosting + Firebase 121 | 122 | ### Github Repo 123 | 124 | ### LIVE DEMO 125 | 126 | ### LinkedIn Clone App built with React Js + TypeScript + Redux + Styled Components + Material-UI + Firebase Realtime Database + Vercel Hosting 127 | 128 | ### Github Repo 129 | 130 | ### LIVE DEMO 131 | 132 | ### Facebook Clone App built with React Js + Next Js + TypeScript + Redux + Styled Components 133 | 134 | ### Github Repo 135 | 136 | ### LIVE DEMO 137 | 138 | ### Instagram Clone App built with React Js + Next Js + TypeScript + Redux + Tailwind CSS + Heroicons 139 | 140 | ### Github Repo 141 | 142 | ### LIVE DEMO 143 | 144 | # Support 145 | 146 | For support, email saddamarbaas@gmail.com. 147 | 148 | # Feedback 149 | 150 | If you have any feedback, please reach out to me at saddamarbaas@gmail.com 151 | 152 | Twitter 153 | https://twitter.com/ArbaaSaddam/ 154 | 155 | Linkedin. 156 | https://www.linkedin.com/in/saddamarbaa/ 157 | 158 | Github 159 | https://github.com/saddamarbaa 160 | 161 | Instagram 162 | https://www.instagram.com/saddam.dev/ 163 | 164 | Facebook 165 | https://www.facebook.com/saddam.arbaa 166 | 167 | # Run_Locally 168 | 169 | Clone the project 170 | 171 | ```bash 172 | https://github.com/saddamarbaa/Ecommerce-website-next.js-typeScript 173 | ``` 174 | 175 | Go to the project directory 176 | 177 | ```bash 178 | cd Ecommerce-website-next.js-typeScript 179 | ``` 180 | 181 | Install dependencies 182 | 183 | ```bash 184 | yarn install 185 | # or 186 | npm install 187 | ``` 188 | 189 | Start the server 190 | 191 | ```bash 192 | npm run dev 193 | # or 194 | yarn dev 195 | ``` 196 | 197 | ESLint + Prettier + Lint-Staged Check 198 | 199 | ```bash 200 | yarn lint 201 | # check-types errors 202 | yarn check-types 203 | # check code format 204 | yarn check-format 205 | # check lint errors 206 | yarn check-lint 207 | # format code and fix prettier erros 208 | yarn format 209 | # test all ESLint + Prettier + types erros 210 | yarn test-all 211 | # or if You're using npm you can use npm instead of yarn for all the above ESLint + Prettier + Lint-Staged Check steps 212 | ``` 213 | 214 | # Status 215 | 216 | Project is: in progress I'm working on it in my free time 217 | 218 | # Inspiration 219 | 220 | Build By Saddam Arbaa Project inspired by [Amazon] https://www.amazon.com 221 | 222 | # Screenshots 223 | 224 | ## Signup Page 225 | 226 | ![image](https://user-images.githubusercontent.com/51326421/168418001-ddf09448-6f3a-4d0c-9ce8-1e691666dd13.png) 227 | 228 | ![image](https://user-images.githubusercontent.com/51326421/168420032-1339ad80-24e0-4f06-bb90-630256ae2973.png) 229 | 230 | ## LogIn Page 231 | 232 | ![image](https://user-images.githubusercontent.com/51326421/168417978-8ad90e22-44e0-4961-aa1d-6a3ad8d235d4.png) 233 | 234 | ![image](https://user-images.githubusercontent.com/51326421/168419975-7eee5c8a-d3cb-4e1f-a50f-0f01f6d85650.png) 235 | 236 | ## Forgot Password Page 237 | 238 | ![image](https://user-images.githubusercontent.com/51326421/168418030-7e87c64b-55ed-4c89-a899-0e9434b147bc.png) 239 | 240 | ![image](https://user-images.githubusercontent.com/51326421/168419992-59d97525-031a-41d7-ba31-53ab1a75c1e6.png) 241 | 242 | ![image](https://user-images.githubusercontent.com/51326421/168418079-ec2ca89d-2997-4e44-af34-4bbe2a06bf29.png) 243 | 244 | ## Reset Password email Link 245 | 246 | ![image](https://user-images.githubusercontent.com/51326421/168418151-aaf3a8d4-03b5-4011-aff2-7b42e53a4425.png) 247 | 248 | ## Reset Password Page 249 | 250 | ![image](https://user-images.githubusercontent.com/51326421/168418261-2c4cb6cb-42b6-44b3-8b98-b91da31f2252.png) 251 | 252 | ![image](https://user-images.githubusercontent.com/51326421/168420074-88a7ff38-e9cc-421c-917f-3070223021fb.png) 253 | 254 | ## Update Profile Page 255 | 256 | ![image](https://user-images.githubusercontent.com/51326421/168419955-ca2231ab-457f-4d48-8188-025e2a931d45.png) 257 | 258 | ## Home Page 259 | 260 | ![image](https://user-images.githubusercontent.com/51326421/168419207-cf99c8c8-3032-4441-bbc9-2aecb7b6df78.png) 261 | 262 | ## Home Page (Filter by category(Bookks)) 263 | 264 | ![image](https://user-images.githubusercontent.com/51326421/168419224-aa093745-8923-4c01-95b3-54897b275fde.png) 265 | 266 | ## Home Page (Filter by category(Sports)) 267 | 268 | ![image](https://user-images.githubusercontent.com/51326421/168419258-dc20e307-92f0-4be6-903d-8312bc6ae6b2.png) 269 | 270 | ## Home Page (Filter by category(Toys)) 271 | 272 | ![image](https://user-images.githubusercontent.com/51326421/168419286-d0912456-8aac-4d70-8693-a9923fc3af48.png) 273 | 274 | ## Home Page (Filter by category(Men's clothing)) 275 | 276 | ![image](https://user-images.githubusercontent.com/51326421/168419300-bed584b1-db33-457e-bd91-c24a8e416473.png) 277 | 278 | ## Home Page (Search Product (Jewelery)) 279 | 280 | ![image](https://user-images.githubusercontent.com/51326421/168419621-b392a53a-cbc4-4173-ac75-1500b68ad356.png) 281 | 282 | ## Product Detail Page 283 | 284 | ![image](https://user-images.githubusercontent.com/51326421/168417430-4be9f4e9-2f3c-468d-a587-21ea0e0edafe.png) 285 | 286 | ## Shopping Cart Page 287 | 288 | ![image](https://user-images.githubusercontent.com/51326421/168417498-f5ab4afa-e964-43f9-81ac-87ead9c9852d.png) 289 | 290 | ## Orders Page 291 | 292 | ![image](https://user-images.githubusercontent.com/51326421/168417573-f9358b57-ba05-4ae9-a613-a034ec5230bc.png) 293 | 294 | ## Admin Products Page 295 | 296 | ![image](https://user-images.githubusercontent.com/51326421/168417651-8ea633e1-13c6-4707-8127-69e6e133ff58.png) 297 | 298 | ## Admin Users Page 299 | 300 | ![image](https://user-images.githubusercontent.com/51326421/168417796-0140add5-abf7-490a-9aee-094bd86754d3.png) 301 | 302 | ## Admin Users Table Page 303 | 304 | ![image](https://user-images.githubusercontent.com/51326421/168417814-c7e859f3-6a79-48df-8560-0bb2de53bfc4.png) 305 | 306 | ## Admin Add Product Page 307 | 308 | ![image](https://user-images.githubusercontent.com/51326421/168417851-4396ddbd-d7cb-4d50-bb78-df36b4583c8e.png) 309 | 310 | ![image](https://user-images.githubusercontent.com/51326421/168419929-81f17f43-dbbd-4d80-b7fb-2d614e88b607.png) 311 | 312 | ## Admin Update Product Page 313 | 314 | ![image](https://user-images.githubusercontent.com/51326421/168417887-ff0a9660-4955-474f-a87f-4f0aaeebc2e8.png) 315 | 316 | ## Admin Add User Page 317 | 318 | ![image](https://user-images.githubusercontent.com/51326421/168417951-0f454bc3-fb59-42cb-b764-2059135a6043.png) 319 | 320 | ![image](https://user-images.githubusercontent.com/51326421/168419886-f8be1530-7131-4625-b044-292fbc6ed3c3.png) 321 | 322 | ## Admin Update User Page 323 | 324 | ![image](https://user-images.githubusercontent.com/51326421/168417927-567f47b6-748e-4fc1-9ea0-a45d71f5c660.png) 325 | -------------------------------------------------------------------------------- /src/page-components/admin-page/products/products.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import DeleteIcon from '@mui/icons-material/Delete'; 4 | import EditIcon from '@mui/icons-material/Edit'; 5 | import { Alert } from '@mui/material'; 6 | import CircularProgress from '@mui/material/CircularProgress'; 7 | import Image from 'next/image'; 8 | import Link from 'next/link'; 9 | import { v4 as uuidv4 } from 'uuid'; 10 | 11 | import { ModalComponent as Modal, PaginationComponent as Pagination } from '@/components'; 12 | import { useDebounce } from '@/components/custom-hooks'; 13 | import { 14 | deleteProduct, 15 | getProducts, 16 | handleProductSearchTerm, 17 | handleSelectedCategory, 18 | handleUpdatePageNumber, 19 | ReducerType, 20 | restDeleteProduct, 21 | updateProductSortBy, 22 | } from '@/global-states'; 23 | import { _productPrototypeReducerState as ReducerState, ProductType } from '@/types'; 24 | import { truncate } from '@/utils/functions/helpers'; 25 | 26 | // props from connect mapDispatchToProps 27 | interface MapDispatchProps { 28 | getProducts: (filteredUrl: string) => void; 29 | // handleProductSearchTerm: (payload: string) => void; 30 | handleSelectedCategory: (payload: string) => void; 31 | handleUpdatePageNumber: (payload: number) => void; 32 | deleteProduct: (productId: string) => void; 33 | restDeleteProduct: () => void; 34 | // updateProductSortBy: (payload: string) => void; 35 | } 36 | 37 | // props from connect mapStateToProps 38 | interface MapStateProps { 39 | listState: ReducerState; 40 | } 41 | 42 | type PropsType = MapDispatchProps & MapStateProps; 43 | 44 | export function HomePageComponent({ 45 | getProducts, 46 | // handleProductSearchTerm, 47 | handleSelectedCategory, 48 | listState, 49 | handleUpdatePageNumber, 50 | deleteProduct, 51 | restDeleteProduct, 52 | }: // updateProductSortBy, 53 | PropsType) { 54 | const { 55 | // list, 56 | listIsLoading, 57 | // listIsSuccess, 58 | listIsError, 59 | listMessage, 60 | // totalDocs, 61 | lastPage, 62 | products, 63 | productSearchTerm, 64 | selectedCategory, 65 | limit, 66 | page, 67 | sortBy, 68 | sort, 69 | deleteProductIsPending, 70 | deleteProductIsSuccess, 71 | deleteProductIsError, 72 | deleteProductMessage, 73 | } = listState; 74 | 75 | const [open, setOpen] = useState(false); 76 | const [id, setId] = useState(); 77 | const [showAlert, setShowAlert] = useState(false); 78 | 79 | // Debounce search term so that it only gives us latest value ... 80 | // ... if searchTerm has not been updated within last 200ms 81 | // As a result the API call should only fire once user stops typing 82 | const debouncedSearchTerm = useDebounce(productSearchTerm, 200); 83 | 84 | useEffect(() => { 85 | handleSelectedCategory('All Products'); 86 | // handleProductSearchTerm(''); 87 | // handleUpdatePageNumber(1); 88 | // updateProductSortBy('desc'); 89 | }, []); 90 | 91 | useEffect(() => { 92 | const filteredUrl = `/products?page=${page}&limit=${limit}`; 93 | 94 | getProducts(filteredUrl); 95 | }, [page, limit, sortBy, sort, debouncedSearchTerm, selectedCategory, deleteProductIsSuccess]); 96 | 97 | const handleChange = (_event: React.MouseEvent, value: number) => { 98 | handleUpdatePageNumber(value); 99 | }; 100 | 101 | useEffect(() => { 102 | if (deleteProductIsError || deleteProductIsSuccess) { 103 | setId(() => undefined); 104 | setShowAlert(() => true); 105 | setOpen(() => false); 106 | 107 | const timer = setTimeout(() => { 108 | setShowAlert(() => false); 109 | restDeleteProduct(); 110 | }, 5000); 111 | 112 | return () => clearTimeout(timer); 113 | } 114 | }, [deleteProductIsError, deleteProductIsSuccess]); 115 | 116 | const handleOpen = () => setOpen(true); 117 | const handleClose = () => setOpen(false); 118 | 119 | const handleDelete = () => { 120 | if (id) { 121 | deleteProduct(id); 122 | } 123 | }; 124 | 125 | return ( 126 |
127 | {lastPage > 0 && ( 128 |
129 | 130 |
131 | )} 132 |
133 | {!products.length && ( 134 |
135 | {listIsLoading && ( 136 |
137 | 138 |
139 | )} 140 | {!listIsError && !listIsLoading && ( 141 |

142 | {' '} 143 | No Products found 144 |

145 | )} 146 | {listIsError && ( 147 | 148 | {listMessage} 149 | 150 | )} 151 |
152 | )} 153 | {showAlert && ( 154 | setShowAlert(false)} 158 | > 159 | {deleteProductMessage} 160 | 161 | )} 162 | 169 |
170 |
171 | {products.length > 0 && 172 | products.map((product: ProductType, index: number) => ( 173 |
177 | 178 | 179 |
180 | {product.category} 181 |
182 | 183 |
184 | {product.name} 191 |
192 |
193 |

{truncate(product.name, 30)}

194 |
195 |
196 | {product.ratings ? ( 197 |
198 | {product.ratings && 199 | Array(Math.ceil(product.ratings)) 200 | .fill(product.ratings) 201 | .map(() => ( 202 | 206 | ✶ 207 | 208 | ))}{' '} 209 |

210 | {' '} 211 | 212 | {product.ratings} 213 | 214 | {product.numberOfReviews} 215 |

216 |
217 | ) : ( 218 |
-
219 | )} 220 |
221 |
222 | {truncate(product.description, 119)} 223 |
224 |
225 | $ {product.price} 226 | {!(index % 2) && Save 5%} 227 |
228 |
229 | {product.stock ? product.stock : 'In Stock - order soon.'} 230 |
231 |
232 | 233 |
234 | 235 | 236 | 242 | 243 | 244 | 254 |
255 |
256 | ))} 257 |
258 | {lastPage > 0 && ( 259 |
260 | 261 |
262 | )} 263 |
264 | ); 265 | } 266 | 267 | const mapStateToProps = (state: ReducerType) => ({ 268 | listState: state.products, 269 | }); 270 | 271 | const mapDispatchToProps = { 272 | getProducts, 273 | handleProductSearchTerm, 274 | handleSelectedCategory, 275 | handleUpdatePageNumber, 276 | deleteProduct, 277 | restDeleteProduct, 278 | updateProductSortBy, 279 | }; 280 | 281 | export default connect(mapStateToProps, mapDispatchToProps)(HomePageComponent); 282 | -------------------------------------------------------------------------------- /src/global-states/actions/admin/productsActionCreators.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from 'redux'; 2 | 3 | import { ProductsActionType, ProductType } from '@/types'; 4 | import { apiRequests, getHostUrl } from '@/utils'; 5 | 6 | export const getProducts = (payload: string) => async (dispatch: Dispatch) => { 7 | dispatch({ type: ProductsActionType.GET_PRODUCTS_LOADING }); 8 | try { 9 | const response = await apiRequests({ 10 | method: 'get', 11 | url: `${getHostUrl()}${payload}`, 12 | }); 13 | dispatch({ 14 | type: ProductsActionType.GET_PRODUCTS_SUCCESS, 15 | payload: response, 16 | }); 17 | } catch (error: any) { 18 | dispatch({ 19 | type: ProductsActionType.GET_PRODUCTS_FAILED, 20 | payload: { error: error?.data?.message || error.statusText || error }, 21 | }); 22 | } 23 | }; 24 | 25 | export const addProduct = (product: ProductType) => async (dispatch: Dispatch) => { 26 | dispatch({ type: ProductsActionType.ADD_PRODUCT_LOADING }); 27 | try { 28 | const response = await apiRequests({ 29 | method: 'post', 30 | url: `${getHostUrl()}/admin/products/add`, 31 | data: product, 32 | }); 33 | dispatch({ 34 | type: ProductsActionType.ADD_PRODUCT_SUCCESS, 35 | payload: response, 36 | }); 37 | } catch (error: any) { 38 | dispatch({ 39 | type: ProductsActionType.ADD_PRODUCT_FAILED, 40 | payload: { error: error?.data?.message || error.statusText || error }, 41 | }); 42 | } 43 | }; 44 | 45 | export const restAddProduct = () => async (dispatch: Dispatch) => { 46 | dispatch({ type: ProductsActionType.ADD_PRODUCT_REST }); 47 | }; 48 | 49 | export const deleteProduct = (id: string) => async (dispatch: Dispatch) => { 50 | dispatch({ type: ProductsActionType.DELETE_PRODUCT_LOADING }); 51 | try { 52 | const response = await apiRequests({ 53 | method: 'delete', 54 | url: `${getHostUrl()}/admin/products/delete/${id}`, 55 | }); 56 | dispatch({ type: ProductsActionType.DELETE_PRODUCT_SUCCESS, payload: response }); 57 | } catch (error: any) { 58 | dispatch({ 59 | type: ProductsActionType.DELETE_PRODUCT_FAILED, 60 | payload: { error: error?.data?.message || error.statusText || error }, 61 | }); 62 | } 63 | }; 64 | 65 | export const restDeleteProduct = () => async (dispatch: Dispatch) => { 66 | dispatch({ type: ProductsActionType.DELETE_PRODUCT_REST }); 67 | }; 68 | 69 | export const updateProduct = 70 | (_product: ProductType, id: string | string[]) => async (dispatch: Dispatch) => { 71 | dispatch({ type: ProductsActionType.UPDATE_PRODUCT_LOADING }); 72 | try { 73 | const response = await apiRequests({ 74 | method: 'put', 75 | url: `${getHostUrl()}/admin/products/update/${id}`, 76 | data: _product, 77 | }); 78 | 79 | dispatch({ type: ProductsActionType.UPDATE_PRODUCT_SUCCESS, payload: response }); 80 | } catch (error: any) { 81 | dispatch({ 82 | type: ProductsActionType.UPDATE_PRODUCT_FAILED, 83 | payload: { error: error?.data?.message || error.statusText || error }, 84 | }); 85 | } 86 | }; 87 | 88 | export const restUpdateProduct = () => async (dispatch: Dispatch) => { 89 | dispatch({ type: ProductsActionType.UPDATE_PRODUCT_REST }); 90 | }; 91 | 92 | export const getIndividualProduct = (id: string | string[]) => async (dispatch: any) => { 93 | dispatch({ type: ProductsActionType.GET_INDIVIDUAL_PRODUCT_LOADING }); 94 | try { 95 | const response = await apiRequests({ 96 | method: 'get', 97 | url: `${getHostUrl()}/products/${id}`, 98 | }); 99 | dispatch({ 100 | type: ProductsActionType.GET_INDIVIDUAL_PRODUCT_SUCCESS, 101 | payload: response, 102 | }); 103 | } catch (error: any) { 104 | dispatch({ 105 | type: ProductsActionType.GET_INDIVIDUAL_PRODUCT_FAILED, 106 | payload: { error: error?.data?.message || error.statusText || error }, 107 | }); 108 | } 109 | }; 110 | 111 | export const restGetIndividualProduct = () => async (dispatch: Dispatch) => { 112 | dispatch({ type: ProductsActionType.GET_INDIVIDUAL_PRODUCT_REST }); 113 | }; 114 | 115 | export const handleProductSearchTerm = (payload: string) => async (dispatch: Dispatch) => { 116 | dispatch({ type: ProductsActionType.PRODUCT_SEARCH_TERM, payload }); 117 | }; 118 | 119 | export const handleSelectedCategory = (payload: string) => async (dispatch: Dispatch) => { 120 | dispatch({ type: ProductsActionType.PRODUCT_CATEGORY, payload }); 121 | }; 122 | 123 | export const handleUpdatePageNumber = (payload: number) => async (dispatch: Dispatch) => { 124 | dispatch({ type: ProductsActionType.UPDATE_PAGE_NUMBER, payload }); 125 | }; 126 | 127 | export const updateProductSortBy = (payload: string) => async (dispatch: Dispatch) => { 128 | dispatch({ type: ProductsActionType.UPDATE_PRODUCT_SORTBY, payload }); 129 | }; 130 | 131 | export const addProductToCart = (id: string, decrease?: boolean) => async (dispatch: Dispatch) => { 132 | dispatch({ type: ProductsActionType.ADD_PRODUCT_TO_CART_LOADING }); 133 | 134 | try { 135 | const response = await apiRequests({ 136 | method: 'post', 137 | url: `${getHostUrl()}/cart?decrease=${decrease || false}`, 138 | data: { productId: id }, 139 | }); 140 | dispatch({ 141 | type: ProductsActionType.ADD_PRODUCT_TO_CART_SUCCESS, 142 | payload: response, 143 | }); 144 | } catch (error: any) { 145 | dispatch({ 146 | type: ProductsActionType.ADD_PRODUCT_TO_CART_FAILED, 147 | payload: { error: error?.data?.message || error.statusText || error }, 148 | }); 149 | } 150 | }; 151 | 152 | export const restAddProductToCart = () => async (dispatch: Dispatch) => { 153 | dispatch({ type: ProductsActionType.ADD_PRODUCT_TO_CART_REST }); 154 | }; 155 | 156 | export const deleteItemFromCart = (id: string) => async (dispatch: Dispatch) => { 157 | dispatch({ type: ProductsActionType.DELETE_ITEM_FROM_CART_LOADING }); 158 | try { 159 | const response = await apiRequests({ 160 | method: 'post', 161 | url: `${getHostUrl()}/cart/delete-item`, 162 | data: { productId: id }, 163 | }); 164 | dispatch({ 165 | type: ProductsActionType.DELETE_ITEM_FROM_CART_SUCCESS, 166 | payload: response, 167 | }); 168 | } catch (error: any) { 169 | dispatch({ 170 | type: ProductsActionType.DELETE_ITEM_FROM_CART_FAILED, 171 | payload: { error: error?.data?.message || error.statusText || error }, 172 | }); 173 | } 174 | }; 175 | 176 | export const restDeleteItemFromCart = () => async (dispatch: Dispatch) => { 177 | dispatch({ type: ProductsActionType.DELETE_ITEM_FROM_CART_REST }); 178 | }; 179 | 180 | export const getCart = () => async (dispatch: Dispatch) => { 181 | dispatch({ type: ProductsActionType.GET_CART_LOADING }); 182 | try { 183 | const response = await apiRequests({ 184 | method: 'get', 185 | url: `${getHostUrl()}/cart`, 186 | }); 187 | dispatch({ 188 | type: ProductsActionType.GET_CART_SUCCESS, 189 | payload: response, 190 | }); 191 | } catch (error: any) { 192 | dispatch({ 193 | type: ProductsActionType.GET_CART_FAILED, 194 | payload: { error: error?.data?.message || error.statusText || error }, 195 | }); 196 | } 197 | }; 198 | 199 | export const restGetCart = () => async (dispatch: Dispatch) => { 200 | dispatch({ type: ProductsActionType.GET_CART_REST }); 201 | }; 202 | 203 | export const clearCart = () => async (dispatch: Dispatch) => { 204 | dispatch({ type: ProductsActionType.CLEAR_CART_LOADING }); 205 | try { 206 | const response = await apiRequests({ 207 | method: 'delete', 208 | url: `${getHostUrl()}/cart/clear-cart`, 209 | }); 210 | dispatch({ 211 | type: ProductsActionType.CLEAR_CART_SUCCESS, 212 | payload: response, 213 | }); 214 | } catch (error: any) { 215 | dispatch({ 216 | type: ProductsActionType.CLEAR_CART_FAILED, 217 | payload: { error: error?.data?.message || error.statusText || error }, 218 | }); 219 | } 220 | }; 221 | 222 | export const getOrders = () => async (dispatch: Dispatch) => { 223 | dispatch({ type: ProductsActionType.GET_ORDER_LOADING }); 224 | try { 225 | const response = await apiRequests({ 226 | method: 'get', 227 | url: `${getHostUrl()}/orders`, 228 | }); 229 | dispatch({ 230 | type: ProductsActionType.GET_ORDER_SUCCESS, 231 | payload: response, 232 | }); 233 | } catch (error: any) { 234 | dispatch({ 235 | type: ProductsActionType.GET_ORDER_FAILED, 236 | payload: { error: error?.data?.message || error.statusText || error }, 237 | }); 238 | } 239 | }; 240 | 241 | export const restGetOrders = () => async (dispatch: Dispatch) => { 242 | dispatch({ type: ProductsActionType.GET_ORDER_REST }); 243 | }; 244 | 245 | export const addOrders = (data: any) => async (dispatch: Dispatch) => { 246 | dispatch({ type: ProductsActionType.ADD_ORDER_LOADING }); 247 | 248 | try { 249 | const response = await apiRequests({ 250 | method: 'post', 251 | url: `${getHostUrl()}/orders`, 252 | data, 253 | }); 254 | dispatch({ 255 | type: ProductsActionType.ADD_ORDER_SUCCESS, 256 | payload: response, 257 | }); 258 | } catch (error: any) { 259 | dispatch({ 260 | type: ProductsActionType.ADD_ORDER_FAILED, 261 | payload: { error: error?.data?.message || error.statusText || error }, 262 | }); 263 | } 264 | }; 265 | 266 | export const restAddOrders = () => async (dispatch: Dispatch) => { 267 | dispatch({ type: ProductsActionType.ADD_ORDER_REST }); 268 | }; 269 | 270 | export const clearOrders = () => async (dispatch: Dispatch) => { 271 | dispatch({ type: ProductsActionType.CLEAR_ORDER_LOADING }); 272 | try { 273 | const response = await apiRequests({ 274 | method: 'delete', 275 | url: `${getHostUrl()}/orders/clear-orders`, 276 | }); 277 | dispatch({ 278 | type: ProductsActionType.CLEAR_ORDER_SUCCESS, 279 | payload: response, 280 | }); 281 | } catch (error: any) { 282 | dispatch({ 283 | type: ProductsActionType.CLEAR_ORDER_FAILED, 284 | payload: { error: error?.data?.message || error.statusText || error }, 285 | }); 286 | } 287 | }; 288 | 289 | export const clearSingleOrder = (id: string) => async (dispatch: Dispatch) => { 290 | dispatch({ type: ProductsActionType.CLEAR_SINGLE_ORDER_LOADING }); 291 | try { 292 | const response = await apiRequests({ 293 | method: 'delete', 294 | url: `${getHostUrl()}/orders/${id}`, 295 | }); 296 | dispatch({ 297 | type: ProductsActionType.CLEAR_SINGLE_ORDER_SUCCESS, 298 | payload: response, 299 | }); 300 | } catch (error: any) { 301 | dispatch({ 302 | type: ProductsActionType.CLEAR_SINGLE_ORDER_FAILED, 303 | payload: { error: error?.data?.message || error.statusText || error }, 304 | }); 305 | } 306 | }; 307 | 308 | export const restClearSingleOrder = () => async (dispatch: Dispatch) => { 309 | dispatch({ type: ProductsActionType.CLEAR_SINGLE_ORDER_REST }); 310 | }; 311 | 312 | export const deleteReview = (id: string | string[]) => async (dispatch: Dispatch) => { 313 | dispatch({ type: ProductsActionType.DELETE_REVIEW_LOADING }); 314 | try { 315 | const response = await apiRequests({ 316 | method: 'delete', 317 | url: `${getHostUrl()}/products/reviews/${id}`, 318 | }); 319 | dispatch({ 320 | type: ProductsActionType.DELETE_REVIEW_SUCCESS, 321 | payload: response, 322 | }); 323 | } catch (error: any) { 324 | dispatch({ 325 | type: ProductsActionType.DELETE_REVIEW_FAILED, 326 | payload: { error: error?.data?.message || error.statusText || error }, 327 | }); 328 | } 329 | }; 330 | 331 | export const restDeleteReview = () => async (dispatch: Dispatch) => { 332 | dispatch({ type: ProductsActionType.DELETE_REVIEW_REST }); 333 | }; 334 | --------------------------------------------------------------------------------