├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── components ├── ApplicationContext.tsx ├── BackTitle.tsx ├── Collapsible.tsx ├── EscapeHTML.tsx ├── NoContent.tsx ├── PageContainer.tsx ├── ResponsiveViews.tsx ├── Toaster.tsx ├── cart │ ├── CartSummary.tsx │ └── CartTaxAndChargesModal.tsx ├── core │ ├── CoreActiveLink.tsx │ ├── CoreButton.tsx │ ├── CoreCheckbox.tsx │ ├── CoreDivider.tsx │ ├── CoreImage.tsx │ ├── CoreInput.tsx │ ├── CoreLink.tsx │ ├── CoreRadio.tsx │ ├── CoreSelectInput.tsx │ └── CoreTextarea.tsx ├── custom-icons │ ├── index.tsx │ └── interface.ts ├── error │ ├── Error.tsx │ └── ErrorBoundary.tsx ├── header │ ├── Header.tsx │ ├── HeaderLinks.tsx │ └── Snackbar.tsx ├── image │ └── ImageInfo.tsx ├── layout │ ├── Delete.tsx │ ├── FormLayout.tsx │ ├── IndexViewLayout.tsx │ ├── LayoutPagination.tsx │ ├── PageLayout.tsx │ ├── PageLinks.tsx │ ├── PrivatePageLayout.tsx │ └── SearchLayout.tsx ├── loader │ ├── Loader.tsx │ └── PageLoader.tsx ├── modal │ ├── Alert.tsx │ └── Modal.tsx ├── order │ ├── CancelOrderModal.tsx │ └── OrderInfo.tsx ├── product │ ├── ProductAttributeUpdateModal.tsx │ ├── ProductFeatureSectionUpdateModal.tsx │ └── ProductTagUpdateModal.tsx └── section │ ├── SectionItemUpdateForm.tsx │ ├── SectionItemUpdateFormBlog.tsx │ ├── SectionItemUpdateFormCatalogue.tsx │ ├── SectionItemUpdateFormCustomSection.tsx │ ├── SectionItemUpdateFormCustomerFeedback.tsx │ ├── SectionItemUpdateFormProcedure.tsx │ ├── SectionItemUpdateFormProduct.tsx │ ├── SectionItemUpdateFormSlide.tsx │ ├── SectionItemUpdateFormUSP.tsx │ └── SectionItemUpdateModal.tsx ├── config └── appConfig.ts ├── constants └── constants.ts ├── contract ├── address.ts ├── admin.ts ├── blog.ts ├── cart.ts ├── catalogue.ts ├── common.ts ├── constants.ts ├── currency.ts ├── faq.ts ├── health.ts ├── image.ts ├── order.ts ├── payment.ts ├── product.ts ├── search.ts ├── section.ts ├── security.ts ├── setting.ts ├── staticPage.ts ├── supportedRegions.ts ├── user.ts └── userWish.ts ├── env ├── local.env ├── localproduction.env └── production.env ├── error └── ApiError.ts ├── hooks ├── useApplicationContext.ts ├── useApplicationContextReducer.ts ├── useCustomLayoutEffect.ts ├── useDisablePageScrolling.ts ├── useEscape.ts ├── useLoginEffect.ts ├── useNativeShare.ts ├── useOnEnter.ts ├── useOrientation.ts ├── useOutsideClick.ts ├── usePortal.ts ├── useUpdateEffect.ts └── useUpdateLayoutEffect.ts ├── http ├── auth.ts ├── blog.ts ├── catalogue.ts ├── currency.ts ├── faq.ts ├── httpClient.ts ├── image.ts ├── order.ts ├── privacyPolicy.ts ├── product.ts ├── refundPolicy.ts ├── section.ts ├── securityQuestion.ts ├── shippingPolicy.ts ├── supportedRegions.ts ├── tnc.ts └── user.ts ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── 404.tsx ├── _app.tsx ├── _document.tsx ├── blog │ ├── index.tsx │ ├── search.tsx │ └── update.tsx ├── catalogue │ ├── images.tsx │ ├── index.tsx │ ├── search.tsx │ └── update.tsx ├── currency │ ├── index.tsx │ └── update.tsx ├── faq-topic │ ├── [topicId] │ │ ├── index.tsx │ │ └── update.tsx │ ├── index.tsx │ └── update.tsx ├── image │ ├── index.tsx │ ├── search.tsx │ └── update.tsx ├── index.tsx ├── login.tsx ├── order-status-type │ ├── index.tsx │ └── update.tsx ├── order │ ├── index.tsx │ └── update.tsx ├── privacy-policy │ ├── index.tsx │ └── update.tsx ├── product-attribute-key │ ├── index.tsx │ └── update.tsx ├── product-brand │ ├── index.tsx │ └── update.tsx ├── product-relations │ ├── index.tsx │ └── update.tsx ├── product │ ├── combo │ │ ├── index.tsx │ │ ├── search.tsx │ │ └── update │ │ │ ├── attributes.tsx │ │ │ ├── feature-sections.tsx │ │ │ ├── images.tsx │ │ │ ├── index.tsx │ │ │ ├── sku.tsx │ │ │ └── tags.tsx │ └── individual │ │ ├── index.tsx │ │ ├── search.tsx │ │ └── update │ │ ├── attributes.tsx │ │ ├── feature-sections.tsx │ │ ├── images.tsx │ │ ├── index.tsx │ │ ├── sku.tsx │ │ └── tags.tsx ├── recent-orders │ └── index.tsx ├── refund-policy │ ├── index.tsx │ └── update.tsx ├── section │ ├── [sectionId] │ │ └── items │ │ │ ├── add.tsx │ │ │ └── index.tsx │ ├── index.tsx │ ├── page │ │ ├── index.tsx │ │ └── update.tsx │ └── update.tsx ├── security-question │ ├── index.tsx │ └── update.tsx ├── shipping-policy │ ├── index.tsx │ └── update.tsx ├── supported-regions │ ├── [supportedRegionType] │ │ └── update.tsx │ └── index.tsx ├── terms-conditions │ ├── index.tsx │ └── update.tsx └── user │ ├── address.tsx │ ├── cart.tsx │ ├── detail.tsx │ ├── index.tsx │ ├── login-history.tsx │ ├── order.tsx │ ├── search.tsx │ └── wishlist.tsx ├── postcss.config.js ├── public ├── fonts │ └── BentonSans │ │ ├── BentonSans-Bold │ │ ├── BentonSans-Bold.eot │ │ ├── BentonSans-Bold.svg │ │ ├── BentonSans-Bold.ttf │ │ ├── BentonSans-Bold.woff │ │ ├── demo.htm │ │ └── license.txt │ │ ├── BentonSans-Italic │ │ ├── BentonSans-BoldItalic.woff │ │ ├── BentonSans-BoldItalic.woff2 │ │ ├── BentonSans-MediumItalic.woff │ │ ├── BentonSans-MediumItalic.woff2 │ │ ├── BentonSans-RegularItalic.woff │ │ └── BentonSans-RegularItalic.woff2 │ │ ├── BentonSans-Medium │ │ ├── BentonSans-Medium.eot │ │ ├── BentonSans-Medium.svg │ │ ├── BentonSans-Medium.ttf │ │ ├── BentonSans-Medium.woff │ │ ├── demo.htm │ │ └── license.txt │ │ └── BentonSans-Regular │ │ ├── BentonSans-Regular.eot │ │ ├── BentonSans-Regular.svg │ │ ├── BentonSans-Regular.ttf │ │ ├── BentonSans-Regular.woff │ │ ├── demo.htm │ │ └── license.txt └── images │ ├── empty │ ├── empty-cart-basket.webp │ └── empty-glass.svg │ ├── favicon.ico │ ├── icons │ ├── arrow-left-outline.svg │ └── arrow-right-outline.svg │ └── logo.png ├── scriptTemplates ├── AppContext.tsx ├── analytics.tsx ├── media.tsx └── meta.tsx ├── scripts └── fileSystem.js ├── styles ├── _components.scss ├── _fonts.scss ├── _globals.scss ├── _html-body.scss ├── _input.scss ├── _loader.scss ├── _mixins.scss ├── _nprogress.scss └── styles.scss ├── tailwind.config.js ├── tsconfig.json └── utils ├── admin.ts ├── applicationContext.ts ├── blog.ts ├── catalogue.ts ├── common.ts ├── currency.ts ├── dates.ts ├── deviceDetect.ts ├── faq.ts ├── home.ts ├── image.ts ├── layout.ts ├── login.ts ├── order.ts ├── payment.ts ├── privacyPolicy.ts ├── product.ts ├── refundPolicy.ts ├── section.ts ├── securityQuestion.ts ├── shippingPolicy.ts ├── supportedRegions.ts ├── tnc.ts └── user.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .next 3 | public/ -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "extends": [ 8 | // Uses the recommended rules from @eslint-plugin-react 9 | "plugin:@next/next/recommended", 10 | 11 | // Uses the recommended rules from @typescript-eslint/eslint-plugin 12 | "plugin:@typescript-eslint/recommended", 13 | 14 | // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 15 | "prettier/@typescript-eslint", 16 | 17 | // Enables eslint-plugin-prettier and displays prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 18 | "plugin:prettier/recommended" 19 | ], 20 | "parserOptions": { 21 | "ecmaVersion": 2018, // Allows for the parsing of modern ECMAScript features 22 | "sourceType": "module", // Allows for the use of imports 23 | "ecmaFeatures": { 24 | "jsx": true // Allows for the parsing of JSX 25 | } 26 | }, 27 | "rules": { 28 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 29 | // e.g. "@typescript-eslint/explicit-function-return-type": "off", 30 | 31 | "@typescript-eslint/explicit-function-return-type": "off", 32 | "react/prop-types": "off", 33 | "@typescript-eslint/interface-name-prefix": "off", 34 | "@typescript-eslint/no-explicit-any": "off", 35 | "@typescript-eslint/no-var-requires": "off", 36 | "@typescript-eslint/explicit-module-boundary-types": "off", 37 | "@typescript-eslint/ban-ts-comment": "off", 38 | "@typescript-eslint/no-empty-interface": "off", 39 | "@typescript-eslint/ban-types": "off", 40 | "@typescript-eslint/no-unused-vars": "off", 41 | "@next/next/no-img-element": "off" 42 | }, 43 | "settings": { 44 | "react": { 45 | "version": "detect" // Tells eslint-plugin-react to automatically detect the version of React to use 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.png binary 3 | *.jpg binary 4 | *.jpeg binary 5 | *.ico binary 6 | *.icns binary 7 | 8 | # Web fonts are binary 9 | *.otf binary 10 | *.eot binary 11 | *.svg binary 12 | *.ttf binary 13 | *.woff binary 14 | *.woff2 binary 15 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Workflow 2 | 3 | on: [push] 4 | 5 | jobs: 6 | Lint: 7 | name: Lint 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v1 14 | - name: Determine npm cache directory 15 | id: npm-cache 16 | run: | 17 | echo "::set-output name=dir::$(npm config get cache)" 18 | - name: Restore npm cache 19 | uses: actions/cache@v1 20 | with: 21 | path: ${{ steps.npm-cache.outputs.dir }} 22 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 23 | restore-keys: | 24 | ${{ runner.os }}-node- 25 | - name: Lint JS, SCSS 26 | run: | 27 | npm ci 28 | npm run lint:fix 29 | 30 | Build: 31 | name: Build 32 | env: 33 | STORE_CMS_ENV: production 34 | CYPRESS_INSTALL_BINARY: 0 35 | CI: true 36 | runs-on: ubuntu-latest 37 | strategy: 38 | matrix: 39 | node-version: [14.x] 40 | steps: 41 | - name: Checkout 42 | uses: actions/checkout@v1 43 | - name: Determine npm cache directory 44 | id: npm-cache 45 | run: | 46 | echo "::set-output name=dir::$(npm config get cache)" 47 | - name: Restore npm cache 48 | uses: actions/cache@v1 49 | with: 50 | path: ${{ steps.npm-cache.outputs.dir }} 51 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 52 | restore-keys: | 53 | ${{ runner.os }}-node- 54 | - name: Use Node.js ${{ matrix.node-version }} 55 | uses: actions/setup-node@v1 56 | with: 57 | node-version: ${{ matrix.node-version }} 58 | - name: npm install, build, and test 59 | run: | 60 | npm ci 61 | STORE_CMS_ENV=production npm run build 62 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14.17.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .next 3 | package-lock.json 4 | public/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "bracketSpacing": true, 4 | "singleQuote": true, 5 | "trailingComma": "es5", 6 | "semi": false, 7 | "arrowParens": "avoid", 8 | "jsxBracketSameLine": true 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OwnStore 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is part of OwnStore suite. Learn more here: https://ownstore.dev 2 | 3 | The suite contains the following projects: 4 | - [Website](https://github.com/OwnStoreOrg/ownstore-website) 5 | - [API](https://github.com/OwnStoreOrg/ownstore-api) 6 | - [CMS](https://github.com/OwnStoreOrg/ownstore-cms) 7 | - [Doc](https://github.com/OwnStoreOrg/ownstore-doc) 8 | - Apps 9 | - [TWA](https://github.com/OwnStoreOrg/ownstore-app-twa) 10 | 11 | **Note:** This project is a clone of a privately-managed repo by the author. The original repo has the complete Git tree, few additional technologies and a CI/CD setup. Changes are initially made in the private repo and then synced here through CLI commands. This is done for security reasons. 12 | 13 | Additional updates present in the original repos 14 | - Cypress (for end-to-end testing) 15 | - Storybook (for UI components explorer) 16 | - Docker files (for containerization) 17 | - Jest (for functional testing of business logic) 18 | - Local https setup for development (Eg. https://local.ownstore-demo.com) 19 | - AWS cloud migration (not pushed to prod yet) 20 | - Cloudflare as a CDN and custom name server (not pushed to prod yet) 21 | - Sub-domain for serving static files (not pushed to prod yet) 22 | - Algolia and ElasticSearch experiments for searching (not pushed to prod yet) 23 | - Sentry end-to-end tracking experiment (not pushed to prod yet) 24 | - Token-based color theming for dark mode and better DX support (not pushed to prod yet) 25 | - Modular folder structure 26 | - Native app with Flutter (Work in progress) 27 | - Kubernetes config to manage multiple app instances with ease (not pushed to prod yet) 28 | - Monitoring and alerting with Prometheus and Grafana (not pushed to prod yet) 29 | - Continuous deployment with GitHub actions, Docker, AWS and Kubernetes (not pushed to prod yet) 30 | - Lighhouse score checker for PRs through GitHub actions 31 | -------------------------------------------------------------------------------- /components/ApplicationContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SCREEN_SIZE } from '../constants/constants' 3 | 4 | export type DEVICE_PROFILE = keyof typeof SCREEN_SIZE 5 | 6 | export enum LoginStateType { 7 | NONE = 'NONE', 8 | LOGGED_IN = 'LOGGED_IN', 9 | LOGGED_OUT = 'LOGGED_OUT', 10 | } 11 | 12 | export interface IDeviceInfo { 13 | isDesktop: boolean 14 | isMobile: boolean 15 | 16 | isTouchDevice: boolean 17 | isLandscapeMode: boolean 18 | 19 | profile: DEVICE_PROFILE 20 | 21 | isSm: boolean 22 | isMd: boolean 23 | isLg: boolean 24 | isXl: boolean 25 | is2Xl: boolean 26 | } 27 | 28 | export interface IApplicationContextProps { 29 | loginState: LoginStateType 30 | isLoggedIn: boolean 31 | 32 | device: IDeviceInfo 33 | 34 | updaters: { 35 | updateLoginState: (val: LoginStateType) => void 36 | } 37 | 38 | logout: () => void 39 | } 40 | 41 | export const defaultApplicationContext: IApplicationContextProps = { 42 | loginState: LoginStateType.NONE, 43 | isLoggedIn: false, 44 | 45 | device: { 46 | isDesktop: true, 47 | isMobile: true, 48 | 49 | isTouchDevice: false, 50 | isLandscapeMode: false, 51 | 52 | profile: 'XL', 53 | 54 | isSm: true, 55 | isMd: false, 56 | isLg: false, 57 | isXl: false, 58 | is2Xl: false, 59 | }, 60 | 61 | updaters: { 62 | updateLoginState: val => null, 63 | }, 64 | 65 | logout: () => null, 66 | } 67 | 68 | const ApplicationContext = React.createContext(defaultApplicationContext) 69 | 70 | export default ApplicationContext 71 | -------------------------------------------------------------------------------- /components/BackTitle.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { useRouter } from 'next/router' 3 | import { ArrowLeftIcon } from '@heroicons/react/solid' 4 | import classnames from 'classnames' 5 | 6 | interface IBackTitleProps { 7 | title: string 8 | className?: string 9 | } 10 | 11 | const BackTitle: React.FC = props => { 12 | const { title, className } = props 13 | 14 | const router = useRouter() 15 | 16 | const handleBackIconClick = () => { 17 | router.back() 18 | } 19 | 20 | return ( 21 |
22 |
25 | 26 |
27 |
{title}
28 |
29 | ) 30 | } 31 | 32 | export default BackTitle 33 | -------------------------------------------------------------------------------- /components/Collapsible.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactCollapsible from 'react-collapsible' 3 | 4 | interface ICollapsibleProps { 5 | trigger: React.ReactElement 6 | transitionTime?: number 7 | } 8 | 9 | const Collapsible: React.FC = props => { 10 | const { trigger, transitionTime, children } = props 11 | 12 | return ( 13 | 14 | {children} 15 | 16 | ) 17 | } 18 | 19 | export default Collapsible 20 | -------------------------------------------------------------------------------- /components/EscapeHTML.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import useSSR from 'use-ssr' 3 | import useCustomLayoutEffect from '../hooks/useCustomLayoutEffect' 4 | 5 | interface IEscapeHTMLProps { 6 | element: string 7 | html: string 8 | className?: string 9 | } 10 | 11 | /* 12 | Renders raw HTML in a React component. 13 | */ 14 | 15 | const EscapeHTML: React.FC = props => { 16 | const { element, html, className } = props 17 | 18 | const { isServer } = useSSR() 19 | const articleRef = useRef(null) 20 | 21 | useCustomLayoutEffect(() => { 22 | let updated = false 23 | if (document.createRange) { 24 | const range = document.createRange() 25 | if (range.createContextualFragment) { 26 | articleRef.current.innerHTML = '' 27 | articleRef.current.appendChild(range.createContextualFragment(html)) 28 | updated = true 29 | } 30 | } 31 | if (!updated) { 32 | // this is added to support fallabck for old browsers 33 | if (articleRef.current.insertAdjacentHTML) { 34 | articleRef.current.innerHTML = '' 35 | articleRef.current.insertAdjacentHTML('afterBegin', html) 36 | } else { 37 | // if no update method found just set innerHTML, this may not execute scripts 38 | articleRef.current.innerHTML = html 39 | } 40 | } 41 | }, [html]) 42 | 43 | return isServer 44 | ? React.createElement(element, { dangerouslySetInnerHTML: { __html: html }, ref: articleRef, className: className }) 45 | : React.createElement(element, { ref: articleRef, className: className }) 46 | } 47 | 48 | export default EscapeHTML 49 | -------------------------------------------------------------------------------- /components/NoContent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classnames from 'classnames' 3 | import CoreImage, { ImageSourceType } from './core/CoreImage' 4 | import { prepareImageUrl } from '../utils/image' 5 | 6 | export enum NoContentType { 7 | DEFAULT = 'DEFAULT', 8 | LOGIN = 'LOGIN', 9 | } 10 | 11 | interface INoContentProps { 12 | type?: NoContentType 13 | message?: string 14 | className?: string 15 | } 16 | 17 | const NoContent: React.FC = props => { 18 | const { className, message = 'No content available' } = props 19 | 20 | return ( 21 |
22 | {/*
*/} 23 |
24 | 29 |
30 |
{message}
31 |
32 | ) 33 | } 34 | 35 | export default NoContent 36 | -------------------------------------------------------------------------------- /components/PageContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const PageContainer: React.FC = props => { 4 | return
{props.children}
5 | } 6 | 7 | export default PageContainer 8 | -------------------------------------------------------------------------------- /components/ResponsiveViews.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, PropsWithChildren } from 'react' 2 | import ApplicationContext from './ApplicationContext' 3 | 4 | interface IResponsiveViewProps { 5 | useCSS?: boolean 6 | } 7 | 8 | export const DesktopView: React.FC = props => { 9 | const { useCSS, children } = props 10 | 11 | const applicationContext = useContext(ApplicationContext) 12 | const { 13 | device: { isDesktop }, 14 | } = applicationContext 15 | 16 | if (useCSS) { 17 | return
{children}
18 | } 19 | 20 | return <>{isDesktop ? children : null} 21 | } 22 | 23 | export const MobileView: React.FC = props => { 24 | const { useCSS, children } = props 25 | 26 | const applicationContext = useContext(ApplicationContext) 27 | const { 28 | device: { isMobile }, 29 | } = applicationContext 30 | 31 | if (useCSS) { 32 | return
{children}
33 | } 34 | 35 | return <>{isMobile ? children : null} 36 | } 37 | -------------------------------------------------------------------------------- /components/Toaster.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react' 2 | import toast, { Toaster } from 'react-hot-toast' 3 | import ApplicationContext from './ApplicationContext' 4 | 5 | export const toastSuccess = (message: string) => { 6 | toast.success(message) 7 | } 8 | 9 | export const toastError = (message: string) => { 10 | toast.error(message) 11 | } 12 | 13 | export const toastDismiss = () => { 14 | toast.dismiss() 15 | } 16 | 17 | interface IToasterProps {} 18 | 19 | const AppToaster: React.FC = props => { 20 | const applicationContext = useContext(ApplicationContext) 21 | const { 22 | device: { isSm }, 23 | } = applicationContext 24 | 25 | return ( 26 | 44 | ) 45 | 46 | return null 47 | } 48 | 49 | export default AppToaster 50 | -------------------------------------------------------------------------------- /components/cart/CartTaxAndChargesModal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ICurrencyInfo } from '../../contract/currency' 3 | import EscapeHTML from '../EscapeHTML' 4 | import Modal from '../modal/Modal' 5 | 6 | interface ICartTaxAndChargesModalProps { 7 | extraChargesAmount: number | null 8 | taxAmount: number | null 9 | currency: ICurrencyInfo 10 | dismissModal: () => void 11 | } 12 | 13 | const CartTaxAndChargesModal: React.FC = props => { 14 | const { dismissModal, extraChargesAmount, taxAmount, currency } = props 15 | 16 | return ( 17 | 18 |
19 |
20 |
Tax
21 |
22 | 23 | {taxAmount} 24 |
25 |
26 | 27 |
28 |
Extra Charges
29 |
30 | 31 | {extraChargesAmount} 32 |
33 |
34 | 35 |
36 | 37 |
38 |
Total
39 |
40 | 41 | {taxAmount + extraChargesAmount} 42 |
43 |
44 |
45 |
46 | ) 47 | } 48 | 49 | export default CartTaxAndChargesModal 50 | -------------------------------------------------------------------------------- /components/core/CoreActiveLink.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useRouter } from 'next/router' 3 | import CoreLink, { ICoreLinkProps } from './CoreLink' 4 | 5 | interface ICoreActiveLinkProps extends ICoreLinkProps { 6 | pagePaths?: string[] 7 | activeClassName?: string 8 | } 9 | 10 | /* 11 | Whenever you want your link to stay active when URL is same as link's href, use CoreActiveLink instead of CoreLink. 12 | There are 2 ways to controls active state: 13 | 1. You can provide activeClassName to this component and whenever URL is same, the className you provided will be attached. 14 | 2. Sometimes you want extra functionality than attaching className, you could also provide a functional child which will return whether 15 | the wrapped link is active or not. 16 | 17 | Usage: 18 | 19 | Live 20 | 21 | 22 | 23 | (isActive => 24 | Live 25 | ) 26 | 27 | 28 | 29 | (isActive => 30 | isActive ? `You've visited` : 'Please visit' 31 | ) 32 | 33 | */ 34 | const CoreActiveLink: React.FC = props => { 35 | const { url, pagePaths, children, activeClassName, className, ...rest } = props 36 | const { asPath, pathname } = useRouter() 37 | 38 | const asPathEquals = asPath === url 39 | const pagePathEquals = (pagePaths || []).includes(pathname) 40 | 41 | /* 42 | Sometimes there are different URL versions of the same page. 43 | URLs can be be different for a page but not the page pagePath. 44 | So if consumer provides pathPath, we'll use it for active state. 45 | */ 46 | const isActive = pagePaths ? pagePathEquals : asPathEquals 47 | 48 | const updatedClassName = isActive ? activeClassName : '' 49 | 50 | return ( 51 | 52 | {typeof children === 'function' ? children(isActive) : children} 53 | 54 | ) 55 | } 56 | 57 | export default CoreActiveLink 58 | -------------------------------------------------------------------------------- /components/core/CoreCheckbox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classnames from 'classnames' 3 | 4 | interface ICoreCheckboxProps { 5 | onChange: (val: boolean) => void 6 | id: string 7 | checked: boolean 8 | label?: string 9 | className?: string 10 | disabled?: boolean 11 | } 12 | 13 | const CoreCheckbox: React.FC = props => { 14 | const { onChange, id, label, checked, className, disabled } = props 15 | 16 | return ( 17 |
18 | onChange(e.target.checked)} checked={checked} disabled={disabled} /> 19 | {label ? ( 20 | 23 | ) : null} 24 |
25 | ) 26 | } 27 | 28 | export default CoreCheckbox 29 | -------------------------------------------------------------------------------- /components/core/CoreDivider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classnames from 'classnames' 3 | 4 | interface ICoreDividerProps { 5 | className?: string 6 | } 7 | 8 | const CoreDivider: React.FC = props => { 9 | const { className } = props 10 | 11 | return
12 | } 13 | 14 | export default CoreDivider 15 | -------------------------------------------------------------------------------- /components/core/CoreImage.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, useRef, useState, useEffect } from 'react' 2 | import classnames from 'classnames' 3 | 4 | export enum ImageSourceType { 5 | ASSET = 'asset', // assets stored locally such as logo, icons, etc.. 6 | CLOUD = 'cloud', // stored in cloud 7 | NONE = 'none', // for absolute urls 8 | } 9 | 10 | export interface ICoreImageProps { 11 | url: string 12 | alt: string 13 | className?: string 14 | style?: CSSProperties 15 | onError?: (param: any) => void 16 | onLoad?: () => void 17 | } 18 | 19 | const CoreImage: React.FC = props => { 20 | const { url, alt, className, style, onError, onLoad } = props 21 | 22 | const ref = useRef(null) 23 | 24 | const onImgLoad = () => { 25 | if (onLoad) { 26 | onLoad() 27 | } 28 | } 29 | 30 | const imgError = (image: any) => { 31 | image.onerror = null 32 | image.target.removeAttribute('src') 33 | image.target.removeAttribute('alt', '') 34 | onError && onError(image) 35 | } 36 | 37 | return ( 38 | {alt 47 | ) 48 | } 49 | 50 | export default CoreImage 51 | -------------------------------------------------------------------------------- /components/core/CoreInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react' 2 | import { BackspaceIcon } from '@heroicons/react/solid' 3 | import classnames from 'classnames' 4 | 5 | export enum CoreTextInputType { 6 | TEXT = 'text', 7 | PASSWORD = 'password', 8 | EMAIL = 'email', 9 | TEL = 'tel', 10 | NUMBER = 'number', 11 | } 12 | 13 | interface ICoreInputProps { 14 | type: CoreTextInputType 15 | value: string 16 | setValue: (value: string) => void 17 | placeholder: string 18 | disabled?: boolean 19 | autoFocus?: boolean 20 | autoComplete?: string 21 | showClearIcon?: boolean 22 | maxLength?: number 23 | onClearClick?: (value: string) => void 24 | inputClassName?: string 25 | className?: string 26 | } 27 | 28 | const CoreTextInput = React.forwardRef((props, ref) => { 29 | const { 30 | type, 31 | value, 32 | setValue, 33 | placeholder, 34 | disabled, 35 | autoFocus, 36 | autoComplete, 37 | showClearIcon = false, 38 | onClearClick, 39 | inputClassName, 40 | className, 41 | maxLength, 42 | } = props 43 | 44 | const inputRef: any = ref || useRef(null) 45 | 46 | return ( 47 |
48 | setValue(e.target.value)} 59 | disabled={disabled} 60 | placeholder={placeholder} 61 | autoComplete={autoComplete} 62 | spellCheck="false" 63 | autoFocus={autoFocus} 64 | maxLength={maxLength} 65 | /> 66 | {value && showClearIcon ? ( 67 |
68 | { 71 | if (onClearClick) onClearClick(value) 72 | inputRef.current.focus() 73 | }} 74 | /> 75 |
76 | ) : null} 77 |
78 | ) 79 | }) 80 | 81 | CoreTextInput.displayName = 'CoreTextInput' 82 | 83 | export default CoreTextInput 84 | -------------------------------------------------------------------------------- /components/core/CoreLink.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, CSSProperties } from 'react' 2 | import NextLink from 'next/link' 3 | import appConfig from '../../config/appConfig' 4 | 5 | export interface ICoreLinkProps { 6 | url: string 7 | className?: string 8 | isExternal?: boolean 9 | style?: CSSProperties 10 | title?: string 11 | onClick?: (e: any) => void 12 | children: ReactNode 13 | } 14 | 15 | const CoreLink: React.FC = props => { 16 | const { url, className, isExternal, style, title, onClick, children } = props 17 | 18 | const handleClick = e => { 19 | if (onClick) onClick(e) 20 | } 21 | 22 | if ((url && url.indexOf('http') === 0) || !url) { 23 | return ( 24 | 32 | {children} 33 | 34 | ) 35 | } 36 | 37 | return ( 38 | 39 | 47 | {children} 48 | 49 | 50 | ) 51 | } 52 | 53 | export default CoreLink 54 | -------------------------------------------------------------------------------- /components/core/CoreRadio.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classnames from 'classnames' 3 | 4 | interface ICoreRadioProps { 5 | value: string 6 | onChange: (val: string) => void 7 | id: string 8 | checked: boolean 9 | label?: string 10 | className?: string 11 | } 12 | 13 | const CoreRadio: React.FC = props => { 14 | const { value, onChange, id, label, checked, className } = props 15 | 16 | return ( 17 |
18 | onChange(e.target.value)} checked={checked} /> 19 | {label ? ( 20 | 23 | ) : null} 24 |
25 | ) 26 | } 27 | 28 | export default CoreRadio 29 | -------------------------------------------------------------------------------- /components/core/CoreSelectInput.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classnames from 'classnames' 3 | 4 | export interface ICoreSelectInputOption { 5 | id: string | number 6 | value: string 7 | label: string 8 | selected: boolean 9 | } 10 | 11 | interface ICoreSelectInputProps { 12 | value: string 13 | onChange: (value: string) => void 14 | options: ICoreSelectInputOption[] 15 | disabled?: boolean 16 | className?: string 17 | } 18 | 19 | const CoreSelectInput: React.FC = props => { 20 | const { value, onChange, options, disabled = false, className } = props 21 | 22 | return ( 23 |
31 | 45 | 46 |
47 | ) 48 | } 49 | 50 | export default CoreSelectInput 51 | -------------------------------------------------------------------------------- /components/core/CoreTextarea.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface ICoreTextareaProps { 4 | value: string 5 | setValue: (val: string) => void 6 | placeholder: string 7 | disabled?: boolean 8 | autoFocus?: boolean 9 | autoComplete?: string 10 | maxLength?: number 11 | className?: string 12 | } 13 | 14 | const CoreTextarea: React.FC = props => { 15 | const { value, setValue, placeholder, disabled, autoFocus, autoComplete, maxLength, className } = props 16 | 17 | return ( 18 | 28 | ) 29 | } 30 | 31 | export default CoreTextarea 32 | -------------------------------------------------------------------------------- /components/custom-icons/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ICustomIconProps } from './interface' 3 | import classnames from 'classnames' 4 | 5 | export const LoaderCustomIcon: React.FC = props => { 6 | return ( 7 | 13 | 14 | 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /components/custom-icons/interface.ts: -------------------------------------------------------------------------------- 1 | import { MouseEvent } from 'react' 2 | 3 | export interface ICustomIconProps { 4 | className?: string 5 | onClick?: (e: MouseEvent) => void 6 | } 7 | -------------------------------------------------------------------------------- /components/error/Error.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ISectionInfo } from '../../contract/section' 3 | import { prepareImageUrl } from '../../utils/image' 4 | import CoreImage, { ImageSourceType } from '../core/CoreImage' 5 | 6 | interface IErrorProps {} 7 | 8 | const Error: React.FC = props => { 9 | return ( 10 |
11 |
12 | 17 |
18 | Site under maintenance. 19 |
20 |
{`We're working on a few fixes and updates. Sorry for the Inconvenience.`}
21 |
22 |
23 | ) 24 | } 25 | 26 | export default Error 27 | -------------------------------------------------------------------------------- /components/error/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Error from './Error' 3 | 4 | interface IErrorBoundaryProps {} 5 | 6 | interface IErrorBoundaryState { 7 | hasError: boolean 8 | } 9 | 10 | class ErrorBoundary extends React.PureComponent { 11 | constructor(props: IErrorBoundaryProps) { 12 | super(props) 13 | this.state = { hasError: false } 14 | } 15 | 16 | static getDerivedStateFromError(error) { 17 | // Update state so the next render will show the fallback UI. 18 | return { hasError: true } 19 | } 20 | 21 | componentDidCatch(error, errorInfo) { 22 | console.error('ErrorBoundary', error, errorInfo) 23 | } 24 | 25 | render() { 26 | if (this.state.hasError) { 27 | return 28 | } 29 | 30 | return this.props.children 31 | } 32 | } 33 | 34 | export default ErrorBoundary 35 | -------------------------------------------------------------------------------- /components/header/Header.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useRef } from 'react' 2 | import ApplicationContext, { LoginStateType } from '../ApplicationContext' 3 | import CoreImage from '../core/CoreImage' 4 | import CoreLink from '../core/CoreLink' 5 | import { getHomePageUrl } from '../../utils/home' 6 | import { APP_LOGO } from '../../constants/constants' 7 | import appConfig from '../../config/appConfig' 8 | import { getLoginPageUrl } from '../../utils/login' 9 | 10 | interface IHeaderProps {} 11 | 12 | const Header: React.FC = props => { 13 | const applicationContext = useContext(ApplicationContext) 14 | const { isLoggedIn, loginState, logout } = applicationContext 15 | 16 | return ( 17 |
18 | 49 | 50 |
51 |
52 | ) 53 | } 54 | 55 | export default Header 56 | -------------------------------------------------------------------------------- /components/header/HeaderLinks.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames' 2 | import React, { ReactNode } from 'react' 3 | import CoreActiveLink from '../core/CoreActiveLink' 4 | 5 | export interface IHeaderLink { 6 | label: string 7 | url: string | null 8 | iconComponent: React.FC 9 | activeIconComponent: React.FC 10 | iconClassName: string | null 11 | count: string | null 12 | onClick: (e: any) => void 13 | } 14 | 15 | interface IHeaderLinksProps { 16 | links: IHeaderLink[] 17 | } 18 | 19 | const HeaderLinks: React.FC = props => { 20 | const mappedLinks = props.links.map((navLink, index) => { 21 | return ( 22 | { 28 | if (navLink.onClick) { 29 | navLink.onClick(e) 30 | } 31 | }}> 32 | {(isActive: boolean) => { 33 | const IconComponent = isActive ? navLink.activeIconComponent : navLink.iconComponent 34 | 35 | return ( 36 | 37 | 43 | {navLink.label} 44 | 45 | ) 46 | }} 47 | 48 | ) 49 | }) 50 | 51 | return <>{mappedLinks} 52 | } 53 | 54 | export default HeaderLinks 55 | -------------------------------------------------------------------------------- /components/header/Snackbar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { useRouter } from 'next/router' 3 | import { ArrowLeftIcon, SearchIcon, PlusIcon, HomeIcon, LoginIcon, LogoutIcon } from '@heroicons/react/outline' 4 | import CoreLink from '../core/CoreLink' 5 | import { getHomePageUrl } from '../../utils/home' 6 | import classnames from 'classnames' 7 | import ApplicationContext from '../ApplicationContext' 8 | import { getLoginPageUrl } from '../../utils/login' 9 | 10 | interface ISnackbarProps { 11 | title?: React.ReactNode 12 | backUrl?: string 13 | } 14 | 15 | const Snackbar: React.FC = props => { 16 | const { title, backUrl } = props 17 | 18 | const router = useRouter() 19 | 20 | const applicationContext = useContext(ApplicationContext) 21 | const { isLoggedIn } = applicationContext 22 | 23 | const handleBackIconClick = () => { 24 | backUrl ? router.push(backUrl) : router.back() 25 | } 26 | 27 | const isLoginPage = router.pathname === getLoginPageUrl() 28 | 29 | return ( 30 |
31 |
32 | {!isLoginPage ? ( 33 |
34 | 35 |
36 | ) : null} 37 |
38 |
{title}
39 |
40 |
41 |
42 | 43 | {isLoggedIn ? : } 44 | 45 | {!isLoginPage ? ( 46 | 47 | 48 | 49 | ) : null} 50 |
51 |
52 | ) 53 | } 54 | 55 | export default Snackbar 56 | -------------------------------------------------------------------------------- /components/image/ImageInfo.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames' 2 | import React from 'react' 3 | import { IImageInfo } from '../../contract/image' 4 | import { getImageUpdatePageUrl, prepareImageUrl } from '../../utils/image' 5 | import CoreImage, { ImageSourceType } from '../core/CoreImage' 6 | import CoreLink from '../core/CoreLink' 7 | 8 | interface IImageInfoProps { 9 | image: IImageInfo 10 | } 11 | 12 | const ImageInfo: React.FC = props => { 13 | const { image } = props 14 | 15 | return ( 16 | 19 | 24 |
25 |
#{image.id}
26 |
27 |
28 | ) 29 | } 30 | 31 | export default ImageInfo 32 | -------------------------------------------------------------------------------- /components/layout/Delete.tsx: -------------------------------------------------------------------------------- 1 | import { TrashIcon } from '@heroicons/react/outline' 2 | import React, { useState } from 'react' 3 | import CoreButton, { CoreButtonSize, CoreButtonType } from '../core/CoreButton' 4 | import Alert from '../modal/Alert' 5 | 6 | interface IDeleteProps { 7 | onDelete: () => void 8 | label?: string 9 | icon?: React.FC 10 | } 11 | 12 | const Delete: React.FC = props => { 13 | const [showAlert, toggleAlert] = useState(false) 14 | 15 | const Icon = props.icon || TrashIcon 16 | 17 | return ( 18 |
19 |
20 | 23 | 24 | {props.label || 'Delete'} 25 | 26 | } 27 | size={CoreButtonSize.MEDIUM} 28 | type={CoreButtonType.SOLID_SECONDARY} 29 | onClick={() => toggleAlert(true)} 30 | /> 31 |
32 | 33 | {showAlert ? ( 34 | { 42 | props.onDelete() 43 | }, 44 | }, 45 | secondary: { 46 | show: true, 47 | label: 'Cancel', 48 | onClick: () => toggleAlert(false), 49 | }, 50 | }} 51 | dismissModal={() => toggleAlert(false)} 52 | /> 53 | ) : null} 54 |
55 | ) 56 | } 57 | 58 | export default Delete 59 | -------------------------------------------------------------------------------- /components/layout/LayoutPagination.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ChevronDoubleLeftIcon, 3 | ChevronDoubleRightIcon, 4 | ChevronLeftIcon, 5 | ChevronRightIcon, 6 | } from '@heroicons/react/outline' 7 | import React, { useContext } from 'react' 8 | import classnames from 'classnames' 9 | import { useRouter } from 'next/router' 10 | import { updateUrlParam } from '../../utils/common' 11 | import CoreLink from '../core/CoreLink' 12 | 13 | interface ILayoutPaginationProps {} 14 | 15 | const LayoutPagination: React.FC = props => { 16 | const router = useRouter() 17 | 18 | const currentPage = Number(router.query.page || 1) 19 | 20 | const visiblePages = [currentPage - 2, currentPage - 1, currentPage, currentPage + 1, currentPage + 2].filter( 21 | l => l > 0 22 | ) 23 | 24 | const getPageUrl = (page: number) => { 25 | if (page >= 1) { 26 | const url = updateUrlParam(router.asPath, 'page', `${page}`) 27 | return url 28 | } 29 | return undefined 30 | } 31 | 32 | return ( 33 |
34 |
35 | {currentPage > 1 ? ( 36 | 37 | 38 | 39 | ) : null} 40 | 43 | 44 | 45 | {visiblePages.map(visiblePage => ( 46 | 52 | {visiblePage} 53 | 54 | ))} 55 | 58 | 59 | 60 |
61 |
62 | ) 63 | } 64 | 65 | export default LayoutPagination 66 | -------------------------------------------------------------------------------- /components/layout/PageLayout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import { DesktopView, MobileView } from '../ResponsiveViews' 3 | import { useRouter } from 'next/router' 4 | import PageLinks, { IPageLink } from './PageLinks' 5 | import classnames from 'classnames' 6 | import PrivatePageLayout from './PrivatePageLayout' 7 | import CoreLink from '../core/CoreLink' 8 | 9 | export interface IPageSummaryInfo { 10 | pageUrl: string | null 11 | description: string 12 | } 13 | 14 | interface IPageLayoutProps { 15 | links: IPageLink[] 16 | summary?: IPageSummaryInfo 17 | } 18 | 19 | const AccountLayoutDesktop: React.FC = props => { 20 | const { children, links } = props 21 | 22 | return ( 23 |
24 |
25 |
26 | 27 |
28 |
{children}
29 |
30 |
31 | ) 32 | } 33 | 34 | const AccountLayoutMobile: React.FC = props => { 35 | const { children, links } = props 36 | 37 | return ( 38 |
39 |
{children}
40 | {/* TODO: Faiyaz - add better ui */} 41 |
{/* */}
42 |
43 | ) 44 | } 45 | 46 | const PageLayout: React.FC = props => { 47 | return ( 48 | 49 | {props.summary?.pageUrl || props.summary?.description ? ( 50 |
51 |
{props.summary.description}
52 |
53 | 54 | {props.summary.pageUrl} 55 | 56 |
57 |
58 | ) : null} 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
67 | ) 68 | } 69 | 70 | export default PageLayout 71 | -------------------------------------------------------------------------------- /components/layout/PageLinks.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CoreActiveLink from '../core/CoreActiveLink' 3 | import classnames from 'classnames' 4 | import { useRouter } from 'next/router' 5 | import { ChevronRightIcon } from '@heroicons/react/solid' 6 | 7 | export interface IPageLink { 8 | label: string 9 | url: string 10 | icon?: React.FC 11 | pagePaths?: string[] 12 | } 13 | 14 | interface IPageLinksProps { 15 | links: IPageLink[] 16 | } 17 | 18 | const PageLinks: React.FC = props => { 19 | const { links } = props 20 | 21 | const router = useRouter() 22 | 23 | const mappedLinks = links.map((link, index) => { 24 | return ( 25 | 33 | {isActive => { 34 | let IconComponent = null 35 | 36 | if (link.icon) { 37 | IconComponent = link.icon 38 | } 39 | 40 | return ( 41 | 42 | {IconComponent ? ( 43 |
44 | 45 |
46 | ) : null} 47 | 48 |
49 |
{link.label}
50 | 51 |
52 |
53 | ) 54 | }} 55 |
56 | ) 57 | }) 58 | 59 | return <>{mappedLinks} 60 | } 61 | 62 | export default PageLinks 63 | -------------------------------------------------------------------------------- /components/layout/PrivatePageLayout.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import React, { useContext } from 'react' 3 | import { getLoginPageUrl } from '../../utils/login' 4 | import ApplicationContext, { LoginStateType } from '../ApplicationContext' 5 | import CoreButton, { CoreButtonSize, CoreButtonType } from '../core/CoreButton' 6 | import PageLoader from '../loader/PageLoader' 7 | import NoContent, { NoContentType } from '../NoContent' 8 | 9 | interface IPrivatePageLayoutProps {} 10 | 11 | const PrivatePageLayout: React.FC = props => { 12 | const { children } = props 13 | 14 | const applicationContext = useContext(ApplicationContext) 15 | const { loginState } = applicationContext 16 | 17 | const router = useRouter() 18 | 19 | if (loginState === LoginStateType.NONE) { 20 | return 21 | } 22 | 23 | if (loginState === LoginStateType.LOGGED_IN) { 24 | return <>{children} 25 | } 26 | 27 | return ( 28 |
29 | 30 |
31 | 37 |
38 |
39 | ) 40 | } 41 | 42 | export default PrivatePageLayout 43 | -------------------------------------------------------------------------------- /components/loader/Loader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import classnames from 'classnames' 3 | 4 | // https://loading.io/css/ 5 | export enum LoaderType { 6 | ROLLER = 'ROLLER', 7 | RING = 'RING', 8 | ELLIPSIS = 'ELLIPSIS', 9 | } 10 | 11 | interface ILoaderProps { 12 | type: LoaderType 13 | className?: string 14 | } 15 | 16 | const Loader: React.FC = props => { 17 | const { type, className } = props 18 | 19 | if (type === LoaderType.RING) { 20 | return ( 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ) 29 | } 30 | 31 | if (type === LoaderType.ROLLER) { 32 | return ( 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | ) 44 | } 45 | 46 | if (type === LoaderType.ELLIPSIS) { 47 | return ( 48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | ) 57 | } 58 | 59 | return null 60 | } 61 | 62 | export default Loader 63 | -------------------------------------------------------------------------------- /components/loader/PageLoader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Loader, { LoaderType } from './Loader' 3 | 4 | interface IPageLoaderProps { 5 | message: string 6 | } 7 | 8 | const PageLoader: React.FC = props => { 9 | const { message } = props 10 | 11 | return ( 12 |
13 | 14 |
{message}
15 |
16 | ) 17 | } 18 | 19 | export default PageLoader 20 | -------------------------------------------------------------------------------- /components/modal/Alert.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Modal from './Modal' 3 | import classnames from 'classnames' 4 | import CoreButton, { CoreButtonSize, CoreButtonType } from '../core/CoreButton' 5 | 6 | interface IAlertProps { 7 | dismissModal: () => void 8 | title?: string 9 | subTitle?: string 10 | cta: { 11 | primary: { 12 | show: boolean 13 | label?: string 14 | loading?: boolean 15 | onClick?: () => void 16 | } 17 | secondary: { 18 | show: boolean 19 | label?: string 20 | onClick?: () => void 21 | } 22 | } 23 | className?: string 24 | } 25 | 26 | const Alert: React.FC = props => { 27 | const { dismissModal, title, subTitle, cta, className } = props 28 | 29 | return ( 30 | 36 |
37 | {cta.secondary.show ? ( 38 | 44 | ) : null} 45 | {cta.primary.show ? ( 46 | 54 | ) : null} 55 |
56 |
57 | ) 58 | } 59 | 60 | export default Alert 61 | -------------------------------------------------------------------------------- /components/modal/Modal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react' 2 | import useEscape from '../../hooks/useEscape' 3 | import useOutsideClick from '../../hooks/useOutsideClick' 4 | import usePortal from '../../hooks/usePortal' 5 | import { XIcon } from '@heroicons/react/solid' 6 | import { addBlur, removeBlur } from '../../utils/common' 7 | import classnames from 'classnames' 8 | import useDisablePageScrolling from '../../hooks/useDisablePageScrolling' 9 | 10 | interface IModalProps { 11 | dismissModal: () => void 12 | title?: string 13 | subTitle?: string 14 | className?: string 15 | showCrossIcon?: boolean 16 | disableOutsideClick?: boolean 17 | } 18 | 19 | const Modal: React.FC = props => { 20 | const { 21 | dismissModal, 22 | title, 23 | subTitle, 24 | className, 25 | showCrossIcon = true, 26 | children, 27 | disableOutsideClick = false, 28 | } = props 29 | 30 | const ref = useRef() 31 | 32 | const Portal = usePortal() 33 | 34 | useEffect(() => { 35 | addBlur() 36 | return () => { 37 | removeBlur() 38 | } 39 | }, []) 40 | 41 | useDisablePageScrolling() 42 | useEscape(() => dismissModal()) 43 | useOutsideClick({ 44 | ref, 45 | onOutsideClick: () => { 46 | if (!disableOutsideClick) { 47 | dismissModal() 48 | } 49 | }, 50 | }) 51 | 52 | return ( 53 | 54 | 76 | 77 | ) 78 | } 79 | 80 | export default Modal 81 | -------------------------------------------------------------------------------- /components/order/OrderInfo.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import { IOrderInfo } from '../../contract/order' 3 | import classnames from 'classnames' 4 | import CoreButton, { CoreButtonSize, CoreButtonType } from '../core/CoreButton' 5 | import EscapeHTML from '../EscapeHTML' 6 | import { getFormattedDateTime } from '../../utils/dates' 7 | import { getOrderPageUrl } from '../../utils/order' 8 | import { ExternalLinkIcon } from '@heroicons/react/outline' 9 | import CoreLink from '../core/CoreLink' 10 | 11 | interface IOrderInfoProps { 12 | orderInfo: IOrderInfo 13 | } 14 | 15 | const OrderInfo: React.FC = props => { 16 | const { orderInfo: order } = props 17 | const { id, totalAmount, currency, createdDateTime, orderStatusHistory, cartItemsMeta } = order 18 | 19 | const recentOrderHistory = orderStatusHistory[0] 20 | 21 | const items = cartItemsMeta 22 | .map(cartItem => { 23 | return `${cartItem.quantity} x ${cartItem.productName}` 24 | }) 25 | .join(', ') 26 | 27 | const renderItem = (title: ReactNode, subTitle: ReactNode, className?: string) => { 28 | return ( 29 |
30 |
{title}
31 |
{subTitle}
32 |
33 | ) 34 | } 35 | 36 | return ( 37 |
38 |
39 | {recentOrderHistory.status.name} 40 |
41 | 42 |
43 | {renderItem('Order Number', id)} 44 | {renderItem( 45 | 'Total Amount', 46 |
47 | 48 | {totalAmount} 49 |
50 | )} 51 | {renderItem('Items', items)} 52 | {renderItem('Ordered On', getFormattedDateTime(createdDateTime))} 53 |
54 | 55 |
56 | 57 | 58 | 59 | 66 |
67 |
68 | ) 69 | } 70 | 71 | export default OrderInfo 72 | -------------------------------------------------------------------------------- /components/product/ProductAttributeUpdateModal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ProductTagIconType } from '../../contract/constants' 3 | import { IProductAttributeInfo, IProductTagInfo } from '../../contract/product' 4 | import { ISectionInfo, SectionInfoItem } from '../../contract/section' 5 | import { CoreTextInputType } from '../core/CoreInput' 6 | import Delete from '../layout/Delete' 7 | import FormLayout, { IFormLayoutInput } from '../layout/FormLayout' 8 | import Modal from '../modal/Modal' 9 | 10 | interface IProductAttributeUpdateModalProps { 11 | attribute: IProductAttributeInfo | null 12 | dismissModal: () => void 13 | onUpdate: (map: any) => void 14 | onDelete: (tag: IProductAttributeInfo) => void 15 | } 16 | 17 | const ProductAttributeUpdateModal: React.FC = props => { 18 | const { attribute, dismissModal, onUpdate: onUpdate, onDelete: _onDelete } = props 19 | 20 | const formInputs: IFormLayoutInput[] = [ 21 | { 22 | key: 'id', 23 | value: attribute?.id, 24 | disabled: true, 25 | title: 'ID', 26 | placeholder: '#ID', 27 | type: 'INPUT', 28 | }, 29 | { 30 | key: 'keyId', 31 | value: attribute?.key?.id, 32 | title: 'Attribute Key ID', 33 | subTitle: 'Attribute key associated with this', 34 | placeholder: 'Attribute Key ID', 35 | type: 'INPUT', 36 | optional: true, 37 | inputProps: { 38 | type: CoreTextInputType.NUMBER, 39 | }, 40 | }, 41 | { 42 | key: 'value', 43 | value: attribute?.value, 44 | title: 'Attribute Value', 45 | subTitle: 'Value of this attribute', 46 | placeholder: 'Attribute Value', 47 | type: 'INPUT', 48 | }, 49 | { 50 | key: 'position', 51 | value: attribute?.position, 52 | title: 'Position', 53 | placeholder: 'Position', 54 | type: 'INPUT', 55 | inputProps: { 56 | type: CoreTextInputType.NUMBER, 57 | }, 58 | }, 59 | { 60 | key: 'isActive', 61 | value: attribute?.isActive || false, 62 | title: 'Active', 63 | placeholder: 'Active', 64 | type: 'CHECKBOX', 65 | }, 66 | ] 67 | 68 | const onSubmit = inputMap => { 69 | onUpdate(inputMap) 70 | } 71 | 72 | const onDelete = () => { 73 | _onDelete(attribute) 74 | } 75 | 76 | return ( 77 | 82 |
83 | {attribute?.id ? : null} 84 | 85 |
86 |
87 | ) 88 | } 89 | 90 | export default ProductAttributeUpdateModal 91 | -------------------------------------------------------------------------------- /components/product/ProductFeatureSectionUpdateModal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ProductTagIconType } from '../../contract/constants' 3 | import { IProductFeatureSectionInfo, IProductTagInfo } from '../../contract/product' 4 | import { ISectionInfo, SectionInfoItem } from '../../contract/section' 5 | import { CoreTextInputType } from '../core/CoreInput' 6 | import Delete from '../layout/Delete' 7 | import FormLayout, { IFormLayoutInput } from '../layout/FormLayout' 8 | import Modal from '../modal/Modal' 9 | 10 | interface IProductFeatureSectionUpdateModalProps { 11 | featureSection: IProductFeatureSectionInfo | null 12 | dismissModal: () => void 13 | onUpdate: (map: any) => void 14 | onDelete: (tag: IProductFeatureSectionInfo) => void 15 | } 16 | 17 | const ProductFeatureSectionUpdateModal: React.FC = props => { 18 | const { featureSection, dismissModal, onUpdate: onUpdate, onDelete: _onDelete } = props 19 | 20 | const formInputs: IFormLayoutInput[] = [ 21 | { 22 | key: 'id', 23 | value: featureSection?.id, 24 | disabled: true, 25 | title: 'ID', 26 | placeholder: '#ID', 27 | type: 'INPUT', 28 | }, 29 | { 30 | key: 'title', 31 | value: featureSection?.title, 32 | title: 'Title', 33 | subTitle: 'Section title', 34 | placeholder: 'Title', 35 | type: 'INPUT', 36 | }, 37 | { 38 | key: 'body', 39 | value: featureSection?.body, 40 | title: 'Body', 41 | subTitle: 'Section body as raw HTML', 42 | placeholder: 'Body', 43 | type: 'TEXTAREA', 44 | textAreaProps: { 45 | className: 'h-[200px]', 46 | }, 47 | }, 48 | { 49 | key: 'position', 50 | value: featureSection?.position, 51 | title: 'Position', 52 | placeholder: 'Position', 53 | type: 'INPUT', 54 | inputProps: { 55 | type: CoreTextInputType.NUMBER, 56 | }, 57 | }, 58 | { 59 | key: 'isActive', 60 | value: featureSection?.isActive || false, 61 | title: 'Active', 62 | placeholder: 'Active', 63 | type: 'CHECKBOX', 64 | }, 65 | ] 66 | 67 | const onSubmit = inputMap => { 68 | onUpdate(inputMap) 69 | } 70 | 71 | const onDelete = () => { 72 | _onDelete(featureSection) 73 | } 74 | 75 | return ( 76 | 81 |
82 | {featureSection?.id ? : null} 83 | 84 |
85 |
86 | ) 87 | } 88 | 89 | export default ProductFeatureSectionUpdateModal 90 | -------------------------------------------------------------------------------- /components/product/ProductTagUpdateModal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ProductTagIconType, ProductType } from '../../contract/constants' 3 | import { IProductTagInfo } from '../../contract/product' 4 | import { ISectionInfo, SectionInfoItem } from '../../contract/section' 5 | import { CoreTextInputType } from '../core/CoreInput' 6 | import Delete from '../layout/Delete' 7 | import FormLayout, { IFormLayoutInput } from '../layout/FormLayout' 8 | import Modal from '../modal/Modal' 9 | 10 | interface IProductTagUpdateModalProps { 11 | tag: IProductTagInfo | null 12 | dismissModal: () => void 13 | onUpdate: (map: any) => void 14 | onDelete: (tag: IProductTagInfo) => void 15 | } 16 | 17 | const ProductTagUpdateModal: React.FC = props => { 18 | const { tag, dismissModal, onUpdate, onDelete: _onDelete } = props 19 | 20 | const formInputs: IFormLayoutInput[] = [ 21 | { 22 | key: 'id', 23 | value: tag?.id, 24 | disabled: true, 25 | title: 'ID', 26 | placeholder: '#ID', 27 | type: 'INPUT', 28 | }, 29 | { 30 | key: 'label', 31 | value: tag?.label, 32 | title: 'Label', 33 | placeholder: 'Label', 34 | type: 'INPUT', 35 | }, 36 | { 37 | key: 'iconType', 38 | value: tag?.iconType, 39 | title: 'Icon Type', 40 | placeholder: 'Icon Type', 41 | type: 'SELECT', 42 | selectProps: { 43 | options: Object.entries(ProductTagIconType).map(([key, value]) => ({ 44 | id: value, 45 | label: value, 46 | value: value, 47 | selected: tag?.iconType === value, 48 | })), 49 | }, 50 | }, 51 | { 52 | key: 'position', 53 | value: tag?.position, 54 | title: 'Position', 55 | placeholder: 'Position', 56 | type: 'INPUT', 57 | inputProps: { 58 | type: CoreTextInputType.NUMBER, 59 | }, 60 | }, 61 | { 62 | key: 'isActive', 63 | value: tag?.isActive || false, 64 | title: 'Active', 65 | placeholder: 'Active', 66 | type: 'CHECKBOX', 67 | }, 68 | ] 69 | 70 | const onSubmit = inputMap => { 71 | onUpdate(inputMap) 72 | } 73 | 74 | const onDelete = () => { 75 | _onDelete(tag) 76 | } 77 | 78 | return ( 79 | 84 |
85 | {tag?.id ? : null} 86 | 87 |
88 |
89 | ) 90 | } 91 | 92 | export default ProductTagUpdateModal 93 | -------------------------------------------------------------------------------- /components/section/SectionItemUpdateModal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ISectionInfo, SectionInfoItem } from '../../contract/section' 3 | import Modal from '../modal/Modal' 4 | import SectionItemUpdateForm, { SectionItemUpdateFormLayoutType } from './SectionItemUpdateForm' 5 | 6 | interface ISectionItemUpdateModalProps { 7 | dismissModal: () => void 8 | sectionItem: SectionInfoItem 9 | section: ISectionInfo 10 | } 11 | 12 | const SectionItemUpdateModal: React.FC = props => { 13 | const { dismissModal, sectionItem, section } = props 14 | 15 | return ( 16 | 21 |
22 | 27 |
28 |
29 | ) 30 | } 31 | 32 | export default SectionItemUpdateModal 33 | -------------------------------------------------------------------------------- /config/appConfig.ts: -------------------------------------------------------------------------------- 1 | const app = { 2 | name: 'OwnStore Demo - CMS', 3 | key: 'OWN-STORE-DEMO-CMS', // should be uppercase 4 | } 5 | 6 | const appConfig = { 7 | isDev: process.env.STORE_CMS_ENV.includes('local'), 8 | env: process.env.STORE_CMS_ENV, 9 | global: { 10 | app: app, 11 | domain: process.env.STORE_CMS_DOMAIN, 12 | baseUrl: process.env.STORE_CMS_BASE_URL, 13 | imageBaseUrl: process.env.STORE_CMS_IMAGE_BASE_URL, 14 | apiBaseUrl: process.env.STORE_CMS_API_BASE_URL, 15 | webBaseUrl: process.env.STORE_CMS_WEB_BASE_URL, 16 | redirectToIndexViewAfterUpdate: true, 17 | redirectToIndexViewAfterDelete: true, 18 | paginationFetchLimit: 20, 19 | }, 20 | order: { 21 | recentOrders: { 22 | autoRefresh: true, 23 | refreshIntervalInSeconds: 30, 24 | }, 25 | }, 26 | search: { 27 | limit: 30, 28 | }, 29 | image: { 30 | imageUploadDirectory: { 31 | PRODUCT: 'product', 32 | CATALOGUE: 'catalogue', 33 | BLOG: 'blog', 34 | SECTION: 'section', 35 | MISC: 'misc', 36 | NONE: 'none', 37 | }, 38 | }, 39 | integrations: { 40 | cloudinary: { 41 | cloudName: process.env.STORE_CMS_INTEGRATION_CLOUDINARY_CLOUND_NAME, 42 | uploadPresetName: process.env.STORE_CMS_INTEGRATION_CLOUDINARY_UPLOAD_PRESET_NAME, 43 | }, 44 | googleAnalytics: { 45 | enabled: process.env.STORE_CMS_INTEGRATION_GOOGLE_ANALYTICS_ENABLED === 'true', 46 | code: process.env.STORE_CMS_INTEGRATION_GOOGLE_ANALYTICS_CODE, 47 | }, 48 | }, 49 | } 50 | 51 | export default appConfig 52 | -------------------------------------------------------------------------------- /constants/constants.ts: -------------------------------------------------------------------------------- 1 | import { ImageSourceType } from '../components/core/CoreImage' 2 | import { IPageSummaryInfo } from '../components/layout/PageLayout' 3 | import appConfig from '../config/appConfig' 4 | import { prepareImageUrl } from '../utils/image' 5 | 6 | export const SCREEN_SIZE = { 7 | SM: 640, 8 | MD: 768, 9 | LG: 1024, 10 | XL: 1280, 11 | '2XL': 1536, 12 | } 13 | 14 | export const APP_LOGO = prepareImageUrl(`/images/logo.png`, ImageSourceType.ASSET) 15 | 16 | export const REGEX_MAP = { 17 | NAME: /^[^0-9]{3,50}$/, 18 | EMAIL: 19 | /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, 20 | PASSWORD: /^[a-zA-Z0-9]{3,30}$/, 21 | PHONE_NUMBER: /^\s*(?:\+?(\d{1,3}))?[-. (]*(\d{3})[-. )]*(\d{3})[-. ]*(\d{4})(?: *x(\d+))?\s*$/, 22 | ADDRESS_LINE: /^.{1,100}$/, 23 | NOT_EMPTY: /(.|\s)*\S(.|\s)*/, 24 | ADDRESS_TYPE: /(HOME|WORK|OTHER)/, 25 | } 26 | 27 | export const PAGE_SUMMARY: Record = { 28 | SUPPORTED_REGIONS: { 29 | pageUrl: '', 30 | description: `Regions supported by the app. You can add countries and/or cities. Add cities if only a selected part of a 31 | country is spoorted. For eg. Let's say you want to accept orders only from Mumbai, then add India country and 32 | Mumbai city. Kindly make sure to add cities only of supported countries. There's no validation at system-level. `, 33 | }, 34 | 35 | FAQ_TOPIC: { 36 | pageUrl: `${appConfig.global.webBaseUrl}/faq`, 37 | description: `Each FAQ has a topic. Select a topic first to add/update any child FAQ info.`, 38 | }, 39 | 40 | FAQ: { 41 | pageUrl: `${appConfig.global.webBaseUrl}/faq/topic/{{FAQ_TOPIC_ID}}`, 42 | description: `Add/Update any FAQ info under topic Id {{FAQ_TOPIC_ID}}.`, 43 | }, 44 | 45 | SECURITY_QUESTION: { 46 | pageUrl: '', 47 | description: `These are the questions shown to users when they try to protect their account.`, 48 | }, 49 | 50 | CURRENCY: { 51 | pageUrl: '', 52 | description: `Currency supported by your app. Will used for product SKUs, payment, etc... You can add only 1.`, 53 | }, 54 | 55 | PRIVACY_POLICY: { 56 | pageUrl: `${appConfig.global.webBaseUrl}/privacy-policy`, 57 | description: '', 58 | }, 59 | 60 | TERMS_CONDITIONS: { 61 | pageUrl: `${appConfig.global.webBaseUrl}/terms-conditions`, 62 | description: '', 63 | }, 64 | 65 | REFUND_POLICY: { 66 | pageUrl: `${appConfig.global.webBaseUrl}/refund-policy`, 67 | description: '', 68 | }, 69 | } 70 | -------------------------------------------------------------------------------- /contract/address.ts: -------------------------------------------------------------------------------- 1 | import { UserAddressType } from './constants' 2 | 3 | export interface IUserAddressInfo { 4 | id: number 5 | name: string 6 | phoneNumber: string 7 | addressLine: string 8 | area: string 9 | areaCode: number | null 10 | city: string 11 | country: string 12 | isPrimary: boolean 13 | isActive: boolean 14 | addressType: UserAddressType 15 | } 16 | 17 | export interface IUserAddressInfoUpdateParams { 18 | name: string 19 | phoneNumber: string 20 | addressLine: string 21 | area: string 22 | areaCode: number | null 23 | city: string 24 | country: string 25 | isPrimary: boolean 26 | isActive: boolean 27 | addressType: UserAddressType 28 | } 29 | 30 | export interface IUserAddressInfoUpdate { 31 | userAddress: IUserAddressInfo | null 32 | success: boolean 33 | } 34 | -------------------------------------------------------------------------------- /contract/admin.ts: -------------------------------------------------------------------------------- 1 | export interface IAdminVerified { 2 | success: boolean 3 | } 4 | 5 | export interface IAdminVerifyParams { 6 | key: string 7 | } 8 | 9 | export interface IAdminVerify { 10 | success: boolean 11 | token: string | null 12 | } 13 | -------------------------------------------------------------------------------- /contract/blog.ts: -------------------------------------------------------------------------------- 1 | import { IImageInfo } from './image' 2 | 3 | export interface IBlogInfo { 4 | id: number 5 | slug: string 6 | title: string 7 | description: string 8 | url: string 9 | image: IImageInfo | null 10 | position: number 11 | isActive: boolean 12 | createdDateTime: Date 13 | } 14 | 15 | export interface IBlogInfoUpdateParams { 16 | title: string 17 | description: string 18 | url: string 19 | imageId: number | null 20 | position: number 21 | isActive: boolean 22 | } 23 | 24 | export interface IBlogInfoUpdate { 25 | success: boolean 26 | } 27 | 28 | export interface IBlogInfoDelete { 29 | success: boolean 30 | } 31 | -------------------------------------------------------------------------------- /contract/cart.ts: -------------------------------------------------------------------------------- 1 | import { ProductType } from './constants' 2 | import { IProductInfo } from './product' 3 | 4 | export interface ICartItem { 5 | id: number 6 | quantity: number 7 | createdDateTime: Date 8 | updatedDateTime: Date 9 | product: IProductInfo 10 | } 11 | 12 | export interface IPriceAndDeliveryChargeMapping { 13 | [amount: number]: number 14 | } 15 | 16 | export interface ICartExtraChargesInfo { 17 | percent: number | null 18 | flat: number | null 19 | decimalPrecision: number | null 20 | } 21 | 22 | export interface ICartTaxInfo { 23 | percent: number | null 24 | flat: number | null 25 | decimalPrecision: number | null 26 | } 27 | 28 | export interface ICartDetail { 29 | priceAndDeliveryChargeMapping: IPriceAndDeliveryChargeMapping 30 | cartItems: ICartItem[] 31 | extraCharges: ICartExtraChargesInfo 32 | tax: ICartTaxInfo 33 | } 34 | 35 | export interface IUserCartItemAddParams { 36 | productId: number 37 | productType: ProductType 38 | totalQuantity: number 39 | } 40 | 41 | export interface IUserCartItemAdd { 42 | success: boolean 43 | cartItem: ICartItem | null 44 | } 45 | 46 | export interface IUserCartItemDelete { 47 | success: boolean 48 | } 49 | -------------------------------------------------------------------------------- /contract/catalogue.ts: -------------------------------------------------------------------------------- 1 | import { IImageInfo } from './image' 2 | 3 | export interface ICatalogueMeta { 4 | id: number 5 | name: string 6 | slug: string 7 | position: number 8 | isActive: boolean 9 | } 10 | 11 | export interface ICatalogueInfo extends ICatalogueMeta { 12 | images: IImageInfo[] 13 | } 14 | 15 | export interface ICatalogueDetail extends ICatalogueInfo {} 16 | 17 | export interface ICatalogueInfoUpdateParams { 18 | name: string 19 | imageIds: string // separated by comma 20 | position: number 21 | isActive: boolean 22 | } 23 | 24 | export interface ICatalogueInfoUpdate { 25 | success: boolean 26 | } 27 | 28 | export interface ICatalogueInfoDelete { 29 | success: boolean 30 | } 31 | -------------------------------------------------------------------------------- /contract/common.ts: -------------------------------------------------------------------------------- 1 | export interface IFindParams { 2 | offset?: number 3 | limit?: number 4 | } 5 | 6 | export interface INetworkInformationInfo { 7 | effectiveType: string | null 8 | type: string | null 9 | } 10 | -------------------------------------------------------------------------------- /contract/constants.ts: -------------------------------------------------------------------------------- 1 | export enum SectionType { 2 | PRODUCTS = 'PRODUCTS', 3 | CATALOGUES = 'CATALOGUES', 4 | BLOGS = 'BLOGS', 5 | FULL_WIDTH_SLIDES = 'FULL_WIDTH_SLIDES', 6 | STRICT_WIDTH_SLIDES = 'STRICT_WIDTH_SLIDES', 7 | USPS = 'USPS', 8 | PROCEDURES = 'PROCEDURES', 9 | CUSTOMER_FEEDBACKS = 'CUSTOMER_FEEDBACKS', 10 | SHARE = 'SHARE', 11 | OFFERS = 'OFFERS', 12 | CUSTOM = 'CUSTOM', 13 | NONE = 'NONE', 14 | } 15 | 16 | export enum UserAddressType { 17 | HOME = 'HOME', 18 | WORK = 'WORK', 19 | OTHER = 'OTHER', 20 | NONE = 'NONE', 21 | } 22 | 23 | export enum ProductType { 24 | INDIVIDUAL = 'INDIVIDUAL', 25 | COMBO = 'COMBO', 26 | NONE = 'NONE', 27 | } 28 | 29 | export enum ProductTagIconType { 30 | STAR = 'STAR', 31 | TAG = 'TAG', 32 | VERIFIED = 'VERIFIED', 33 | SPARKLE = 'SPARKLE', 34 | GLOBE = 'GLOBE', 35 | LIGHTNING_BOLT = 'LIGHTNING_BOLT', 36 | NONE = 'NONE', 37 | } 38 | 39 | export enum PlatformType { 40 | WEB = 'WEB', 41 | ANDROID = 'ANDROID', 42 | IOS = 'IOS', 43 | } 44 | 45 | export enum LoginSourceType { 46 | MANUAL = 'MANUAL', 47 | GOOGLE = 'GOOGLE', 48 | GOOGLE_ONE_TAP = 'GOOGLE_ONE_TAP', 49 | FACEBOOK = 'FACEBOOK', 50 | MANUAL_RESET = 'MANUAL_RESET', 51 | } 52 | 53 | export enum LoginType { 54 | LOGIN = 'LOGIN', 55 | SIGNUP = 'SIGNUP', 56 | } 57 | 58 | export enum PaymentMethodType { 59 | CARD = 'CARD', 60 | WALLET = 'WALLET', 61 | } 62 | 63 | export enum SupportedRegionType { 64 | COUNTRY = 'country', 65 | CITY = 'city', 66 | } 67 | 68 | export enum PageSectionType { 69 | HOME = 'home', 70 | INDIVIDUAL_PRODUCT = 'individual-product', 71 | COMBO_PRODUCT = 'combo-product', 72 | ERROR = 'error', 73 | EXPLORE = 'explore', 74 | SEARCH = 'search', 75 | } 76 | -------------------------------------------------------------------------------- /contract/currency.ts: -------------------------------------------------------------------------------- 1 | export interface ICurrencyInfo { 2 | id: number 3 | name: string 4 | isoCode: string 5 | symbol: string 6 | } 7 | 8 | export interface ICurrencyInfoUpdateParams { 9 | name: string 10 | isoCode: string 11 | symbol: string 12 | } 13 | 14 | export interface ICurrencyInfoUpdate { 15 | success: boolean 16 | } 17 | 18 | export interface ICurrencyInfoDelete { 19 | success: boolean 20 | } 21 | -------------------------------------------------------------------------------- /contract/faq.ts: -------------------------------------------------------------------------------- 1 | export interface IFAQTopicInfo { 2 | id: number 3 | name: string 4 | slug: string 5 | position: number 6 | isActive: boolean 7 | } 8 | 9 | export interface IFAQTopicInfoUpdateParams { 10 | name: string 11 | position: number 12 | isActive: boolean 13 | } 14 | 15 | export interface IFAQTopicInfoUpdate { 16 | success: boolean 17 | } 18 | 19 | export interface IFAQTopicInfoDelete { 20 | success: boolean 21 | } 22 | 23 | export interface IFAQInfo { 24 | id: number 25 | question: string 26 | answer: string 27 | position: number 28 | isActive: boolean 29 | } 30 | 31 | export interface IFAQInfoUpdateParams { 32 | topicId: number 33 | question: string 34 | answer: string 35 | position: number 36 | isActive: boolean 37 | } 38 | 39 | export interface IFAQInfoUpdate { 40 | success: boolean 41 | } 42 | 43 | export interface IFAQInfoDelete { 44 | success: boolean 45 | } 46 | -------------------------------------------------------------------------------- /contract/health.ts: -------------------------------------------------------------------------------- 1 | export interface IHealthStatus { 2 | message: string 3 | } 4 | -------------------------------------------------------------------------------- /contract/image.ts: -------------------------------------------------------------------------------- 1 | export interface IImageInfo { 2 | id: number 3 | url: string 4 | name: string 5 | createdAt: Date 6 | } 7 | 8 | export interface IImageInfoUpdateParams { 9 | name: string 10 | url: string 11 | meta: { 12 | thirdPartyId: string | null 13 | originalName: string | null 14 | sizeInBytes: string | null 15 | width: number | null 16 | height: number | null 17 | } | null 18 | } 19 | 20 | export interface IImageInfoUpdate { 21 | success: boolean 22 | id: number | null 23 | } 24 | -------------------------------------------------------------------------------- /contract/order.ts: -------------------------------------------------------------------------------- 1 | import { ICartDetail, ICartItem } from './cart' 2 | import { ICurrencyInfo } from './currency' 3 | import { IUserInfo } from './user' 4 | import { IUserAddressInfo } from './address' 5 | import { PaymentMethodType } from './constants' 6 | 7 | export interface IOrderStatusHistoryInfo { 8 | id: number 9 | status: IOrderStatusInfo 10 | createdDateTime: Date 11 | } 12 | 13 | export interface IOrderStatusInfo { 14 | id: number 15 | name: string 16 | createdDateTime: Date 17 | } 18 | 19 | export interface IOrderStatusInfoUpdateParams { 20 | name: string 21 | } 22 | 23 | export interface IOrderStatusInfoUpdate { 24 | success: boolean 25 | } 26 | 27 | export interface IOrderStatusInfoDelete { 28 | success: boolean 29 | } 30 | 31 | export interface IOrderCancellationInfo { 32 | id: number 33 | reason: string 34 | createdDateTime: Date 35 | } 36 | 37 | export interface IOrderCartItemMeta { 38 | id: number 39 | quantity: number 40 | productName: string 41 | } 42 | 43 | export interface IOrderInfo { 44 | id: number 45 | currency: ICurrencyInfo 46 | totalAmount: number 47 | cartItemsMeta: IOrderCartItemMeta[] 48 | orderStatusHistory: IOrderStatusHistoryInfo[] 49 | userId: number 50 | updatedDateTime: Date 51 | createdDateTime: Date 52 | } 53 | 54 | export interface IOrderDetail extends IOrderInfo { 55 | retailAmount: number 56 | saleAmount: number 57 | discountAmount: number 58 | deliveryAmount: number 59 | extraChargesAmount: number | null 60 | taxAmount: number | null 61 | cart: ICartDetail 62 | user: IUserInfo 63 | address: IUserAddressInfo 64 | statusText: string | null 65 | orderCancellation: IOrderCancellationInfo | null 66 | paymentMethod: PaymentMethodType 67 | thirdPartyPaymentId: string 68 | cancellationReasons: string[] 69 | } 70 | 71 | export interface IOrderAddParams { 72 | addressId: number 73 | currencyId: number 74 | retailAmount: number 75 | saleAmount: number 76 | discountAmount: number 77 | deliveryAmount: number 78 | totalAmount: number 79 | extraChargesAmount: number | null 80 | taxAmount: number | null 81 | cart: ICartDetail 82 | thirdPartyPaymentId: string 83 | paymentMethod: PaymentMethodType 84 | } 85 | 86 | export interface IUpdateOrderInfoParams { 87 | statusText: string | null 88 | orderStatusId: number | null 89 | cancellationReason: string | null 90 | } 91 | 92 | export interface IUpdateOrderInfo { 93 | success: boolean 94 | } 95 | 96 | export interface IRefundOrderDetailParams { 97 | reason: string 98 | } 99 | 100 | export interface IRefundOrderDetail { 101 | success: boolean 102 | orderDetail: IOrderDetail | null 103 | } 104 | -------------------------------------------------------------------------------- /contract/payment.ts: -------------------------------------------------------------------------------- 1 | import { PaymentMethodType } from './constants' 2 | 3 | export interface IInitiatePayment { 4 | clientSecret: string | null 5 | } 6 | 7 | export interface ISuccessfulPaymentParams { 8 | thirdPartyPaymentId: string 9 | addressId: number 10 | paymentMethod: PaymentMethodType 11 | } 12 | 13 | export interface ISuccessfulPayment { 14 | success: boolean 15 | orderId: number | null 16 | } 17 | -------------------------------------------------------------------------------- /contract/search.ts: -------------------------------------------------------------------------------- 1 | import { IIndividualProductInfo, IComboProductInfo } from './product' 2 | import { ICatalogueInfo } from './catalogue' 3 | 4 | export interface ISearchInfo { 5 | catalogues: ICatalogueInfo[] | null 6 | individualProducts: IIndividualProductInfo[] | null 7 | comboProducts: IComboProductInfo[] | null 8 | } 9 | -------------------------------------------------------------------------------- /contract/security.ts: -------------------------------------------------------------------------------- 1 | export interface IUserSecurityPasswordHintInfo { 2 | hint: string | null 3 | } 4 | 5 | export interface IUserUpdateSecurityPasswordHintInfoParams { 6 | password: string 7 | hint: string 8 | } 9 | 10 | export interface IUserUpdateSecurityPasswordHintInfo { 11 | success: boolean 12 | message: string | null 13 | } 14 | 15 | export interface ISecurityQuestionInfo { 16 | id: number 17 | question: string 18 | } 19 | 20 | export interface IISecurityQuestionInfoUpdateParams { 21 | question: string 22 | } 23 | 24 | export interface IISecurityQuestionInfoUpdate { 25 | success: boolean 26 | } 27 | 28 | export interface IISecurityQuestionInfoDelete { 29 | success: boolean 30 | } 31 | 32 | export interface ISecurityAnswerInfo { 33 | questionId: number 34 | answer: string 35 | } 36 | 37 | export interface IUserSecurityQuestionAnswer { 38 | id: number 39 | question: ISecurityQuestionInfo 40 | } 41 | 42 | export interface IUserSecurityQuestionsDetail { 43 | allQuestions: ISecurityQuestionInfo[] 44 | answeredQuestions: IUserSecurityQuestionAnswer[] 45 | } 46 | 47 | export interface IUserUpdateSecurityQuestionAnswerParams { 48 | password: string 49 | securityAnswers: ISecurityAnswerInfo[] 50 | } 51 | 52 | export interface IUserUpdateSecurityQuestionAnswer { 53 | success: boolean 54 | message: string | null 55 | } 56 | 57 | export interface IUserVerifySecurityQuestionAnswerParams { 58 | email: string 59 | securityAnswers: ISecurityAnswerInfo[] 60 | } 61 | 62 | export interface IUserVerifySecurityQuestionAnswer { 63 | success: boolean 64 | } 65 | -------------------------------------------------------------------------------- /contract/setting.ts: -------------------------------------------------------------------------------- 1 | export interface ISettingInfo { 2 | // passwordHintSet: boolean 3 | securityQuestionsSet: boolean 4 | allow: { 5 | newRegisterations: boolean 6 | newOrders: boolean 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /contract/staticPage.ts: -------------------------------------------------------------------------------- 1 | export interface IStaticPageDetail { 2 | title: string 3 | body: string 4 | updatedDateTime: Date | null 5 | } 6 | 7 | export interface IStaticPageUpdateParams { 8 | title: string 9 | body: string 10 | } 11 | 12 | export interface IStaticPageUpdate { 13 | success: boolean 14 | } 15 | -------------------------------------------------------------------------------- /contract/supportedRegions.ts: -------------------------------------------------------------------------------- 1 | export interface ISupportedCountryInfo { 2 | id: number 3 | name: string 4 | shortName: string 5 | flagUrl: string | null 6 | } 7 | 8 | export interface ISupportedCityInfo { 9 | id: number 10 | name: string 11 | shortName: string 12 | flagUrl: string | null 13 | } 14 | 15 | export type ISupportedRegionInfo = ISupportedCountryInfo | ISupportedCityInfo 16 | 17 | export interface ISupportedRegionsInfo { 18 | cities: ISupportedCityInfo[] 19 | countries: ISupportedCountryInfo[] 20 | } 21 | 22 | export interface ISupportedRegionInfoUpdateParams { 23 | name: string 24 | shortName: string 25 | flagUrl: string | null 26 | } 27 | 28 | export interface ISupportedRegionInfoUpdate { 29 | success: boolean 30 | } 31 | 32 | export interface ISupportedRegionInfoDelete { 33 | success: boolean 34 | } 35 | -------------------------------------------------------------------------------- /contract/userWish.ts: -------------------------------------------------------------------------------- 1 | import { ProductType } from './constants' 2 | import { IProductInfo } from './product' 3 | 4 | export interface IUserWishInfo { 5 | id: number 6 | createdDateTime: Date 7 | product: IProductInfo 8 | } 9 | 10 | export interface IUserWishInfoAddParams { 11 | productId: number 12 | productType: ProductType 13 | } 14 | 15 | export interface IUserWishInfoAdd { 16 | success: boolean 17 | userWish: IUserWishInfo | null 18 | } 19 | 20 | export interface IUserWishInfoDelete { 21 | success: boolean 22 | } 23 | -------------------------------------------------------------------------------- /env/local.env: -------------------------------------------------------------------------------- 1 | # <> = Indicates a value is secret and should be replaced 2 | 3 | # Global 4 | STORE_CMS_DOMAIN=localhost:3002 5 | STORE_CMS_BASE_URL=http://localhost:3002 6 | STORE_CMS_IMAGE_BASE_URL=https://res.cloudinary.com/your-store/image/upload 7 | STORE_CMS_API_BASE_URL=http://localhost:3001 8 | STORE_CMS_WEB_BASE_URL=https://own-store-demo.vercel.app 9 | 10 | # Integrations 11 | STORE_CMS_INTEGRATION_CLOUDINARY_CLOUND_NAME=<> 12 | STORE_CMS_INTEGRATION_CLOUDINARY_UPLOAD_PRESET_NAME=<> 13 | 14 | STORE_CMS_INTEGRATION_GOOGLE_ANALYTICS_ENABLED=false 15 | STORE_CMS_INTEGRATION_GOOGLE_ANALYTICS_CODE=<> 16 | -------------------------------------------------------------------------------- /env/localproduction.env: -------------------------------------------------------------------------------- 1 | # <> = Indicates a value is secret and should be replaced 2 | 3 | # Global 4 | STORE_CMS_DOMAIN=localhost:3002 5 | STORE_CMS_BASE_URL=http://localhost:3002 6 | STORE_CMS_IMAGE_BASE_URL=https://res.cloudinary.com/your-store/image/upload 7 | STORE_CMS_API_BASE_URL=https://your-store-api.herokuapp.com 8 | STORE_CMS_WEB_BASE_URL=https://own-store-demo.vercel.app 9 | 10 | # Integrations 11 | STORE_CMS_INTEGRATION_CLOUDINARY_CLOUND_NAME=<> 12 | STORE_CMS_INTEGRATION_CLOUDINARY_UPLOAD_PRESET_NAME=<> 13 | 14 | STORE_CMS_INTEGRATION_GOOGLE_ANALYTICS_ENABLED=false 15 | STORE_CMS_INTEGRATION_GOOGLE_ANALYTICS_CODE=<> 16 | -------------------------------------------------------------------------------- /env/production.env: -------------------------------------------------------------------------------- 1 | # <> = Indicates a value is secret and should be replaced 2 | 3 | # Global 4 | STORE_CMS_DOMAIN=own-store-demo-cms.vercel.app 5 | STORE_CMS_BASE_URL=https://own-store-demo-cms.vercel.app 6 | STORE_CMS_IMAGE_BASE_URL=https://res.cloudinary.com/your-store/image/upload 7 | STORE_CMS_API_BASE_URL=https://your-store-api.herokuapp.com 8 | STORE_CMS_WEB_BASE_URL=https://own-store-demo.vercel.app 9 | 10 | # Integrations 11 | STORE_CMS_INTEGRATION_CLOUDINARY_CLOUND_NAME=<> 12 | STORE_CMS_INTEGRATION_CLOUDINARY_UPLOAD_PRESET_NAME=<> 13 | 14 | STORE_CMS_INTEGRATION_GOOGLE_ANALYTICS_ENABLED=true 15 | STORE_CMS_INTEGRATION_GOOGLE_ANALYTICS_CODE=<> 16 | -------------------------------------------------------------------------------- /error/ApiError.ts: -------------------------------------------------------------------------------- 1 | export interface ApiRequestParams { 2 | method: 'get' | 'post' | 'put' | 'delete' 3 | path: string 4 | data?: Record 5 | headers?: Record 6 | } 7 | 8 | export interface ApiErrorResponse { 9 | status: number 10 | message: string 11 | code: string 12 | data: any 13 | } 14 | 15 | class ApiError extends Error { 16 | public readonly request: ApiRequestParams 17 | public readonly response: ApiErrorResponse 18 | public readonly _apiError: boolean 19 | public constructor(request: ApiRequestParams, response: ApiErrorResponse) { 20 | super('API Request Failed') 21 | this.request = request 22 | this.response = response 23 | this._apiError = true 24 | } 25 | public static isApiError(err: Error): boolean { 26 | return err instanceof Error && !!(err as ApiError)._apiError 27 | } 28 | } 29 | 30 | export default ApiError 31 | -------------------------------------------------------------------------------- /hooks/useApplicationContext.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import { useEffect } from 'react' 3 | import { getDeviceInfo } from '../utils/applicationContext' 4 | import { deleteAdminAuthToken, getAdminAuthToken } from '../utils/admin' 5 | import useApplicationContextReducer from './useApplicationContextReducer' 6 | import useOrientation from './useOrientation' 7 | import { isAdminVerified } from '../http/auth' 8 | import { LoginStateType } from '../components/ApplicationContext' 9 | import { getHomePageUrl } from '../utils/home' 10 | import { getLoginPageUrl } from '../utils/login' 11 | 12 | const useApplicationContext = () => { 13 | const { applicationContext, dispatchApplicationContext } = useApplicationContextReducer() 14 | const router = useRouter() 15 | 16 | const { isLandscapeMode } = useOrientation() 17 | 18 | const updateLoginState = (val: LoginStateType): void => { 19 | dispatchApplicationContext({ 20 | type: 'UPDATE_LOGIN_STATE', 21 | payload: val, 22 | }) 23 | } 24 | 25 | useEffect(() => { 26 | dispatchApplicationContext({ 27 | type: 'UPDATE_DEVICE', 28 | payload: getDeviceInfo(), 29 | }) 30 | }, [isLandscapeMode]) 31 | 32 | useEffect(() => { 33 | const authToken = getAdminAuthToken() 34 | 35 | if (authToken) { 36 | isAdminVerified() 37 | .then(resp => { 38 | updateLoginState(LoginStateType.LOGGED_IN) 39 | }) 40 | .catch(e => { 41 | console.error(e) 42 | updateLoginState(LoginStateType.LOGGED_OUT) 43 | }) 44 | } else { 45 | updateLoginState(LoginStateType.LOGGED_OUT) 46 | } 47 | }, []) 48 | 49 | const logout = () => { 50 | deleteAdminAuthToken() 51 | updateLoginState(LoginStateType.LOGGED_OUT) 52 | } 53 | 54 | applicationContext.updaters = { 55 | updateLoginState: updateLoginState, 56 | } 57 | 58 | applicationContext.logout = logout 59 | 60 | return { 61 | applicationContext, 62 | dispatchApplicationContext, 63 | } 64 | } 65 | 66 | export default useApplicationContext 67 | -------------------------------------------------------------------------------- /hooks/useApplicationContextReducer.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch, useEffect, useReducer, useState } from 'react' 2 | import { 3 | defaultApplicationContext, 4 | IApplicationContextProps, 5 | IDeviceInfo, 6 | LoginStateType, 7 | } from '../components/ApplicationContext' 8 | 9 | export type ApplicationContextAction = 10 | | { 11 | type: 'UPDATE_DEVICE' 12 | payload: IDeviceInfo 13 | } 14 | | { 15 | type: 'UPDATE_LOGIN_STATE' 16 | payload: LoginStateType 17 | } 18 | | { 19 | type: 'RESET' 20 | } 21 | 22 | const applicationReducer = ( 23 | state: IApplicationContextProps, 24 | action: ApplicationContextAction 25 | ): IApplicationContextProps => { 26 | switch (action.type) { 27 | case 'UPDATE_DEVICE': { 28 | return { 29 | ...state, 30 | device: action.payload, 31 | } 32 | } 33 | 34 | case 'UPDATE_LOGIN_STATE': { 35 | return { 36 | ...state, 37 | loginState: action.payload, 38 | } 39 | } 40 | 41 | case 'RESET': { 42 | const { device } = state 43 | return { 44 | ...defaultApplicationContext, 45 | device: device, 46 | } 47 | } 48 | 49 | default: 50 | return state 51 | } 52 | } 53 | 54 | const useApplicationContextReducer = (): { 55 | applicationContext: IApplicationContextProps 56 | dispatchApplicationContext: Dispatch 57 | } => { 58 | const [applicationContext, dispatchApplicationContext] = useReducer(applicationReducer, defaultApplicationContext) 59 | 60 | return { 61 | applicationContext: { 62 | ...applicationContext, 63 | isLoggedIn: applicationContext.loginState === LoginStateType.LOGGED_IN, 64 | }, 65 | dispatchApplicationContext, 66 | } 67 | } 68 | 69 | export default useApplicationContextReducer 70 | -------------------------------------------------------------------------------- /hooks/useCustomLayoutEffect.ts: -------------------------------------------------------------------------------- 1 | import { EffectCallback, useLayoutEffect } from 'react' 2 | import useSSR from 'use-ssr' 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-empty-function 5 | const useDummyEffect = (effect: EffectCallback, params) => {} 6 | 7 | const useCustomLayoutEffect = (effect: EffectCallback, params) => { 8 | const { isServer } = useSSR() 9 | const effectFn = isServer ? useDummyEffect : useLayoutEffect 10 | effectFn(effect, params) 11 | } 12 | 13 | export default useCustomLayoutEffect 14 | -------------------------------------------------------------------------------- /hooks/useDisablePageScrolling.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { disablePageScrolling, enablePageScrolling } from '../utils/common' 3 | 4 | const useDisablePageScrolling = () => { 5 | useEffect(() => { 6 | disablePageScrolling() 7 | return () => enablePageScrolling() 8 | }, []) 9 | } 10 | 11 | export default useDisablePageScrolling 12 | -------------------------------------------------------------------------------- /hooks/useEscape.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | 3 | type OnEscape = (arg: KeyboardEvent) => void 4 | 5 | const useEscape = (onEscape: OnEscape) => { 6 | const handleEscape = (e: KeyboardEvent) => { 7 | if (e.keyCode === 27) { 8 | onEscape(e) 9 | } 10 | } 11 | 12 | useEffect(() => { 13 | document.addEventListener('keydown', handleEscape) 14 | return () => { 15 | document.removeEventListener('keydown', handleEscape) 16 | } 17 | }, []) 18 | } 19 | 20 | export default useEscape 21 | -------------------------------------------------------------------------------- /hooks/useLoginEffect.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect, useContext } from 'react' 2 | import ApplicationContext from '../components/ApplicationContext' 3 | 4 | const useLoginEffect = (effect, dependencies = []) => { 5 | const applicationContext = useContext(ApplicationContext) 6 | const { isLoggedIn } = applicationContext 7 | 8 | useEffect(() => { 9 | if (isLoggedIn) { 10 | effect() 11 | } 12 | }, [isLoggedIn, ...dependencies]) 13 | } 14 | 15 | export default useLoginEffect 16 | -------------------------------------------------------------------------------- /hooks/useNativeShare.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useContext } from 'react' 2 | import ApplicationContext from '../components/ApplicationContext' 3 | import appConfig from '../config/appConfig' 4 | import debug from 'debug' 5 | 6 | const log = debug('native-share') 7 | 8 | declare let window: any 9 | 10 | const useNativeShare = () => { 11 | const [showNativeShare, toggleNativeShare] = useState(false) 12 | const [nativeShareFailed, toggleNativeShareFailed] = useState(false) 13 | 14 | const { 15 | device: { isMobile }, 16 | } = useContext(ApplicationContext) 17 | 18 | useEffect(() => { 19 | log('nativeShare supported', !!window.navigator.share) 20 | 21 | if (window.navigator.share && isMobile) { 22 | log('show native share') 23 | 24 | toggleNativeShare(true) 25 | } 26 | }, []) 27 | 28 | const nativeShareUrl = ({ title, text, url }: { title?: string; text: string; url: string }) => { 29 | if (window.navigator.share) { 30 | window.navigator 31 | .share({ 32 | title: title || appConfig.global.app.name, 33 | text, 34 | url: url, 35 | }) 36 | .catch(e => { 37 | console.log(e, e.name, e.message) 38 | 39 | if (!['AbortError'].includes(e.name)) { 40 | // https://hotstar.atlassian.net/browse/ER-1904 41 | // https://developer.apple.com/forums/thread/662629 42 | toggleNativeShareFailed(true) 43 | } 44 | }) 45 | } 46 | } 47 | 48 | return { 49 | showNativeShare, 50 | nativeShareUrl, 51 | nativeShareFailed, 52 | toggleNativeShareFailed, 53 | } 54 | } 55 | 56 | export default useNativeShare 57 | -------------------------------------------------------------------------------- /hooks/useOnEnter.ts: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | 3 | const useOnEnter = (elementRef, handleSubmit) => { 4 | const [shortcut, setShortcut] = useState(null) 5 | 6 | useEffect(() => { 7 | if (shortcut === 13) { 8 | handleSubmit() 9 | } 10 | }, [shortcut]) 11 | 12 | useEffect(() => { 13 | const handleKeyUp = (event: KeyboardEvent) => { 14 | setShortcut(event.keyCode) 15 | } 16 | 17 | if (elementRef.current) { 18 | elementRef.current.addEventListener('keydown', handleKeyUp) 19 | } 20 | return () => { 21 | if (elementRef.current) { 22 | elementRef.current.removeEventListener('keydown', handleKeyUp) 23 | } 24 | } 25 | }, []) 26 | } 27 | 28 | export default useOnEnter 29 | -------------------------------------------------------------------------------- /hooks/useOrientation.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | 3 | const useOrientation = () => { 4 | const [isLandscape, toggleIsLandscape] = useState(false) 5 | 6 | useEffect(() => { 7 | const mediaQuery = window.matchMedia('(orientation: landscape)') 8 | toggleIsLandscape(mediaQuery.matches) 9 | 10 | const onOrientationChange = m => { 11 | toggleIsLandscape(m.matches) 12 | } 13 | 14 | mediaQuery.addListener(onOrientationChange) 15 | return () => { 16 | mediaQuery.removeListener(onOrientationChange) 17 | } 18 | }, []) 19 | 20 | return { 21 | isLandscapeMode: isLandscape, 22 | } 23 | } 24 | 25 | export default useOrientation 26 | -------------------------------------------------------------------------------- /hooks/useOutsideClick.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | 3 | interface Options { 4 | ref: React.MutableRefObject 5 | onOutsideClick: (arg: MouseEvent) => void 6 | } 7 | 8 | const useOutsideClick = ({ ref, onOutsideClick }: Options) => { 9 | const handleClickOutside = (event: MouseEvent) => { 10 | if (ref.current && !ref.current.contains(event.target)) { 11 | onOutsideClick(event) 12 | } 13 | } 14 | 15 | useEffect(() => { 16 | document.addEventListener('mousedown', handleClickOutside) 17 | return () => { 18 | document.removeEventListener('mousedown', handleClickOutside) 19 | } 20 | }) 21 | } 22 | 23 | export default useOutsideClick 24 | -------------------------------------------------------------------------------- /hooks/usePortal.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect, useCallback, ReactNode } from 'react' 2 | import { createPortal } from 'react-dom' 3 | import useSSR from 'use-ssr' 4 | 5 | const usePortal = (): React.FC => { 6 | // useful hooks 7 | const { isServer, isBrowser } = useSSR() 8 | 9 | // create a portal child element only on browser 10 | const portal = useRef(isBrowser ? document.createElement('div') : null) 11 | 12 | // Append/Remove child portal 13 | // @ts-ignore 14 | useEffect(() => { 15 | if (isServer) return 16 | 17 | const node = portal.current 18 | document.body.appendChild(portal.current) 19 | 20 | return () => document.body.removeChild(node) 21 | }, [isServer, portal]) 22 | 23 | // Portal component 24 | const Portal = useCallback( 25 | ({ children }: { children: ReactNode }) => { 26 | if (portal.current != null) return createPortal(children, portal.current) 27 | return null 28 | }, 29 | [portal] 30 | ) 31 | 32 | return Portal 33 | } 34 | 35 | export default usePortal 36 | -------------------------------------------------------------------------------- /hooks/useUpdateEffect.ts: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from 'react' 2 | 3 | const useUpdateEffect = (effect, dependencies = []) => { 4 | const isInitialMount = useRef(true) 5 | 6 | useEffect(() => { 7 | if (isInitialMount.current) { 8 | isInitialMount.current = false 9 | } else { 10 | effect() 11 | } 12 | }, dependencies) 13 | } 14 | 15 | export default useUpdateEffect 16 | -------------------------------------------------------------------------------- /hooks/useUpdateLayoutEffect.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react' 2 | import useCustomLayoutEffect from './useCustomLayoutEffect' 3 | 4 | const useUpdateLayoutEffect = (effect, dependencies = []) => { 5 | const isInitialMount = useRef(true) 6 | useCustomLayoutEffect(() => { 7 | if (isInitialMount.current) { 8 | isInitialMount.current = false 9 | } else { 10 | effect() 11 | } 12 | }, dependencies) 13 | } 14 | 15 | export default useUpdateLayoutEffect 16 | -------------------------------------------------------------------------------- /http/auth.ts: -------------------------------------------------------------------------------- 1 | import { IAdminVerified, IAdminVerify, IAdminVerifyParams } from '../contract/admin' 2 | import { httpClient } from './httpClient' 3 | 4 | export const verifyAdmin = async (params: IAdminVerifyParams): Promise => { 5 | const result = await httpClient.post(`/admin/verify`, params) 6 | return result 7 | } 8 | 9 | export const isAdminVerified = async (): Promise => { 10 | const result = await httpClient.get(`/admin/is-verified`) 11 | return result 12 | } 13 | -------------------------------------------------------------------------------- /http/blog.ts: -------------------------------------------------------------------------------- 1 | import { IBlogInfo, IBlogInfoDelete, IBlogInfoUpdate, IBlogInfoUpdateParams } from '../contract/blog' 2 | import { IFindParams } from '../contract/common' 3 | import { httpClient } from './httpClient' 4 | 5 | export const getAllBlogs = async (params: IFindParams): Promise => { 6 | const result = await httpClient.get(`/blog/info`, params) 7 | return result 8 | } 9 | 10 | export const getBlogById = async (blogId: number): Promise => { 11 | const result = await httpClient.get(`/blog/info/${blogId}`) 12 | return result 13 | } 14 | 15 | export const updateBlog = async (blogId: number | null, params: IBlogInfoUpdateParams): Promise => { 16 | const result = await httpClient.post(`/blog/info${blogId ? `/${blogId}` : ''}`, params) 17 | return result 18 | } 19 | 20 | export const deleteBlog = async (blogId: number): Promise => { 21 | const result = await httpClient.delete(`/blog/info/${blogId}`) 22 | return result 23 | } 24 | 25 | export const searchBlogByName = async (query: string, params: IFindParams): Promise => { 26 | const result = await httpClient.get(`/blog/info/search/${query}`, params) 27 | return result 28 | } 29 | -------------------------------------------------------------------------------- /http/catalogue.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ICatalogueInfo, 3 | ICatalogueInfoDelete, 4 | ICatalogueInfoUpdate, 5 | ICatalogueInfoUpdateParams, 6 | } from '../contract/catalogue' 7 | import { IFindParams } from '../contract/common' 8 | import { httpClient } from './httpClient' 9 | 10 | export const getAllCatalogues = async (params: IFindParams): Promise => { 11 | const result = await httpClient.get(`/catalogue/info`, params) 12 | return result 13 | } 14 | 15 | export const getCatalogueById = async (catalogueId: number): Promise => { 16 | const result = await httpClient.get(`/catalogue/info/${catalogueId}`) 17 | return result 18 | } 19 | 20 | export const updateCatalogue = async ( 21 | catalogueId: number | null, 22 | params: ICatalogueInfoUpdateParams 23 | ): Promise => { 24 | const result = await httpClient.post( 25 | `/catalogue/info${catalogueId ? `/${catalogueId}` : ''}`, 26 | params 27 | ) 28 | return result 29 | } 30 | 31 | export const deleteCatalogue = async (catalogueId: number): Promise => { 32 | const result = await httpClient.delete(`/catalogue/info/${catalogueId}`) 33 | return result 34 | } 35 | 36 | export const searchCatalogueByName = async (query: string, params: IFindParams): Promise => { 37 | const result = await httpClient.get(`/catalogue/info/search/${query}`, params) 38 | return result 39 | } 40 | -------------------------------------------------------------------------------- /http/currency.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ICurrencyInfo, 3 | ICurrencyInfoDelete, 4 | ICurrencyInfoUpdate, 5 | ICurrencyInfoUpdateParams, 6 | } from '../contract/currency' 7 | import { httpClient } from './httpClient' 8 | 9 | export const getAllCurrencies = async (): Promise => { 10 | const result = await httpClient.get(`/currency/info`) 11 | return result 12 | } 13 | 14 | export const getCurrencyById = async (currencyId: number): Promise => { 15 | const result = await httpClient.get(`/currency/info/${currencyId}`) 16 | return result 17 | } 18 | 19 | export const updateCurrency = async ( 20 | currencyId: number | null, 21 | params: ICurrencyInfoUpdateParams 22 | ): Promise => { 23 | const result = await httpClient.post( 24 | `/currency/info${currencyId ? `/${currencyId}` : ''}`, 25 | params 26 | ) 27 | return result 28 | } 29 | 30 | export const deleteCurrency = async (currencyId: number): Promise => { 31 | const result = await httpClient.delete(`/currency/info/${currencyId}`) 32 | return result 33 | } 34 | -------------------------------------------------------------------------------- /http/faq.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IFAQInfo, 3 | IFAQInfoDelete, 4 | IFAQInfoUpdate, 5 | IFAQInfoUpdateParams, 6 | IFAQTopicInfo, 7 | IFAQTopicInfoDelete, 8 | IFAQTopicInfoUpdate, 9 | IFAQTopicInfoUpdateParams, 10 | } from '../contract/faq' 11 | import { httpClient } from './httpClient' 12 | 13 | export const getAllFAQTopics = async (): Promise => { 14 | const result = await httpClient.get(`/faq/topic/info`) 15 | return result 16 | } 17 | 18 | export const getFAQTopicById = async (topicId: number): Promise => { 19 | const result = await httpClient.get(`/faq/topic/info/${topicId}`) 20 | return result 21 | } 22 | 23 | export const updateFAQTopic = async ( 24 | topicId: number | null, 25 | params: IFAQTopicInfoUpdateParams 26 | ): Promise => { 27 | const result = await httpClient.post(`/faq/topic/info${topicId ? `/${topicId}` : ''}`, params) 28 | return result 29 | } 30 | 31 | export const deleteFAQTopic = async (topicId: number): Promise => { 32 | const result = await httpClient.delete(`/faq/topic/info/${topicId}`) 33 | return result 34 | } 35 | 36 | export const getFAQsByTopicId = async (topicId: number): Promise => { 37 | const result = await httpClient.get(`/faq/topic/${topicId}/info`) 38 | return result 39 | } 40 | 41 | export const getFAQById = async (faqId: number): Promise => { 42 | const result = await httpClient.get(`/faq/info/${faqId}`) 43 | return result 44 | } 45 | 46 | export const updateFAQ = async (faqId: number | null, params: IFAQInfoUpdateParams): Promise => { 47 | const result = await httpClient.post(`/faq/info${faqId ? `/${faqId}` : ''}`, params) 48 | return result 49 | } 50 | 51 | export const deleteFAQ = async (faqId: number): Promise => { 52 | const result = await httpClient.delete(`/faq/info/${faqId}`) 53 | return result 54 | } 55 | -------------------------------------------------------------------------------- /http/image.ts: -------------------------------------------------------------------------------- 1 | import { IFindParams } from '../contract/common' 2 | import { IImageInfo, IImageInfoUpdate, IImageInfoUpdateParams } from '../contract/image' 3 | import { httpClient } from './httpClient' 4 | 5 | export const getAllImages = async (params: IFindParams): Promise => { 6 | const result = await httpClient.get(`/image/info`, params) 7 | return result 8 | } 9 | 10 | export const getImageById = async (imageId: number): Promise => { 11 | const result = await httpClient.get(`/image/info/${imageId}`) 12 | return result 13 | } 14 | 15 | export const updateImage = async ( 16 | imageId: number | null, 17 | params: IImageInfoUpdateParams 18 | ): Promise => { 19 | const result = await httpClient.post(`/image/info${imageId ? `/${imageId}` : ''}`, params) 20 | return result 21 | } 22 | 23 | export const searchImageByName = async (query: string, params: IFindParams): Promise => { 24 | const result = await httpClient.get(`/image/info/search/${query}`, params) 25 | return result 26 | } 27 | -------------------------------------------------------------------------------- /http/order.ts: -------------------------------------------------------------------------------- 1 | import { IFindParams } from '../contract/common' 2 | import { 3 | IOrderDetail, 4 | IOrderInfo, 5 | IOrderStatusInfo, 6 | IOrderStatusInfoDelete, 7 | IOrderStatusInfoUpdate, 8 | IOrderStatusInfoUpdateParams, 9 | IRefundOrderDetail, 10 | IRefundOrderDetailParams, 11 | IUpdateOrderInfo, 12 | IUpdateOrderInfoParams, 13 | } from '../contract/order' 14 | import { httpClient } from './httpClient' 15 | 16 | export const getRecentOrders = async (params: IFindParams): Promise => { 17 | const result = await httpClient.get(`/order/recent/info`, params) 18 | return result 19 | } 20 | 21 | export const getOrderById = async (orderId: number): Promise => { 22 | const result = await httpClient.get(`/order/detail/${orderId}`) 23 | return result 24 | } 25 | 26 | export const updateOrder = async (orderId: number, params: IUpdateOrderInfoParams): Promise => { 27 | const result = await httpClient.post(`/order/info/${orderId}`, params) 28 | return result 29 | } 30 | 31 | export const refundUserOrder = async ( 32 | userId: number, 33 | orderId: number, 34 | params: IRefundOrderDetailParams 35 | ): Promise => { 36 | const result = await httpClient.post(`/order/user/${userId}/detail/${orderId}/refund`, params) 37 | return result 38 | } 39 | 40 | export const getAllOrderStatus = async (): Promise => { 41 | const result = await httpClient.get(`/order/status/info`) 42 | return result 43 | } 44 | 45 | export const getOrderStatusById = async (statusId: number): Promise => { 46 | const result = await httpClient.get(`/order/status/info/${statusId}`) 47 | return result 48 | } 49 | 50 | export const updateOrderStatus = async ( 51 | statusId: number | null, 52 | params: IOrderStatusInfoUpdateParams 53 | ): Promise => { 54 | const result = await httpClient.post( 55 | `/order/status/info${statusId ? `/${statusId}` : ''}`, 56 | params 57 | ) 58 | return result 59 | } 60 | 61 | export const deleteOrderStatus = async (statusId: number): Promise => { 62 | const result = await httpClient.delete(`/order/status/info/${statusId}`) 63 | return result 64 | } 65 | -------------------------------------------------------------------------------- /http/privacyPolicy.ts: -------------------------------------------------------------------------------- 1 | import { IStaticPageDetail, IStaticPageUpdate, IStaticPageUpdateParams } from '../contract/staticPage' 2 | import { httpClient } from './httpClient' 3 | 4 | export const getPrivacyPolicyDetail = async (): Promise => { 5 | const result = await httpClient.get(`/privacy-policy/detail`) 6 | return result 7 | } 8 | 9 | export const updatePrivacyPolicyDetail = async (params: IStaticPageUpdateParams): Promise => { 10 | const result = await httpClient.post(`/privacy-policy/detail`, params) 11 | return result 12 | } 13 | -------------------------------------------------------------------------------- /http/refundPolicy.ts: -------------------------------------------------------------------------------- 1 | import { IStaticPageDetail, IStaticPageUpdate, IStaticPageUpdateParams } from '../contract/staticPage' 2 | import { httpClient } from './httpClient' 3 | 4 | export const getRefundPolicyDetail = async (): Promise => { 5 | const result = await httpClient.get(`/refund-policy/detail`) 6 | return result 7 | } 8 | 9 | export const updateRefundPolicyDetail = async (params: IStaticPageUpdateParams): Promise => { 10 | const result = await httpClient.post(`/refund-policy/detail`, params) 11 | return result 12 | } 13 | -------------------------------------------------------------------------------- /http/securityQuestion.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ISecurityQuestionInfo, 3 | IISecurityQuestionInfoUpdateParams, 4 | IISecurityQuestionInfoUpdate, 5 | IISecurityQuestionInfoDelete, 6 | } from '../contract/security' 7 | import { httpClient } from './httpClient' 8 | 9 | export const getAllSecurityQuestions = async (): Promise => { 10 | const result = await httpClient.get(`/security/security-question/info`) 11 | return result 12 | } 13 | 14 | export const getSecurityQuestionById = async (questionId: number): Promise => { 15 | const result = await httpClient.get(`/security/security-question/info/${questionId}`) 16 | return result 17 | } 18 | 19 | export const updateSecurityQuestion = async ( 20 | questionId: number | null, 21 | params: IISecurityQuestionInfoUpdateParams 22 | ): Promise => { 23 | const result = await httpClient.post( 24 | `/security/security-question/info${questionId ? `/${questionId}` : ''}`, 25 | params 26 | ) 27 | return result 28 | } 29 | 30 | export const deleteSecurityQuestion = async (questionId: number): Promise => { 31 | const result = await httpClient.delete(`/security/security-question/info/${questionId}`) 32 | return result 33 | } 34 | -------------------------------------------------------------------------------- /http/shippingPolicy.ts: -------------------------------------------------------------------------------- 1 | import { IStaticPageDetail, IStaticPageUpdate, IStaticPageUpdateParams } from '../contract/staticPage' 2 | import { httpClient } from './httpClient' 3 | 4 | export const getShippingPolicyDetail = async (): Promise => { 5 | const result = await httpClient.get(`/shipping-policy/detail`) 6 | return result 7 | } 8 | 9 | export const updateShippingPolicyDetail = async (params: IStaticPageUpdateParams): Promise => { 10 | const result = await httpClient.post(`/shipping-policy/detail`, params) 11 | return result 12 | } 13 | -------------------------------------------------------------------------------- /http/supportedRegions.ts: -------------------------------------------------------------------------------- 1 | import { SupportedRegionType } from '../contract/constants' 2 | import { 3 | ISupportedRegionsInfo, 4 | ISupportedRegionInfoUpdateParams, 5 | ISupportedRegionInfoUpdate, 6 | ISupportedRegionInfoDelete, 7 | ISupportedRegionInfo, 8 | } from '../contract/supportedRegions' 9 | import { httpClient } from './httpClient' 10 | 11 | export const getSupportedRegions = async (): Promise => { 12 | const result = await httpClient.get(`/supported-region/info`) 13 | return result 14 | } 15 | 16 | export const getSupportedRegionById = async ( 17 | supportedRegionId: number, 18 | supportedRegionType: SupportedRegionType 19 | ): Promise => { 20 | const result = await httpClient.get( 21 | `/supported-region/type/${supportedRegionType}/info/${supportedRegionId}` 22 | ) 23 | return result 24 | } 25 | 26 | export const updateSupportedRegion = async ( 27 | supportedRegionId: number | null, 28 | supportedRegionType: SupportedRegionType, 29 | params: ISupportedRegionInfoUpdateParams 30 | ): Promise => { 31 | const result = await httpClient.post( 32 | `/supported-region/type/${supportedRegionType}/info/${supportedRegionId}`, 33 | params 34 | ) 35 | return result 36 | } 37 | 38 | export const deleteSupportedRegion = async ( 39 | supportedRegionId: number | null, 40 | supportedRegionType: SupportedRegionType 41 | ): Promise => { 42 | const result = await httpClient.delete( 43 | `/supported-region/type/${supportedRegionType}/info/${supportedRegionId}` 44 | ) 45 | return result 46 | } 47 | -------------------------------------------------------------------------------- /http/tnc.ts: -------------------------------------------------------------------------------- 1 | import { IStaticPageDetail, IStaticPageUpdate, IStaticPageUpdateParams } from '../contract/staticPage' 2 | import { httpClient } from './httpClient' 3 | 4 | export const getTnCDetail = async (): Promise => { 5 | const result = await httpClient.get(`/tnc/detail`) 6 | return result 7 | } 8 | 9 | export const updateTnCDetail = async (params: IStaticPageUpdateParams): Promise => { 10 | const result = await httpClient.post(`/tnc/detail`, params) 11 | return result 12 | } 13 | -------------------------------------------------------------------------------- /http/user.ts: -------------------------------------------------------------------------------- 1 | import { IFindParams } from '../contract/common' 2 | import { 3 | IUserDetail, 4 | IUserGlobalDetail, 5 | IUserGlobalDetailParams, 6 | IUserInfo, 7 | IUserInfoUpdate, 8 | IUserInfoUpdateParams, 9 | IUserLoginAttributesInfo, 10 | } from '../contract/user' 11 | import { httpClient } from './httpClient' 12 | 13 | export const getAllUsers = async (params: IFindParams): Promise => { 14 | const result = await httpClient.get(`/user/info`, params) 15 | return result 16 | } 17 | 18 | export const getUserGlobalDetail = async ( 19 | userId: number, 20 | params: IUserGlobalDetailParams 21 | ): Promise => { 22 | const result = await httpClient.get(`/user/${userId}/detail/global`, params) 23 | return result 24 | } 25 | 26 | export const searchUserByName = async (query: string, params: IFindParams): Promise => { 27 | const result = await httpClient.get(`/user/info/search/${query}`, params) 28 | return result 29 | } 30 | 31 | export const updateUserInfo = async (userId: number, params: IUserInfoUpdateParams): Promise => { 32 | const result = await httpClient.put(`/user/${userId}/info`, params) 33 | return result 34 | } 35 | 36 | export const getUserLoginHistory = async (userId: number, params: IFindParams): Promise => { 37 | const result = await httpClient.get(`/user/${userId}/login-history/info`, params) 38 | return result 39 | } 40 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const { getAbsPath } = require('./scripts/fileSystem') 4 | 5 | const appEnv = process.env.STORE_CMS_ENV 6 | 7 | if (!appEnv) { 8 | console.error('STORE_CMS_ENV env variable is not set', process.env.HSCI_STORE_CMS_ENV) 9 | process.exit(1) 10 | } 11 | 12 | const { parsed: parsedEnvs } = require('dotenv').config({ 13 | path: getAbsPath(`env/${appEnv}.env`), 14 | }) 15 | 16 | const nextConfig = { 17 | env: { 18 | ...parsedEnvs, 19 | STORE_CMS_ENV: process.env.STORE_CMS_ENV, 20 | }, 21 | trailingSlash: false, 22 | basePath: '', 23 | poweredByHeader: false, 24 | optimizeFonts: true, 25 | typescript: { 26 | ignoreBuildErrors: false, 27 | }, 28 | async redirects() { 29 | return [] 30 | }, 31 | } 32 | 33 | module.exports = nextConfig 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "own-store-cms", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev:init": "next dev -p 3002", 7 | "dev": "run-s sync:contract dev:init", 8 | "start:local": "STORE_CMS_ENV=local npm run dev", 9 | "start:localprod": "STORE_CMS_ENV=localproduction npm run dev", 10 | "start": "next start", 11 | "build:init": "next build", 12 | "build": "run-s build:init", 13 | "lint": "eslint '*/**/*.{js,ts,tsx}'", 14 | "lint:fix": "eslint '*/**/*.{js,ts,tsx}' --fix", 15 | "sync:contract": "rm -rf ./contract && cp -fR ../own-store-api/src/app/contract ." 16 | }, 17 | "dependencies": { 18 | "classnames": "^2.3.1", 19 | "dayjs": "^1.10.6", 20 | "debug": "^4.3.1", 21 | "js-cookie": "^3.0.1", 22 | "lodash.throttle": "^4.1.1", 23 | "next": "11.1.2", 24 | "node-sass": "^5.0.0", 25 | "nprogress": "^0.2.0", 26 | "react": "17.0.2", 27 | "react-collapsible": "^2.8.4", 28 | "react-dom": "17.0.2", 29 | "react-hot-toast": "^2.1.1", 30 | "ua-parser-js": "^0.7.28", 31 | "use-ssr": "^1.0.24" 32 | }, 33 | "devDependencies": { 34 | "@heroicons/react": "^1.0.4", 35 | "@next/eslint-plugin-next": "^11.1.2", 36 | "@types/debug": "^4.1.7", 37 | "@types/js-cookie": "^2.2.7", 38 | "@types/react": "17.0.20", 39 | "@typescript-eslint/eslint-plugin": "^4.15.1", 40 | "@typescript-eslint/parser": "^4.15.1", 41 | "autoprefixer": "^10.3.1", 42 | "dotenv": "^8.2.0", 43 | "eslint": "^7.20.0", 44 | "eslint-config-next": "^11.0.1", 45 | "eslint-config-prettier": "^7.2.0", 46 | "eslint-plugin-prettier": "^3.3.1", 47 | "npm-run-all": "^4.1.5", 48 | "postcss": "^8.3.6", 49 | "prettier": "^2.2.1", 50 | "tailwindcss": "^2.2.7", 51 | "typescript": "4.4.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /pages/404.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { IGlobalLayoutProps } from './_app' 3 | import { GetStaticProps, NextPage } from 'next' 4 | import ApplicationContext from '../components/ApplicationContext' 5 | import PageContainer from '../components/PageContainer' 6 | import CoreImage, { ImageSourceType } from '../components/core/CoreImage' 7 | import CoreButton, { CoreButtonSize, CoreButtonType } from '../components/core/CoreButton' 8 | import { getHomePageUrl } from '../utils/home' 9 | import { ISectionInfo } from '../contract/section' 10 | import { prepareImageUrl } from '../utils/image' 11 | 12 | interface IProps extends IGlobalLayoutProps { 13 | pageData: {} 14 | } 15 | 16 | const NotFoundPage: NextPage = props => { 17 | const { pageData } = props 18 | 19 | const applicationContext = useContext(ApplicationContext) 20 | const { 21 | device: { isMobile }, 22 | } = applicationContext 23 | 24 | return ( 25 |
26 | 27 |
28 | 33 |
{`We couldn't find the page you were looking for.`}
34 |
35 |
36 |
37 |
38 | ) 39 | } 40 | 41 | export const getStaticProps: GetStaticProps = async context => { 42 | return { 43 | props: { 44 | pageData: null, 45 | layoutData: { 46 | headerTitle: 'Not Found', 47 | seo: { 48 | title: 'Page not found', 49 | }, 50 | }, 51 | }, 52 | } 53 | } 54 | 55 | export default NotFoundPage 56 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document' 3 | import MetaTags from '../scriptTemplates/meta' 4 | import { AppContextScript } from '../scriptTemplates/AppContext' 5 | import MediaScripts from '../scriptTemplates/media' 6 | import { AnalyticsScripts } from '../scriptTemplates/analytics' 7 | 8 | class MyDocument extends Document { 9 | static async getInitialProps(ctx: DocumentContext) { 10 | const initialProps = await Document.getInitialProps(ctx) 11 | return { ...initialProps } 12 | } 13 | 14 | render() { 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | ) 29 | } 30 | } 31 | 32 | export default MyDocument 33 | -------------------------------------------------------------------------------- /pages/blog/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import IndexViewLayout, { IIndexViewItem } from '../../components/layout/IndexViewLayout' 7 | import useLoginEffect from '../../hooks/useLoginEffect' 8 | import { getAllBlogs } from '../../http/blog' 9 | import { getBlogPageUrl, getBlogSearchPageUrl, getBlogUpdatePageUrl } from '../../utils/blog' 10 | import { useRouter } from 'next/router' 11 | import { getLayoutFindParams } from '../../utils/common' 12 | import LayoutPagination from '../../components/layout/LayoutPagination' 13 | 14 | interface IProps extends IGlobalLayoutProps { 15 | pageData: {} 16 | } 17 | 18 | const Blog: NextPage = props => { 19 | const [list, setList] = useState([]) 20 | const [fetching, setFetching] = useState(false) 21 | 22 | const router = useRouter() 23 | 24 | useLoginEffect(() => { 25 | setFetching(true) 26 | 27 | getAllBlogs({ 28 | ...getLayoutFindParams(router), 29 | }) 30 | .then(resp => { 31 | const newList: IIndexViewItem[] = resp.map(data => { 32 | return { 33 | id: data.id, 34 | label: data.title, 35 | description: data.url, 36 | editUrl: getBlogUpdatePageUrl(data.id), 37 | isInactive: !data.isActive, 38 | } 39 | }) 40 | 41 | setList(newList) 42 | }) 43 | .finally(() => { 44 | setFetching(false) 45 | }) 46 | }, [router.asPath]) 47 | 48 | const links: IPageLink[] = [ 49 | { 50 | label: 'View', 51 | url: getBlogPageUrl(), 52 | pagePaths: ['/blog'], 53 | }, 54 | { 55 | label: 'Search', 56 | url: getBlogSearchPageUrl(), 57 | }, 58 | { 59 | label: 'Add', 60 | url: getBlogUpdatePageUrl(), 61 | }, 62 | ] 63 | 64 | return ( 65 |
66 | 67 |
68 | 69 | 70 |
71 |
72 |
73 | ) 74 | } 75 | 76 | export const getStaticProps: GetStaticProps = async context => { 77 | return { 78 | props: { 79 | pageData: null, 80 | layoutData: { 81 | seo: { 82 | title: 'Blog - CMS', 83 | }, 84 | headerTitle: 'Blog', 85 | }, 86 | }, 87 | } 88 | } 89 | 90 | export default Blog 91 | -------------------------------------------------------------------------------- /pages/catalogue/images.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import { useRouter } from 'next/router' 7 | import useLoginEffect from '../../hooks/useLoginEffect' 8 | import ApplicationContext from '../../components/ApplicationContext' 9 | import { getCatalogueById } from '../../http/catalogue' 10 | import { getCatalogueImagesPageUrl, getCataloguePageUrl, getCatalogueUpdatePageUrl } from '../../utils/catalogue' 11 | import { ICatalogueInfo } from '../../contract/catalogue' 12 | import NoContent from '../../components/NoContent' 13 | import ImageInfo from '../../components/image/ImageInfo' 14 | 15 | interface IProps extends IGlobalLayoutProps { 16 | pageData: {} 17 | } 18 | 19 | const CatalogueImages: NextPage = props => { 20 | const [catalogue, setCatalogue] = useState(null) 21 | 22 | const router = useRouter() 23 | const applicationContext = useContext(ApplicationContext) 24 | 25 | const catalogueId = router.query.id as any 26 | 27 | useLoginEffect(() => { 28 | if (catalogueId) { 29 | getCatalogueById(catalogueId).then(resp => { 30 | setCatalogue(resp) 31 | }) 32 | } 33 | }, [catalogueId]) 34 | 35 | const links: IPageLink[] = [ 36 | { 37 | label: 'Catalogues', 38 | url: getCataloguePageUrl(), 39 | }, 40 | { 41 | label: 'Basic Info', 42 | url: getCatalogueUpdatePageUrl(catalogueId), 43 | }, 44 | { 45 | label: 'Images', 46 | url: getCatalogueImagesPageUrl(catalogueId), 47 | }, 48 | ] 49 | 50 | return ( 51 |
52 | 53 | {!catalogue?.images ? ( 54 | 55 | ) : ( 56 |
57 | {catalogue.images.map(image => ( 58 | 59 | ))} 60 |
61 | )} 62 |
63 |
64 | ) 65 | } 66 | 67 | export const getStaticProps: GetStaticProps = async context => { 68 | return { 69 | props: { 70 | pageData: null, 71 | layoutData: { 72 | seo: { 73 | title: 'Catalogue Images - Update - CMS', 74 | }, 75 | headerTitle: 'Catalogue Images - Update', 76 | }, 77 | }, 78 | } 79 | } 80 | 81 | export default CatalogueImages 82 | -------------------------------------------------------------------------------- /pages/catalogue/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import IndexViewLayout, { IIndexViewItem } from '../../components/layout/IndexViewLayout' 7 | import useLoginEffect from '../../hooks/useLoginEffect' 8 | import { getAllCatalogues } from '../../http/catalogue' 9 | import { getCataloguePageUrl, getCatalogueSearchPageUrl, getCatalogueUpdatePageUrl } from '../../utils/catalogue' 10 | import { getLayoutFindParams } from '../../utils/common' 11 | import { useRouter } from 'next/router' 12 | import LayoutPagination from '../../components/layout/LayoutPagination' 13 | 14 | interface IProps extends IGlobalLayoutProps { 15 | pageData: {} 16 | } 17 | 18 | const Catalogue: NextPage = props => { 19 | const [list, setList] = useState([]) 20 | const [fetching, setFetching] = useState(false) 21 | 22 | const router = useRouter() 23 | 24 | useLoginEffect(() => { 25 | setFetching(true) 26 | 27 | getAllCatalogues({ 28 | ...getLayoutFindParams(router), 29 | }) 30 | .then(resp => { 31 | const newList: IIndexViewItem[] = resp.map(data => { 32 | return { 33 | id: data.id, 34 | label: data.name, 35 | editUrl: getCatalogueUpdatePageUrl(data.id), 36 | isInactive: !data.isActive, 37 | } 38 | }) 39 | 40 | setList(newList) 41 | }) 42 | .finally(() => { 43 | setFetching(false) 44 | }) 45 | }, [router.asPath]) 46 | 47 | const links: IPageLink[] = [ 48 | { 49 | label: 'View', 50 | url: getCataloguePageUrl(), 51 | pagePaths: ['/catalogue'], 52 | }, 53 | { 54 | label: 'Search', 55 | url: getCatalogueSearchPageUrl(), 56 | }, 57 | { 58 | label: 'Add', 59 | url: getCatalogueUpdatePageUrl(), 60 | }, 61 | ] 62 | 63 | return ( 64 |
65 | 66 |
67 | 68 | 69 |
70 |
71 |
72 | ) 73 | } 74 | 75 | export const getStaticProps: GetStaticProps = async context => { 76 | return { 77 | props: { 78 | pageData: null, 79 | layoutData: { 80 | seo: { 81 | title: 'Catalogue - CMS', 82 | }, 83 | headerTitle: 'Catalogue', 84 | }, 85 | }, 86 | } 87 | } 88 | 89 | export default Catalogue 90 | -------------------------------------------------------------------------------- /pages/catalogue/search.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import { getCataloguePageUrl, getCatalogueSearchPageUrl, getCatalogueUpdatePageUrl } from '../../utils/catalogue' 7 | import { useRouter } from 'next/router' 8 | import SearchLayout, { SearchType } from '../../components/layout/SearchLayout' 9 | import { IIndexViewItem } from '../../components/layout/IndexViewLayout' 10 | import { getCatalogueById, searchCatalogueByName } from '../../http/catalogue' 11 | import appConfig from '../../config/appConfig' 12 | 13 | interface IProps extends IGlobalLayoutProps { 14 | pageData: {} 15 | } 16 | 17 | const CatalogueSearch: NextPage = props => { 18 | const router = useRouter() 19 | 20 | const links: IPageLink[] = [ 21 | { 22 | label: 'View', 23 | url: getCataloguePageUrl(), 24 | }, 25 | { 26 | label: 'Search', 27 | url: getCatalogueSearchPageUrl(), 28 | }, 29 | { 30 | label: 'Add', 31 | url: getCatalogueUpdatePageUrl(), 32 | }, 33 | ] 34 | 35 | const getIndexList = (searchType: SearchType, value: string | number): Promise => { 36 | if (searchType === SearchType.NAME) { 37 | return searchCatalogueByName(value as string, { 38 | limit: appConfig.search.limit, 39 | }).then(resp => { 40 | const newList: IIndexViewItem[] = resp.map(data => { 41 | return { 42 | id: data.id, 43 | label: data.name, 44 | viewUrl: getCatalogueUpdatePageUrl(data.id), 45 | isInactive: !data.isActive, 46 | } 47 | }) 48 | return newList 49 | }) 50 | } 51 | 52 | if (searchType === SearchType.ID) { 53 | return getCatalogueById(Number(value)).then(resp => { 54 | const list: IIndexViewItem[] = [ 55 | { 56 | id: resp.id, 57 | label: resp.name, 58 | viewUrl: getCatalogueUpdatePageUrl(resp.id), 59 | isInactive: !resp.isActive, 60 | }, 61 | ] 62 | return list 63 | }) 64 | } 65 | 66 | return new Promise(res => res([])) 67 | } 68 | 69 | return ( 70 |
71 | 72 | 73 | 74 |
75 | ) 76 | } 77 | 78 | export const getStaticProps: GetStaticProps = async context => { 79 | return { 80 | props: { 81 | pageData: null, 82 | layoutData: { 83 | seo: { 84 | title: 'Catalogue - Search - CMS', 85 | }, 86 | headerTitle: 'Catalogue - Search', 87 | }, 88 | }, 89 | } 90 | } 91 | 92 | export default CatalogueSearch 93 | -------------------------------------------------------------------------------- /pages/currency/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from './../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import { getCurrencyPageUrl, getCurrencyUpdatePageUrl } from '../../utils/currency' 7 | import IndexViewLayout, { IIndexViewItem } from '../../components/layout/IndexViewLayout' 8 | import { getAllCurrencies } from '../../http/currency' 9 | import useLoginEffect from '../../hooks/useLoginEffect' 10 | import { PAGE_SUMMARY } from '../../constants/constants' 11 | 12 | interface IProps extends IGlobalLayoutProps { 13 | pageData: {} 14 | } 15 | 16 | const Currency: NextPage = props => { 17 | const [list, setList] = useState([]) 18 | const [fetching, setFetching] = useState(false) 19 | 20 | useLoginEffect(() => { 21 | setFetching(true) 22 | 23 | getAllCurrencies() 24 | .then(resp => { 25 | const newList: IIndexViewItem[] = resp.map(data => { 26 | return { 27 | id: data.id, 28 | label: data.name, 29 | description: data.isoCode, 30 | editUrl: getCurrencyUpdatePageUrl(data.id), 31 | } 32 | }) 33 | 34 | setList(newList) 35 | }) 36 | .finally(() => { 37 | setFetching(false) 38 | }) 39 | }, []) 40 | 41 | const links: IPageLink[] = [ 42 | { 43 | label: 'View', 44 | url: getCurrencyPageUrl(), 45 | }, 46 | { 47 | label: 'Add', 48 | url: getCurrencyUpdatePageUrl(), 49 | }, 50 | ] 51 | 52 | return ( 53 |
54 | 55 |
56 | 57 |
58 |
59 |
60 | ) 61 | } 62 | 63 | export const getStaticProps: GetStaticProps = async context => { 64 | return { 65 | props: { 66 | pageData: null, 67 | layoutData: { 68 | seo: { 69 | title: 'Currency - CMS', 70 | }, 71 | headerTitle: 'Currency', 72 | }, 73 | }, 74 | } 75 | } 76 | 77 | export default Currency 78 | -------------------------------------------------------------------------------- /pages/faq-topic/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import IndexViewLayout, { IIndexViewItem } from '../../components/layout/IndexViewLayout' 7 | import useLoginEffect from '../../hooks/useLoginEffect' 8 | import { getAllFAQTopics } from '../../http/faq' 9 | import { getFAQPageUrl, getFAQTopicPageUrl, getFAQTopicUpdatePageUrl } from '../../utils/faq' 10 | import { PAGE_SUMMARY } from '../../constants/constants' 11 | 12 | interface IProps extends IGlobalLayoutProps { 13 | pageData: {} 14 | } 15 | 16 | const FAQTopic: NextPage = props => { 17 | const [list, setList] = useState([]) 18 | const [fetching, setFetching] = useState(false) 19 | 20 | useLoginEffect(() => { 21 | setFetching(true) 22 | 23 | getAllFAQTopics() 24 | .then(resp => { 25 | const newList: IIndexViewItem[] = resp.map(data => { 26 | return { 27 | id: data.id, 28 | label: data.name, 29 | editUrl: getFAQTopicUpdatePageUrl(data.id), 30 | viewUrl: getFAQPageUrl(data.id), 31 | isInactive: !data.isActive, 32 | } 33 | }) 34 | 35 | setList(newList) 36 | }) 37 | .finally(() => { 38 | setFetching(false) 39 | }) 40 | }, []) 41 | 42 | const links: IPageLink[] = [ 43 | { 44 | label: 'View', 45 | url: getFAQTopicPageUrl(), 46 | }, 47 | { 48 | label: 'Add', 49 | url: getFAQTopicUpdatePageUrl(), 50 | }, 51 | ] 52 | 53 | return ( 54 |
55 | 56 |
57 | 58 |
59 |
60 |
61 | ) 62 | } 63 | 64 | export const getStaticProps: GetStaticProps = async context => { 65 | return { 66 | props: { 67 | pageData: null, 68 | layoutData: { 69 | seo: { 70 | title: 'FAQ Topic - CMS', 71 | }, 72 | headerTitle: 'FAQ Topic', 73 | }, 74 | }, 75 | } 76 | } 77 | 78 | export default FAQTopic 79 | -------------------------------------------------------------------------------- /pages/image/search.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import { getCataloguePageUrl, getCatalogueSearchPageUrl, getCatalogueUpdatePageUrl } from '../../utils/catalogue' 7 | import { useRouter } from 'next/router' 8 | import SearchLayout, { SearchType } from '../../components/layout/SearchLayout' 9 | import { IIndexViewItem } from '../../components/layout/IndexViewLayout' 10 | import { getCatalogueById, searchCatalogueByName } from '../../http/catalogue' 11 | import appConfig from '../../config/appConfig' 12 | import { getImagePageUrl, getImageSearchPageUrl, getImageUpdatePageUrl } from '../../utils/image' 13 | import { getImageById, searchImageByName } from '../../http/image' 14 | 15 | interface IProps extends IGlobalLayoutProps { 16 | pageData: {} 17 | } 18 | 19 | const CatalogueSearch: NextPage = props => { 20 | const router = useRouter() 21 | 22 | const links: IPageLink[] = [ 23 | { 24 | label: 'View', 25 | url: getImagePageUrl(), 26 | }, 27 | { 28 | label: 'Search', 29 | url: getImageSearchPageUrl(), 30 | }, 31 | { 32 | label: 'Add', 33 | url: getImageUpdatePageUrl(), 34 | }, 35 | ] 36 | 37 | const getIndexList = (searchType: SearchType, value: string | number): Promise => { 38 | if (searchType === SearchType.NAME) { 39 | return searchImageByName(value as string, { 40 | limit: appConfig.search.limit, 41 | }).then(resp => { 42 | const newList: IIndexViewItem[] = resp.map(data => { 43 | return { 44 | id: data.id, 45 | label: data.name, 46 | viewUrl: getImageUpdatePageUrl(data.id), 47 | } 48 | }) 49 | return newList 50 | }) 51 | } 52 | 53 | if (searchType === SearchType.ID) { 54 | return getImageById(Number(value)).then(resp => { 55 | const list: IIndexViewItem[] = [ 56 | { 57 | id: resp.id, 58 | label: resp.name, 59 | viewUrl: getImageUpdatePageUrl(resp.id), 60 | }, 61 | ] 62 | return list 63 | }) 64 | } 65 | 66 | return new Promise(res => res([])) 67 | } 68 | 69 | return ( 70 |
71 | 72 | 73 | 74 |
75 | ) 76 | } 77 | 78 | export const getStaticProps: GetStaticProps = async context => { 79 | return { 80 | props: { 81 | pageData: null, 82 | layoutData: { 83 | seo: { 84 | title: 'Image - Search - CMS', 85 | }, 86 | headerTitle: 'Image - Search', 87 | }, 88 | }, 89 | } 90 | } 91 | 92 | export default CatalogueSearch 93 | -------------------------------------------------------------------------------- /pages/order-status-type/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import IndexViewLayout, { IIndexViewItem } from '../../components/layout/IndexViewLayout' 7 | import useLoginEffect from '../../hooks/useLoginEffect' 8 | import { getAllOrderStatus } from '../../http/order' 9 | import { getOrderStatusPageUrl, getOrderStatusUpdatePageUrl } from '../../utils/order' 10 | 11 | interface IProps extends IGlobalLayoutProps { 12 | pageData: {} 13 | } 14 | 15 | const OrderStatusType: NextPage = props => { 16 | const [list, setList] = useState([]) 17 | const [fetching, setFetching] = useState(false) 18 | 19 | useLoginEffect(() => { 20 | setFetching(true) 21 | 22 | getAllOrderStatus() 23 | .then(resp => { 24 | const newList: IIndexViewItem[] = resp.map(data => { 25 | return { 26 | id: data.id, 27 | label: data.name, 28 | editUrl: getOrderStatusUpdatePageUrl(data.id), 29 | } 30 | }) 31 | 32 | setList(newList) 33 | }) 34 | .finally(() => { 35 | setFetching(false) 36 | }) 37 | }, []) 38 | 39 | const links: IPageLink[] = [ 40 | { 41 | label: 'View', 42 | url: getOrderStatusPageUrl(), 43 | }, 44 | { 45 | label: 'Add', 46 | url: getOrderStatusUpdatePageUrl(), 47 | }, 48 | ] 49 | 50 | return ( 51 |
52 | 53 |
54 | 55 |
56 |
57 |
58 | ) 59 | } 60 | 61 | export const getStaticProps: GetStaticProps = async context => { 62 | return { 63 | props: { 64 | pageData: null, 65 | layoutData: { 66 | seo: { 67 | title: 'Order Status Type - CMS', 68 | }, 69 | headerTitle: 'Order Status Type', 70 | }, 71 | }, 72 | } 73 | } 74 | 75 | export default OrderStatusType 76 | -------------------------------------------------------------------------------- /pages/privacy-policy/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import useLoginEffect from '../../hooks/useLoginEffect' 5 | import EscapeHTML from '../../components/EscapeHTML' 6 | import { IPageLink } from '../../components/layout/PageLinks' 7 | import PageLayout from '../../components/layout/PageLayout' 8 | import { getPrivacyPolicyDetail } from '../../http/privacyPolicy' 9 | import { getPrivacyPolicyPageUrl, getPrivacyPolicyUpdatePageUrl } from '../../utils/privacyPolicy' 10 | import PageLoader from '../../components/loader/PageLoader' 11 | import { PAGE_SUMMARY } from '../../constants/constants' 12 | import { IStaticPageDetail } from '../../contract/staticPage' 13 | 14 | interface IProps extends IGlobalLayoutProps { 15 | pageData: {} 16 | } 17 | 18 | const PrivacyPolicy: NextPage = props => { 19 | const [detail, setDetail] = useState(null) 20 | 21 | useLoginEffect(() => { 22 | getPrivacyPolicyDetail().then(resp => { 23 | setDetail(resp) 24 | }) 25 | }, []) 26 | 27 | const links: IPageLink[] = [ 28 | { 29 | label: 'View', 30 | url: getPrivacyPolicyPageUrl(), 31 | }, 32 | { 33 | label: 'Edit', 34 | url: getPrivacyPolicyUpdatePageUrl(), 35 | }, 36 | ] 37 | 38 | return ( 39 |
40 | 41 | {!detail ? ( 42 | 43 | ) : ( 44 |
45 |
46 |
{detail.title}
47 |
48 | 49 |
50 | 51 |
52 |
53 | )} 54 |
55 |
56 | ) 57 | } 58 | 59 | export const getStaticProps: GetStaticProps = async context => { 60 | return { 61 | props: { 62 | pageData: null, 63 | layoutData: { 64 | seo: { 65 | title: 'Privacy Policy - CMS', 66 | }, 67 | headerTitle: 'Privacy Policy', 68 | }, 69 | }, 70 | } 71 | } 72 | 73 | export default PrivacyPolicy 74 | -------------------------------------------------------------------------------- /pages/product-attribute-key/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import IndexViewLayout, { IIndexViewItem } from '../../components/layout/IndexViewLayout' 7 | import useLoginEffect from '../../hooks/useLoginEffect' 8 | import { getAllProductAttributeKeys, getAllProductBrands } from '../../http/product' 9 | import { 10 | getProductAttributeKeyPageUrl, 11 | getProductAttributeKeyUpdatePageUrl, 12 | getProductBrandPageUrl, 13 | getProductBrandUpdatePageUrl, 14 | } from '../../utils/product' 15 | import { useRouter } from 'next/router' 16 | import { getLayoutFindParams } from '../../utils/common' 17 | import LayoutPagination from '../../components/layout/LayoutPagination' 18 | 19 | interface IProps extends IGlobalLayoutProps { 20 | pageData: {} 21 | } 22 | 23 | const ProductAttributeKey: NextPage = props => { 24 | const [list, setList] = useState([]) 25 | const [fetching, setFetching] = useState(false) 26 | 27 | const router = useRouter() 28 | 29 | useLoginEffect(() => { 30 | setFetching(true) 31 | 32 | getAllProductAttributeKeys({ 33 | ...getLayoutFindParams(router), 34 | }) 35 | .then(resp => { 36 | const newList: IIndexViewItem[] = resp.map(data => { 37 | return { 38 | id: data.id, 39 | label: data.name, 40 | editUrl: getProductAttributeKeyUpdatePageUrl(data.id), 41 | } 42 | }) 43 | 44 | setList(newList) 45 | }) 46 | .finally(() => { 47 | setFetching(false) 48 | }) 49 | }, [router.asPath]) 50 | 51 | const links: IPageLink[] = [ 52 | { 53 | label: 'View', 54 | url: getProductAttributeKeyPageUrl(), 55 | pagePaths: ['/product-attribute-key'], 56 | }, 57 | { 58 | label: 'Add', 59 | url: getProductAttributeKeyUpdatePageUrl(), 60 | }, 61 | ] 62 | 63 | return ( 64 |
65 | 66 |
67 | 68 | 69 |
70 |
71 |
72 | ) 73 | } 74 | 75 | export const getStaticProps: GetStaticProps = async context => { 76 | return { 77 | props: { 78 | pageData: null, 79 | layoutData: { 80 | seo: { 81 | title: 'Product Attribute Keys - CMS', 82 | }, 83 | headerTitle: 'Product Attribute Keys', 84 | }, 85 | }, 86 | } 87 | } 88 | 89 | export default ProductAttributeKey 90 | -------------------------------------------------------------------------------- /pages/product-brand/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from './../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import IndexViewLayout, { IIndexViewItem } from '../../components/layout/IndexViewLayout' 7 | import useLoginEffect from '../../hooks/useLoginEffect' 8 | import { getAllProductBrands } from '../../http/product' 9 | import { getProductBrandPageUrl, getProductBrandUpdatePageUrl } from '../../utils/product' 10 | import LayoutPagination from '../../components/layout/LayoutPagination' 11 | import { useRouter } from 'next/router' 12 | import { getLayoutFindParams } from '../../utils/common' 13 | 14 | interface IProps extends IGlobalLayoutProps { 15 | pageData: {} 16 | } 17 | 18 | const ProductBrand: NextPage = props => { 19 | const [list, setList] = useState([]) 20 | const [fetching, setFetching] = useState(false) 21 | 22 | const router = useRouter() 23 | 24 | useLoginEffect(() => { 25 | setFetching(true) 26 | 27 | getAllProductBrands({ 28 | ...getLayoutFindParams(router), 29 | }) 30 | .then(resp => { 31 | const newList: IIndexViewItem[] = resp.map(data => { 32 | return { 33 | id: data.id, 34 | label: data.name, 35 | description: data.description, 36 | editUrl: getProductBrandUpdatePageUrl(data.id), 37 | } 38 | }) 39 | 40 | setList(newList) 41 | }) 42 | .finally(() => { 43 | setFetching(false) 44 | }) 45 | }, [router.asPath]) 46 | 47 | const links: IPageLink[] = [ 48 | { 49 | label: 'View', 50 | url: getProductBrandPageUrl(), 51 | pagePaths: ['/product-brand'], 52 | }, 53 | { 54 | label: 'Add', 55 | url: getProductBrandUpdatePageUrl(), 56 | }, 57 | ] 58 | 59 | return ( 60 |
61 | 62 |
63 | 64 | 65 |
66 |
67 |
68 | ) 69 | } 70 | 71 | export const getStaticProps: GetStaticProps = async context => { 72 | return { 73 | props: { 74 | pageData: null, 75 | layoutData: { 76 | seo: { 77 | title: 'Product Brand - CMS', 78 | }, 79 | headerTitle: 'Product Brand', 80 | }, 81 | }, 82 | } 83 | } 84 | 85 | export default ProductBrand 86 | -------------------------------------------------------------------------------- /pages/product-relations/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import IndexViewLayout, { IIndexViewItem } from '../../components/layout/IndexViewLayout' 7 | import useLoginEffect from '../../hooks/useLoginEffect' 8 | import { getAllProductsRelations } from '../../http/product' 9 | import { getProductRelationsPageUrl, getProductRelationsUpdatePageUrl } from '../../utils/product' 10 | import { useRouter } from 'next/router' 11 | import { getLayoutFindParams } from '../../utils/common' 12 | import LayoutPagination from '../../components/layout/LayoutPagination' 13 | 14 | interface IProps extends IGlobalLayoutProps { 15 | pageData: {} 16 | } 17 | 18 | const ProductRelations: NextPage = props => { 19 | const [list, setList] = useState([]) 20 | const [fetching, setFetching] = useState(false) 21 | 22 | const router = useRouter() 23 | 24 | useLoginEffect(() => { 25 | setFetching(true) 26 | 27 | getAllProductsRelations({ 28 | ...getLayoutFindParams(router), 29 | }) 30 | .then(resp => { 31 | const newList: IIndexViewItem[] = resp.map(data => { 32 | return { 33 | id: data.id, 34 | label: data.name, 35 | editUrl: getProductRelationsUpdatePageUrl(data.id), 36 | } 37 | }) 38 | 39 | setList(newList) 40 | }) 41 | .finally(() => { 42 | setFetching(false) 43 | }) 44 | }, [router.asPath]) 45 | 46 | const links: IPageLink[] = [ 47 | { 48 | label: 'View', 49 | url: getProductRelationsPageUrl(), 50 | pagePaths: ['/product-relations'], 51 | }, 52 | { 53 | label: 'Add', 54 | url: getProductRelationsUpdatePageUrl(), 55 | }, 56 | ] 57 | 58 | return ( 59 |
60 | 61 |
62 | 63 | 64 |
65 |
66 |
67 | ) 68 | } 69 | 70 | export const getStaticProps: GetStaticProps = async context => { 71 | return { 72 | props: { 73 | pageData: null, 74 | layoutData: { 75 | seo: { 76 | title: 'Product Relations - CMS', 77 | }, 78 | headerTitle: 'Product Relations', 79 | }, 80 | }, 81 | } 82 | } 83 | 84 | export default ProductRelations 85 | -------------------------------------------------------------------------------- /pages/product/combo/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../../components/layout/PageLayout' 5 | import { IPageLink } from '../../../components/layout/PageLinks' 6 | import IndexViewLayout, { IIndexViewItem } from '../../../components/layout/IndexViewLayout' 7 | import useLoginEffect from '../../../hooks/useLoginEffect' 8 | import { getAllComboProducts } from '../../../http/product' 9 | import { 10 | getComboProductPageUrl, 11 | getComboProductSearchPageUrl, 12 | getComboProductUpdatePageUrl, 13 | } from '../../../utils/product' 14 | import { useRouter } from 'next/router' 15 | import LayoutPagination from '../../../components/layout/LayoutPagination' 16 | import { getLayoutFindParams } from '../../../utils/common' 17 | 18 | interface IProps extends IGlobalLayoutProps { 19 | pageData: {} 20 | } 21 | 22 | const ComboProduct: NextPage = props => { 23 | const [list, setList] = useState([]) 24 | const [fetching, setFetching] = useState(false) 25 | 26 | const router = useRouter() 27 | 28 | useLoginEffect(() => { 29 | setFetching(true) 30 | 31 | getAllComboProducts({ 32 | ...getLayoutFindParams(router), 33 | }) 34 | .then(resp => { 35 | const newList: IIndexViewItem[] = resp.map(data => { 36 | return { 37 | id: data.id, 38 | label: data.name, 39 | editUrl: getComboProductUpdatePageUrl(data.id), 40 | isInactive: !data.isActive, 41 | } 42 | }) 43 | 44 | setList(newList) 45 | }) 46 | .finally(() => { 47 | setFetching(false) 48 | }) 49 | }, [router.asPath]) 50 | 51 | const links: IPageLink[] = [ 52 | { 53 | label: 'View', 54 | url: getComboProductPageUrl(), 55 | pagePaths: ['/product/combo'], 56 | }, 57 | { 58 | label: 'Search', 59 | url: getComboProductSearchPageUrl(), 60 | }, 61 | { 62 | label: 'Add', 63 | url: getComboProductUpdatePageUrl(), 64 | }, 65 | ] 66 | 67 | return ( 68 |
69 | 70 |
71 | 72 | 73 |
74 |
75 |
76 | ) 77 | } 78 | 79 | export const getStaticProps: GetStaticProps = async context => { 80 | return { 81 | props: { 82 | pageData: null, 83 | layoutData: { 84 | seo: { 85 | title: 'Combo Product - CMS', 86 | }, 87 | headerTitle: 'Combo Product', 88 | }, 89 | }, 90 | } 91 | } 92 | 93 | export default ComboProduct 94 | -------------------------------------------------------------------------------- /pages/product/individual/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../../components/layout/PageLayout' 5 | import { IPageLink } from '../../../components/layout/PageLinks' 6 | import IndexViewLayout, { IIndexViewItem } from '../../../components/layout/IndexViewLayout' 7 | import useLoginEffect from '../../../hooks/useLoginEffect' 8 | import { getAllIndividualProducts } from '../../../http/product' 9 | import { 10 | getIndividualProductPageUrl, 11 | getIndividualProductSearchPageUrl, 12 | getIndividualProductUpdatePageUrl, 13 | } from '../../../utils/product' 14 | import LayoutPagination from '../../../components/layout/LayoutPagination' 15 | import { useRouter } from 'next/router' 16 | import { getLayoutFindParams } from '../../../utils/common' 17 | 18 | interface IProps extends IGlobalLayoutProps { 19 | pageData: {} 20 | } 21 | 22 | const IndividualProduct: NextPage = props => { 23 | const [list, setList] = useState([]) 24 | const [fetching, setFetching] = useState(false) 25 | 26 | const router = useRouter() 27 | 28 | useLoginEffect(() => { 29 | setFetching(true) 30 | 31 | getAllIndividualProducts({ 32 | ...getLayoutFindParams(router), 33 | }) 34 | .then(resp => { 35 | const newList: IIndexViewItem[] = resp.map(data => { 36 | return { 37 | id: data.id, 38 | label: data.name, 39 | description: data.catalogue.name, 40 | editUrl: getIndividualProductUpdatePageUrl(data.id), 41 | isInactive: !data.isActive, 42 | } 43 | }) 44 | 45 | setList(newList) 46 | }) 47 | .finally(() => { 48 | setFetching(false) 49 | }) 50 | }, [router.asPath]) 51 | 52 | const links: IPageLink[] = [ 53 | { 54 | label: 'View', 55 | url: getIndividualProductPageUrl(), 56 | pagePaths: ['/product/individual'], 57 | }, 58 | { 59 | label: 'Search', 60 | url: getIndividualProductSearchPageUrl(), 61 | }, 62 | { 63 | label: 'Add', 64 | url: getIndividualProductUpdatePageUrl(), 65 | }, 66 | ] 67 | 68 | return ( 69 |
70 | 71 |
72 | 73 | 74 |
75 |
76 |
77 | ) 78 | } 79 | 80 | export const getStaticProps: GetStaticProps = async context => { 81 | return { 82 | props: { 83 | pageData: null, 84 | layoutData: { 85 | seo: { 86 | title: 'Product - CMS', 87 | }, 88 | headerTitle: 'Product', 89 | }, 90 | }, 91 | } 92 | } 93 | 94 | export default IndividualProduct 95 | -------------------------------------------------------------------------------- /pages/refund-policy/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import useLoginEffect from '../../hooks/useLoginEffect' 5 | import EscapeHTML from '../../components/EscapeHTML' 6 | import { IPageLink } from '../../components/layout/PageLinks' 7 | import PageLayout from '../../components/layout/PageLayout' 8 | import { getRefundPolicyDetail } from '../../http/refundPolicy' 9 | import { getRefundPolicyPageUrl, getRefundPolicyUpdatePageUrl } from '../../utils/refundPolicy' 10 | import PageLoader from '../../components/loader/PageLoader' 11 | import { PAGE_SUMMARY } from '../../constants/constants' 12 | import { IStaticPageDetail } from '../../contract/staticPage' 13 | 14 | interface IProps extends IGlobalLayoutProps { 15 | pageData: {} 16 | } 17 | 18 | const RefundPolicy: NextPage = props => { 19 | const [detail, setDetail] = useState(null) 20 | 21 | useLoginEffect(() => { 22 | getRefundPolicyDetail().then(resp => { 23 | setDetail(resp) 24 | }) 25 | }, []) 26 | 27 | const links: IPageLink[] = [ 28 | { 29 | label: 'View', 30 | url: getRefundPolicyPageUrl(), 31 | }, 32 | { 33 | label: 'Edit', 34 | url: getRefundPolicyUpdatePageUrl(), 35 | }, 36 | ] 37 | 38 | return ( 39 |
40 | 41 | {!detail ? ( 42 | 43 | ) : ( 44 |
45 |
46 |
{detail.title}
47 |
48 | 49 |
50 | 51 |
52 |
53 | )} 54 |
55 |
56 | ) 57 | } 58 | 59 | export const getStaticProps: GetStaticProps = async context => { 60 | return { 61 | props: { 62 | pageData: null, 63 | layoutData: { 64 | seo: { 65 | title: 'Refund Policy - CMS', 66 | }, 67 | headerTitle: 'Refund Policy', 68 | }, 69 | }, 70 | } 71 | } 72 | 73 | export default RefundPolicy 74 | -------------------------------------------------------------------------------- /pages/security-question/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from './../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import { getCurrencyPageUrl, getCurrencyUpdatePageUrl } from '../../utils/currency' 7 | import IndexViewLayout, { IIndexViewItem } from '../../components/layout/IndexViewLayout' 8 | import { getAllCurrencies } from '../../http/currency' 9 | import useLoginEffect from '../../hooks/useLoginEffect' 10 | import { getSecurityQuestionPageUrl, getSecurityQuestionUpdatePageUrl } from '../../utils/securityQuestion' 11 | import { getAllSecurityQuestions } from '../../http/securityQuestion' 12 | import { PAGE_SUMMARY } from '../../constants/constants' 13 | 14 | interface IProps extends IGlobalLayoutProps { 15 | pageData: {} 16 | } 17 | 18 | const SecurityQuestion: NextPage = props => { 19 | const [list, setList] = useState([]) 20 | const [fetching, setFetching] = useState(false) 21 | 22 | useLoginEffect(() => { 23 | setFetching(true) 24 | 25 | getAllSecurityQuestions() 26 | .then(resp => { 27 | const newList: IIndexViewItem[] = resp.map(data => { 28 | return { 29 | id: data.id, 30 | label: data.question, 31 | editUrl: getSecurityQuestionUpdatePageUrl(data.id), 32 | } 33 | }) 34 | 35 | setList(newList) 36 | }) 37 | .finally(() => { 38 | setFetching(false) 39 | }) 40 | }, []) 41 | 42 | const links: IPageLink[] = [ 43 | { 44 | label: 'View', 45 | url: getSecurityQuestionPageUrl(), 46 | }, 47 | { 48 | label: 'Add', 49 | url: getSecurityQuestionUpdatePageUrl(), 50 | }, 51 | ] 52 | 53 | return ( 54 |
55 | 56 |
57 | 58 |
59 |
60 |
61 | ) 62 | } 63 | 64 | export const getStaticProps: GetStaticProps = async context => { 65 | return { 66 | props: { 67 | pageData: null, 68 | layoutData: { 69 | seo: { 70 | title: 'Security Question - CMS', 71 | }, 72 | headerTitle: 'Security Question', 73 | }, 74 | }, 75 | } 76 | } 77 | 78 | export default SecurityQuestion 79 | -------------------------------------------------------------------------------- /pages/shipping-policy/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import useLoginEffect from '../../hooks/useLoginEffect' 5 | import EscapeHTML from '../../components/EscapeHTML' 6 | import { IPageLink } from '../../components/layout/PageLinks' 7 | import PageLayout from '../../components/layout/PageLayout' 8 | import { getShippingPolicyDetail } from '../../http/shippingPolicy' 9 | import { getShippingPolicyPageUrl, getShippingPolicyUpdatePageUrl } from '../../utils/shippingPolicy' 10 | import PageLoader from '../../components/loader/PageLoader' 11 | import { PAGE_SUMMARY } from '../../constants/constants' 12 | import { IStaticPageDetail } from '../../contract/staticPage' 13 | 14 | interface IProps extends IGlobalLayoutProps { 15 | pageData: {} 16 | } 17 | 18 | const ShippingPolicy: NextPage = props => { 19 | const [detail, setDetail] = useState(null) 20 | 21 | useLoginEffect(() => { 22 | getShippingPolicyDetail().then(resp => { 23 | setDetail(resp) 24 | }) 25 | }, []) 26 | 27 | const links: IPageLink[] = [ 28 | { 29 | label: 'View', 30 | url: getShippingPolicyPageUrl(), 31 | }, 32 | { 33 | label: 'Edit', 34 | url: getShippingPolicyUpdatePageUrl(), 35 | }, 36 | ] 37 | 38 | return ( 39 |
40 | 41 | {!detail ? ( 42 | 43 | ) : ( 44 |
45 |
46 |
{detail.title}
47 |
48 | 49 |
50 | 51 |
52 |
53 | )} 54 |
55 |
56 | ) 57 | } 58 | 59 | export const getStaticProps: GetStaticProps = async context => { 60 | return { 61 | props: { 62 | pageData: null, 63 | layoutData: { 64 | seo: { 65 | title: 'Shipping Policy - CMS', 66 | }, 67 | headerTitle: 'Shipping Policy', 68 | }, 69 | }, 70 | } 71 | } 72 | 73 | export default ShippingPolicy 74 | -------------------------------------------------------------------------------- /pages/terms-conditions/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import useLoginEffect from '../../hooks/useLoginEffect' 5 | import { getTnCDetail } from '../../http/tnc' 6 | import EscapeHTML from '../../components/EscapeHTML' 7 | import { IPageLink } from '../../components/layout/PageLinks' 8 | import { getTnCPageUrl, getTnCUpdatePageUrl } from '../../utils/tnc' 9 | import PageLayout from '../../components/layout/PageLayout' 10 | import PageLoader from '../../components/loader/PageLoader' 11 | import { PAGE_SUMMARY } from '../../constants/constants' 12 | import { IStaticPageDetail } from '../../contract/staticPage' 13 | 14 | interface IProps extends IGlobalLayoutProps { 15 | pageData: {} 16 | } 17 | 18 | const TnC: NextPage = props => { 19 | const [detail, setDetail] = useState(null) 20 | 21 | useLoginEffect(() => { 22 | getTnCDetail().then(resp => { 23 | setDetail(resp) 24 | }) 25 | }, []) 26 | 27 | const links: IPageLink[] = [ 28 | { 29 | label: 'View', 30 | url: getTnCPageUrl(), 31 | }, 32 | { 33 | label: 'Edit', 34 | url: getTnCUpdatePageUrl(), 35 | }, 36 | ] 37 | 38 | return ( 39 |
40 | 41 | {!detail ? ( 42 | 43 | ) : ( 44 |
45 |
46 |
{detail.title}
47 |
48 | 49 |
50 | 51 |
52 |
53 | )} 54 |
55 |
56 | ) 57 | } 58 | 59 | export const getStaticProps: GetStaticProps = async context => { 60 | return { 61 | props: { 62 | pageData: null, 63 | layoutData: { 64 | seo: { 65 | title: 'Terms & Conditions - CMS', 66 | }, 67 | headerTitle: 'Terms & Conditions', 68 | }, 69 | }, 70 | } 71 | } 72 | 73 | export default TnC 74 | -------------------------------------------------------------------------------- /pages/user/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from './../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import IndexViewLayout, { IIndexViewItem } from '../../components/layout/IndexViewLayout' 7 | import useLoginEffect from '../../hooks/useLoginEffect' 8 | import { PAGE_SUMMARY } from '../../constants/constants' 9 | import { getAllUsers } from '../../http/user' 10 | import { getUserPageUrl, getUserDetailPageUrl, getUserSearchPageUrl } from '../../utils/user' 11 | import { useRouter } from 'next/router' 12 | import { getLayoutFindParams } from '../../utils/common' 13 | import LayoutPagination from '../../components/layout/LayoutPagination' 14 | 15 | interface IProps extends IGlobalLayoutProps { 16 | pageData: {} 17 | } 18 | 19 | const User: NextPage = props => { 20 | const [list, setList] = useState([]) 21 | const [fetching, setFetching] = useState(false) 22 | 23 | const router = useRouter() 24 | 25 | useLoginEffect(() => { 26 | setFetching(true) 27 | 28 | getAllUsers({ 29 | ...getLayoutFindParams(router), 30 | }) 31 | .then(resp => { 32 | const newList: IIndexViewItem[] = resp.map(data => { 33 | return { 34 | id: data.id, 35 | label: data.email, 36 | description: data.name, 37 | editUrl: getUserDetailPageUrl(data.id), 38 | isInactive: !data.isActive, 39 | } 40 | }) 41 | 42 | setList(newList) 43 | }) 44 | .finally(() => { 45 | setFetching(false) 46 | }) 47 | }, [router.asPath]) 48 | 49 | const links: IPageLink[] = [ 50 | { 51 | label: 'View', 52 | url: getUserPageUrl(), 53 | pagePaths: ['/user'], 54 | }, 55 | { 56 | label: 'Search', 57 | url: getUserSearchPageUrl(), 58 | }, 59 | ] 60 | 61 | return ( 62 |
63 | 64 |
65 | 66 | 67 |
68 |
69 |
70 | ) 71 | } 72 | 73 | export const getStaticProps: GetStaticProps = async context => { 74 | return { 75 | props: { 76 | pageData: null, 77 | layoutData: { 78 | seo: { 79 | title: 'User - CMS', 80 | }, 81 | headerTitle: 'User', 82 | }, 83 | }, 84 | } 85 | } 86 | 87 | export default User 88 | -------------------------------------------------------------------------------- /pages/user/search.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { IGlobalLayoutProps } from '../_app' 3 | import { NextPage, GetStaticProps } from 'next' 4 | import PageLayout from '../../components/layout/PageLayout' 5 | import { IPageLink } from '../../components/layout/PageLinks' 6 | import { useRouter } from 'next/router' 7 | import SearchLayout, { SearchType } from '../../components/layout/SearchLayout' 8 | import { IIndexViewItem } from '../../components/layout/IndexViewLayout' 9 | import appConfig from '../../config/appConfig' 10 | import { getUserDetailPageUrl, getUserPageUrl, getUserSearchPageUrl } from '../../utils/user' 11 | import { getUserGlobalDetail, searchUserByName } from '../../http/user' 12 | 13 | interface IProps extends IGlobalLayoutProps { 14 | pageData: {} 15 | } 16 | 17 | const UserSearch: NextPage = props => { 18 | const router = useRouter() 19 | 20 | const links: IPageLink[] = [ 21 | { 22 | label: 'View', 23 | url: getUserPageUrl(), 24 | }, 25 | { 26 | label: 'Search', 27 | url: getUserSearchPageUrl(), 28 | }, 29 | ] 30 | 31 | const getIndexList = (searchType: SearchType, value: string | number): Promise => { 32 | if (searchType === SearchType.NAME) { 33 | return searchUserByName(value as string, { 34 | limit: appConfig.search.limit, 35 | }).then(resp => { 36 | const newList: IIndexViewItem[] = resp.map(data => { 37 | return { 38 | id: data.id, 39 | label: data.name || data.email, 40 | editUrl: getUserDetailPageUrl(data.id), 41 | isInactive: !data.isActive, 42 | } 43 | }) 44 | return newList 45 | }) 46 | } 47 | 48 | if (searchType === SearchType.ID) { 49 | return getUserGlobalDetail(Number(value), {}).then(resp => { 50 | const data = resp.userDetail 51 | const list: IIndexViewItem[] = [ 52 | { 53 | id: data.id, 54 | label: data.name || data.email, 55 | editUrl: getUserDetailPageUrl(data.id), 56 | isInactive: !data.isActive, 57 | }, 58 | ] 59 | return list 60 | }) 61 | } 62 | 63 | return new Promise(res => res([])) 64 | } 65 | 66 | return ( 67 |
68 | 69 | 70 | 71 |
72 | ) 73 | } 74 | 75 | export const getStaticProps: GetStaticProps = async context => { 76 | return { 77 | props: { 78 | pageData: null, 79 | layoutData: { 80 | seo: { 81 | title: 'User - Search - CMS', 82 | }, 83 | headerTitle: 'User - Search', 84 | }, 85 | }, 86 | } 87 | } 88 | 89 | export default UserSearch 90 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Bold/BentonSans-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Bold/BentonSans-Bold.eot -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Bold/BentonSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Bold/BentonSans-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Bold/BentonSans-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Bold/BentonSans-Bold.woff -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Bold/license.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Bold/license.txt -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Italic/BentonSans-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Italic/BentonSans-BoldItalic.woff -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Italic/BentonSans-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Italic/BentonSans-BoldItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Italic/BentonSans-MediumItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Italic/BentonSans-MediumItalic.woff -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Italic/BentonSans-MediumItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Italic/BentonSans-MediumItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Italic/BentonSans-RegularItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Italic/BentonSans-RegularItalic.woff -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Italic/BentonSans-RegularItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Italic/BentonSans-RegularItalic.woff2 -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Medium/BentonSans-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Medium/BentonSans-Medium.eot -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Medium/BentonSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Medium/BentonSans-Medium.ttf -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Medium/BentonSans-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Medium/BentonSans-Medium.woff -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Medium/license.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Medium/license.txt -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Regular/BentonSans-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Regular/BentonSans-Regular.eot -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Regular/BentonSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Regular/BentonSans-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Regular/BentonSans-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Regular/BentonSans-Regular.woff -------------------------------------------------------------------------------- /public/fonts/BentonSans/BentonSans-Regular/license.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/fonts/BentonSans/BentonSans-Regular/license.txt -------------------------------------------------------------------------------- /public/images/empty/empty-cart-basket.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/images/empty/empty-cart-basket.webp -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/images/favicon.ico -------------------------------------------------------------------------------- /public/images/icons/arrow-left-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/images/icons/arrow-right-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OwnStoreOrg/ownstore-cms/e815468d3b8d71cd9d741d5bba6fabffbd3c8728/public/images/logo.png -------------------------------------------------------------------------------- /scriptTemplates/AppContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const AppContextScript = () => ( 4 | <> 5 | 14 | 8 | 9 | ) 10 | 11 | export default MediaScripts 12 | -------------------------------------------------------------------------------- /scriptTemplates/meta.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface IProps {} 4 | 5 | const MetaTags: React.FC = props => ( 6 | <> 7 | 12 | 13 | 14 | ) 15 | 16 | export default MetaTags 17 | -------------------------------------------------------------------------------- /scripts/fileSystem.js: -------------------------------------------------------------------------------- 1 | const { readdirSync } = require('fs') 2 | const process = require('process') 3 | const path = require('path') 4 | 5 | /* 6 | Returns absolute file path of the given path. 7 | */ 8 | const getAbsPath = dirPath => { 9 | const extendedPath = typeof dirPath === 'string' ? [dirPath] : dirPath 10 | return path.resolve(process.cwd(), ...extendedPath) 11 | } 12 | 13 | /* 14 | Returns whether a path/dir is hidden or not. 15 | isHiddenPath('.next') will return true 16 | */ 17 | const isHiddenPath = path => { 18 | return /(^|\/)\.[^\/\.]/g.test(path) 19 | } 20 | 21 | /* 22 | Returns sub-directories of the source you provide. 23 | 24 | Usage: 25 | getSubDirectories(getAbsPath('app')) 26 | 27 | Returns: [ 28 | 'api', 29 | 'assets', 30 | 'components', 31 | 'constants', 32 | 'helpers', 33 | 'hooks', 34 | 'pages' 35 | ] 36 | 37 | Leaves out hidden directories such '.next' and files 'tsconfig.json' 38 | */ 39 | const getSubDirectories = source => 40 | readdirSync(source, { withFileTypes: true }) 41 | .filter(dirent => { 42 | return dirent.isDirectory() && !isHiddenPath(dirent.name) 43 | }) 44 | .map(dirent => dirent.name) 45 | 46 | module.exports = { 47 | getAbsPath, 48 | getSubDirectories, 49 | } 50 | -------------------------------------------------------------------------------- /styles/_components.scss: -------------------------------------------------------------------------------- 1 | .page-layout-desktop-width { 2 | width: calc(100% - 350px); 3 | } 4 | 5 | // Section item update modal overrides 6 | .sectionItemUpdateModalOverrides, 7 | .imageUpdateModalOverrides, 8 | .productTagUpdateModalOverrides, 9 | .productAttributeUpdateModalOverrides, 10 | .productFeatureSectionUpdateModalOverrides { 11 | & .modal-header { 12 | @apply bg-white border-b border-gray400 rounded-t-lg; 13 | } 14 | 15 | @include mobile { 16 | @apply w-full absolute m-0 bottom-0 pt-1; 17 | width: 100% !important; 18 | margin: 0 !important; 19 | border-radius: 0 !important; 20 | 21 | & .content { 22 | height: calc(100vh - 130px); 23 | @apply overflow-y-auto; 24 | } 25 | } 26 | 27 | @include desktop { 28 | width: 500px !important; 29 | 30 | & .content { 31 | height: 460px; 32 | @apply rounded-b-lg overflow-y-auto; 33 | } 34 | } 35 | } 36 | 37 | // Cancel order modal overrides 38 | .cancelOrderModalOverrides { 39 | & .modal-header { 40 | @apply bg-white border-b border-gray400 rounded-t-lg; 41 | } 42 | 43 | @include mobile { 44 | @apply w-full absolute m-0 bottom-0 pt-1; 45 | width: 100% !important; 46 | margin: 0 !important; 47 | border-radius: 0 !important; 48 | 49 | & .content { 50 | height: calc(100vh - 130px); 51 | @apply overflow-y-auto; 52 | } 53 | } 54 | 55 | @include desktop { 56 | width: 500px !important; 57 | 58 | & .content { 59 | height: 400px; 60 | @apply rounded-b-lg overflow-y-auto; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /styles/_fonts.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'BentonSans'; 3 | src: url('/fonts/BentonSans/BentonSans-Regular/BentonSans-Regular.woff') 4 | format('woff'), 5 | url('/fonts/BentonSans/BentonSans-Regular/BentonSans-Regular.tff') 6 | format('ttf'), 7 | url('/fonts/BentonSans/BentonSans-Regular/BentonSans-Regular.eot') 8 | format('eot'); 9 | font-weight: normal; 10 | font-style: normal; 11 | font-display: swap; 12 | }; 13 | 14 | @font-face { 15 | font-family: 'BentonSans-Bold'; 16 | src: url('/fonts/BentonSans/BentonSans-Bold/BentonSans-Bold.woff') 17 | format('woff'), 18 | url('/fonts/BentonSans/BentonSans-Bold/BentonSans-Bold.ttf') 19 | format('ttf'), 20 | url('/fonts/BentonSans/BentonSans-Bold/BentonSans-Bold.eot') 21 | format('eot'); 22 | font-weight: bold; 23 | font-style: normal; 24 | font-display: swap; 25 | }; 26 | 27 | @font-face { 28 | font-family: 'BentonSans-Medium'; 29 | src: url('/fonts/BentonSans/BentonSans-Medium/BentonSans-Medium.woff') 30 | format('woff'), 31 | url('/fonts/BentonSans/BentonSans-Medium/BentonSans-Medium.ttf') 32 | format('ttf'), 33 | url('/fonts/BentonSans/BentonSans-Medium/BentonSans-Medium.eot') 34 | format('eot'); 35 | font-weight: normal; 36 | font-style: normal; 37 | font-display: swap; 38 | }; 39 | 40 | @font-face { 41 | font-family: 'BentonSans-RegularItalic'; 42 | src: url('/static/fonts/BentonSans/BentonSans-Italic/BentonSans-RegularItalic.woff2') 43 | format('woff2'), 44 | url('/static/fonts/BentonSans/BentonSans-Italic/BentonSans-RegularItalic.woff') 45 | format('woff'); 46 | font-weight: normal; 47 | font-style: italic; 48 | font-display: swap; 49 | }; 50 | 51 | @font-face { 52 | font-family: 'BentonSans-BoldItalic'; 53 | src: url('/fonts/BentonSans/BentonSans-Italic/BentonSans-BoldItalic.woff2') 54 | format('woff2'), 55 | url('/fonts/BentonSans/BentonSans-Italic/BentonSans-BoldItalic.woff') 56 | format('woff'); 57 | font-weight: bold; 58 | font-style: italic; 59 | font-display: swap; 60 | }; 61 | 62 | @font-face { 63 | font-family: 'BentonSans-MediumItalic'; 64 | src: url('/fonts/BentonSans/BentonSans-Italic/BentonSans-MediumItalic.woff2') 65 | format('woff2'), 66 | url('/fonts/BentonSans/BentonSans-Italic/BentonSans-MediumItalic.woff') 67 | format('woff'); 68 | font-weight: 500; 69 | font-style: italic; 70 | font-display: swap; 71 | }; -------------------------------------------------------------------------------- /styles/_globals.scss: -------------------------------------------------------------------------------- 1 | // globals 2 | a { 3 | @apply text-primaryText transition-all; 4 | 5 | &:hover { 6 | @apply text-primaryTextBold; 7 | } 8 | } 9 | 10 | body { 11 | @apply text-primaryText font-primary; 12 | -webkit-tap-highlight-color: transparent; /* for removing the highlight */ 13 | } 14 | 15 | div:focus { 16 | outline: none !important; 17 | } 18 | 19 | ul { 20 | @apply list-disc; 21 | } 22 | 23 | // Slick carousel globals 24 | .slick-track { 25 | margin-left: inherit !important; 26 | } 27 | 28 | .slick-prev, 29 | .slick-next { 30 | @apply rounded-full shadow transition-all; 31 | z-index: 2; 32 | top: 40% !important; 33 | padding: 10px !important; 34 | height: theme('height.10') !important; 35 | width: theme('width.10') !important; 36 | background: theme('backgroundColor.white') !important; 37 | 38 | &:hover { 39 | @apply shadow-md; 40 | } 41 | 42 | &::before { 43 | @apply opacity-100; 44 | } 45 | 46 | &:focus, &:hover, &:active { 47 | @apply bg-white; 48 | } 49 | } 50 | 51 | .slick-prev { 52 | left: -12px !important; 53 | 54 | @media (min-width: 1280px) and (max-width: 1320px) { 55 | left: 0px !important; 56 | } 57 | } 58 | 59 | .slick-next { 60 | right: -12px !important; 61 | 62 | @media (min-width: 1280px) and (max-width: 1320px) { 63 | right: 0px !important; 64 | } 65 | } 66 | 67 | // Accordion globals 68 | .Collapsible__trigger { 69 | & .collapsible-chevron-icon { 70 | @apply transition-transform; 71 | } 72 | 73 | &.is-open { 74 | & .collapsible-chevron-icon { 75 | @apply transform rotate-180; 76 | } 77 | } 78 | } 79 | 80 | .fade-in { 81 | visibility: visible; 82 | opacity: 1; 83 | transition: opacity 0.5s linear; 84 | } 85 | 86 | .fade-out { 87 | visibility: hidden; 88 | opacity: 0; 89 | transition: visibility 0.5s, opacity 0.5s linear; 90 | } 91 | 92 | .no-padding { 93 | padding: 0 !important; 94 | } 95 | 96 | .no-margin { 97 | margin: 0 !important; 98 | } 99 | 100 | .no-hover-bg { 101 | &:hover { 102 | background: inherit !important; 103 | } 104 | } 105 | 106 | .mobile-view { 107 | display: inherit; 108 | 109 | @include desktop { 110 | display: none; 111 | } 112 | } 113 | 114 | .desktop-view { 115 | display: none; 116 | 117 | @include desktop { 118 | display: inherit; 119 | } 120 | } 121 | 122 | .underline-dashed { 123 | text-decoration: underline dashed; 124 | } 125 | 126 | -------------------------------------------------------------------------------- /styles/_html-body.scss: -------------------------------------------------------------------------------- 1 | // Styles for HTML stored in DB (eg. Terms & Conditions) 2 | 3 | .html-body { 4 | & p { 5 | @apply py-2; 6 | } 7 | 8 | & ul { 9 | @apply list-disc; 10 | @apply ml-5; 11 | } 12 | 13 | & li { 14 | @apply py-2; 15 | } 16 | 17 | & b { 18 | @apply font-medium font-primary-medium text-primaryText; 19 | } 20 | 21 | & a { 22 | @apply underline hover:text-primaryTextBold; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /styles/_input.scss: -------------------------------------------------------------------------------- 1 | 2 | input[type=text], 3 | input[type=password], 4 | input[type=email], 5 | input[type=tel], 6 | input[type=number], 7 | textarea { 8 | @apply text-base w-full rounded-md border border-gallery bg-white outline-none text-mineShaft pt-2 pr-0 pl-3; 9 | transition: border 0.2s ease 0s, color 0.2s ease 0s; 10 | 11 | &:focus { 12 | @apply border-gray700; 13 | } 14 | 15 | &::placeholder { 16 | @apply text-sm lg:text-base; 17 | } 18 | } 19 | 20 | select { 21 | // A reset of styles, including removing the default dropdown arrow 22 | appearance: none; 23 | background-color: transparent; 24 | border: none; 25 | padding: 0 1em 0 0; 26 | margin: 0; 27 | width: 100%; 28 | font-family: inherit; 29 | font-size: inherit; 30 | cursor: inherit; 31 | line-height: inherit; 32 | 33 | // Remove dropdown arrow in IE10 & IE11 34 | // @link https://www.filamentgroup.com/lab/select-css.html 35 | &::-ms-expand { 36 | display: none; 37 | } 38 | 39 | // Remove focus outline, will add on alternate element 40 | outline: none; 41 | } 42 | 43 | // custom select 44 | .select { 45 | display: grid; 46 | grid-template-areas: "select"; 47 | align-items: center; 48 | position: relative; 49 | 50 | select, 51 | &::after { 52 | grid-area: select; 53 | } 54 | 55 | @apply border border-gallery rounded py-[6px] px-2 text-base cursor-pointer; 56 | 57 | // Custom arrow 58 | &:not(.select--multiple)::after { 59 | content: ""; 60 | clip-path: polygon(100% 0%, 0 0%, 50% 100%); 61 | 62 | @apply justify-self-end bg-gray600 w-3 h-2; 63 | } 64 | } 65 | 66 | select[multiple] { 67 | @apply pr-0; 68 | 69 | /* 70 | * Safari will not reveal an option 71 | * unless the select height has room to 72 | * show all of it 73 | * Firefox and Chrome allow showing 74 | * a partial option 75 | */ 76 | 77 | option { 78 | white-space: normal; 79 | cursor: pointer; 80 | 81 | // Only affects Chrome 82 | // outline-color: blue; 83 | } 84 | 85 | /* 86 | * Experimental - styling of selected options 87 | * in the multiselect 88 | * Not supported crossbrowser 89 | */ 90 | // &:not(:disabled) option { 91 | // border-radius: 12px; 92 | // transition: 120ms all ease-in; 93 | 94 | // &:checked { 95 | // background: linear-gradient(hsl(242, 61%, 76%), hsl(242, 61%, 71%)); 96 | // padding-left: 0.5em; 97 | // color: black !important; 98 | // } 99 | // } 100 | } 101 | 102 | .select--disabled { 103 | @apply cursor-not-allowed bg-gray200; 104 | } 105 | 106 | // Interim solution until :focus-within has better support 107 | select:focus + .focus { 108 | @apply absolute top-[-1px] left-[-1px] right-[-1px] bottom-[-1px] border border-gray700 rounded; 109 | } 110 | -------------------------------------------------------------------------------- /styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin hide-scrollbar { 2 | /* 3 | Firefox doesn't have support to hide scrollbar. 4 | Hence, setting scrollbar's width to none. 5 | */ 6 | & { 7 | scrollbar-width: none; 8 | -webkit-overflow-scrolling: touch; 9 | 10 | /* 11 | Hide scrollbar for Chrome, Safari and Opera. 12 | */ 13 | &::-webkit-scrollbar { 14 | display: none; 15 | } 16 | } 17 | } 18 | 19 | @mixin desktop { 20 | @media (min-width: theme('screens.lg')) { 21 | @content; 22 | } 23 | } 24 | 25 | @mixin mobile { 26 | @media (max-width: theme('screens.lg')) { 27 | @content; 28 | } 29 | } 30 | 31 | @mixin tablet { 32 | @media (min-width: theme('screens.md')) { 33 | @content; 34 | } 35 | } 36 | 37 | @mixin lg { 38 | @media (min-width: theme('screens.lg')) { 39 | @content; 40 | } 41 | } 42 | 43 | @mixin xl { 44 | @media (min-width: theme('screens.xl')) { 45 | @content; 46 | } 47 | } 48 | 49 | @mixin landscapeMode { 50 | @media screen and (orientation: landscape) and (min-device-width: 320px) and (max-device-width: 1023px) { 51 | @content; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /styles/_nprogress.scss: -------------------------------------------------------------------------------- 1 | /* Make clicks pass-through */ 2 | #nprogress { 3 | @apply pointer-events-none; 4 | } 5 | 6 | #nprogress .bar { 7 | @apply z-20 fixed top-0 left-0 w-full h-[2px] bg-primary; 8 | } 9 | 10 | /* Fancy blur effect */ 11 | #nprogress .peg { 12 | @apply block absolute right-0 w-24 h-full shadow-lg opacity-100 transform rotate-3 translate-y-0 -translate-x-1; 13 | } 14 | 15 | .nprogress-custom-parent { 16 | @apply hidden relative; 17 | } 18 | 19 | .nprogress-custom-parent #nprogress .bar { 20 | @apply absolute; 21 | } 22 | -------------------------------------------------------------------------------- /styles/styles.scss: -------------------------------------------------------------------------------- 1 | // include taiwind directories 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | // imports 7 | @import 'mixins'; 8 | @import 'fonts'; 9 | @import 'globals'; 10 | @import 'input'; 11 | @import 'loader'; 12 | @import 'nprogress'; 13 | @import 'components'; 14 | @import 'html-body'; 15 | 16 | // User forms 17 | .user-input { 18 | @apply py-2; 19 | 20 | &::placeholder { 21 | @apply text-base; 22 | } 23 | 24 | &-error { 25 | border-color: red !important; 26 | } 27 | 28 | &-group { 29 | @apply mb-5; 30 | } 31 | 32 | &-label { 33 | @apply text-sm text-primaryTextBold font-medium mb-2 font-normal font-primary-medium; 34 | } 35 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "downlevelIteration": true, 17 | "baseUrl": "." 18 | }, 19 | "exclude": ["node_modules"], 20 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] 21 | } 22 | -------------------------------------------------------------------------------- /utils/admin.ts: -------------------------------------------------------------------------------- 1 | import appConfig from '../config/appConfig' 2 | import cookie from 'js-cookie' 3 | 4 | const authTokenKey = `${appConfig.global.app.key}-AUTH-TOKEN` 5 | 6 | export const getAdminAuthToken = (): string | null => { 7 | return localStorage.getItem(authTokenKey) 8 | } 9 | 10 | export const setAdminAuthToken = (authToken: string) => { 11 | localStorage.setItem(authTokenKey, authToken) 12 | } 13 | 14 | export const deleteAdminAuthToken = () => { 15 | localStorage.removeItem(authTokenKey) 16 | } 17 | -------------------------------------------------------------------------------- /utils/applicationContext.ts: -------------------------------------------------------------------------------- 1 | import { IDeviceInfo, DEVICE_PROFILE } from '../components/ApplicationContext' 2 | import { SCREEN_SIZE } from '../constants/constants' 3 | import { matchMinMaxMediaQuery } from './common' 4 | import { isPwa, isTouchDevice } from './deviceDetect' 5 | 6 | export const getDeviceInfo = (): IDeviceInfo => { 7 | const isMobile = window.matchMedia(`(max-width: ${SCREEN_SIZE.LG - 1}px)`).matches 8 | const isDesktop = window.matchMedia(`(min-width: ${SCREEN_SIZE.LG}px)`).matches 9 | 10 | const landscapeMode = window.matchMedia('(orientation: landscape)').matches && isMobile 11 | 12 | let profile: DEVICE_PROFILE = null 13 | 14 | if (matchMinMaxMediaQuery(0, SCREEN_SIZE.SM - 1)) { 15 | profile = 'SM' 16 | } else if (matchMinMaxMediaQuery(SCREEN_SIZE.SM, SCREEN_SIZE.MD - 1)) { 17 | profile = 'MD' 18 | } else if (matchMinMaxMediaQuery(SCREEN_SIZE.MD, SCREEN_SIZE.LG - 1)) { 19 | profile = 'LG' 20 | } else if (matchMinMaxMediaQuery(SCREEN_SIZE.LG, SCREEN_SIZE.XL - 1)) { 21 | profile = 'XL' 22 | } else if (matchMinMaxMediaQuery(SCREEN_SIZE.XL, SCREEN_SIZE['2XL'] - 1)) { 23 | profile = '2XL' 24 | } 25 | 26 | return { 27 | isDesktop: isDesktop, 28 | isMobile: isMobile, 29 | 30 | isTouchDevice: isTouchDevice(), 31 | isLandscapeMode: landscapeMode, 32 | 33 | profile: profile, 34 | 35 | isSm: profile === 'SM', 36 | isMd: profile === 'MD', 37 | isLg: profile === 'LG', 38 | isXl: profile === 'XL', 39 | is2Xl: profile === '2XL', 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /utils/blog.ts: -------------------------------------------------------------------------------- 1 | export const getBlogPageUrl = () => { 2 | return '/blog' 3 | } 4 | 5 | export const getBlogSearchPageUrl = () => { 6 | return '/blog/search' 7 | } 8 | 9 | export const getBlogUpdatePageUrl = (id?: number) => { 10 | return `/blog/update${id ? `?id=${id}` : ''}` 11 | } 12 | -------------------------------------------------------------------------------- /utils/catalogue.ts: -------------------------------------------------------------------------------- 1 | export const getCataloguePageUrl = () => { 2 | return '/catalogue' 3 | } 4 | 5 | export const getCatalogueUpdatePageUrl = (id?: number) => { 6 | return `/catalogue/update${id ? `?id=${id}` : ''}` 7 | } 8 | 9 | export const getCatalogueImagesPageUrl = (id: number) => { 10 | return `/catalogue/images?id=${id}` 11 | } 12 | 13 | export const getCatalogueSearchPageUrl = () => { 14 | return '/catalogue/search' 15 | } 16 | -------------------------------------------------------------------------------- /utils/currency.ts: -------------------------------------------------------------------------------- 1 | export const getCurrencyPageUrl = () => { 2 | return '/currency' 3 | } 4 | 5 | export const getCurrencyUpdatePageUrl = (id?: number) => { 6 | return `/currency/update${id ? `?id=${id}` : ''}` 7 | } 8 | -------------------------------------------------------------------------------- /utils/dates.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs' 2 | 3 | export const getFormattedDateTime = (date: Date): string => { 4 | const dateTime = dayjs(date).format('MMM DD[,] YYYY [at] HH:mm') 5 | return dateTime 6 | } 7 | -------------------------------------------------------------------------------- /utils/deviceDetect.ts: -------------------------------------------------------------------------------- 1 | const device = { 2 | Android() { 3 | return window.navigator.userAgent.match(/Android/i) 4 | }, 5 | BlackBerry() { 6 | return window.navigator.userAgent.match(/BlackBerry/i) 7 | }, 8 | iOS() { 9 | return window.navigator.userAgent.match(/iPhone|iPod|iPad/i) 10 | }, 11 | Chrome() { 12 | return window.navigator.userAgent.match(/Chrome/i) 13 | }, 14 | Firefox() { 15 | return window.navigator.userAgent.match(/Firefox/i) 16 | }, 17 | Safari() { 18 | return window.navigator.userAgent.match(/^((?!chrome|android).)*safari/i) 19 | }, 20 | OperaMini() { 21 | return window.navigator.userAgent.match(/Opera Mini/i) 22 | }, 23 | IE() { 24 | return window.navigator.userAgent.match(/MSIE/i) 25 | }, 26 | Windows() { 27 | return window.navigator.userAgent.match(/IEMobile/i) 28 | }, 29 | TouchDevice() { 30 | // @ts-ignore 31 | return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0 32 | }, 33 | any() { 34 | return !!(device.Android() || device.BlackBerry() || device.iOS() || device.OperaMini() || device.Windows()) 35 | }, 36 | } 37 | 38 | export const isChrome = () => !!device.Chrome() 39 | export const isFirefox = () => !!device.Firefox() 40 | export const isSafari = () => !!device.Safari() 41 | export const isOperaMini = () => !!device.OperaMini() 42 | export const isIE = () => !!device.IE() 43 | export const isPwa = () => window.matchMedia('(display-mode: standalone)').matches 44 | export const isIOSDevice = () => !!device.iOS() 45 | export const isAndroidDevice = () => !!device.Android() 46 | export const isTouchDevice = () => !!device.TouchDevice() 47 | 48 | export const getChromeVersion = () => 49 | isChrome && parseInt(window.navigator.userAgent.match(new RegExp('Chrome/([0-9]+).'))[1], 10) 50 | 51 | export const getFirefoxVersion = () => 52 | isFirefox && parseInt(window.navigator.userAgent.match(new RegExp('Firefox/([0-9]+).'))[1], 10) 53 | -------------------------------------------------------------------------------- /utils/faq.ts: -------------------------------------------------------------------------------- 1 | export const getFAQTopicPageUrl = () => { 2 | return '/faq-topic' 3 | } 4 | 5 | export const getFAQTopicUpdatePageUrl = (id?: number) => { 6 | return `/faq-topic/update${id ? `?id=${id}` : ''}` 7 | } 8 | 9 | export const getFAQPageUrl = (topicId: number) => { 10 | return `/faq-topic/${topicId}` 11 | } 12 | 13 | export const getFAQUpdatePageUrl = (topicId: number, id?: number) => { 14 | return `/faq-topic/${topicId}/update${id ? `?id=${id}` : ''}` 15 | } 16 | -------------------------------------------------------------------------------- /utils/home.ts: -------------------------------------------------------------------------------- 1 | export const getHomePageUrl = () => { 2 | return '/' 3 | } 4 | -------------------------------------------------------------------------------- /utils/image.ts: -------------------------------------------------------------------------------- 1 | import { ImageSourceType } from '../components/core/CoreImage' 2 | import appConfig from '../config/appConfig' 3 | 4 | export const prepareImageUrl = (urlPath: string, source: ImageSourceType): string => { 5 | if (source === ImageSourceType.CLOUD) { 6 | return `${appConfig.global.imageBaseUrl}${urlPath || ''}` 7 | } 8 | if (source === ImageSourceType.ASSET) { 9 | return urlPath 10 | } 11 | return urlPath 12 | } 13 | 14 | export const getImagePageUrl = () => { 15 | return '/image' 16 | } 17 | 18 | export const getImageUpdatePageUrl = (id?: number) => { 19 | return `/image/update${id ? `?id=${id}` : ''}` 20 | } 21 | 22 | export const getImageSearchPageUrl = () => { 23 | return '/image/search' 24 | } 25 | -------------------------------------------------------------------------------- /utils/layout.ts: -------------------------------------------------------------------------------- 1 | import { NextRouter } from 'next/router' 2 | import { IApplicationContextProps } from '../components/ApplicationContext' 3 | import { toastSuccess } from '../components/Toaster' 4 | import appConfig from '../config/appConfig' 5 | 6 | export const onUpdateSuccess = ( 7 | params: { url?: string }, 8 | applicationContext: IApplicationContextProps, 9 | router: NextRouter 10 | ) => { 11 | toastSuccess('Updated successfully!') 12 | if (appConfig.global.redirectToIndexViewAfterUpdate && params.url) { 13 | router.push(params.url) 14 | } 15 | } 16 | 17 | export const onDeleteSuccess = ( 18 | params: { url?: string }, 19 | applicationContext: IApplicationContextProps, 20 | router: NextRouter 21 | ) => { 22 | toastSuccess('Deleted successfully!') 23 | if (appConfig.global.redirectToIndexViewAfterDelete && params.url) { 24 | router.push(params.url) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /utils/login.ts: -------------------------------------------------------------------------------- 1 | export const getLoginPageUrl = () => { 2 | return '/login' 3 | } 4 | -------------------------------------------------------------------------------- /utils/order.ts: -------------------------------------------------------------------------------- 1 | export const getRecentOrdersPageUrl = () => { 2 | return '/recent-orders' 3 | } 4 | 5 | export const getOrderPageUrl = (id: number) => { 6 | return `/order?id=${id}` 7 | } 8 | 9 | export const getOrderUpdatePageUrl = (id: number) => { 10 | return `/order/update?id=${id}` 11 | } 12 | 13 | export const getOrderStatusPageUrl = () => { 14 | return '/order-status-type' 15 | } 16 | 17 | export const getOrderStatusUpdatePageUrl = (id?: number) => { 18 | return `/order-status-type/update${id ? `?id=${id}` : ''}` 19 | } 20 | -------------------------------------------------------------------------------- /utils/privacyPolicy.ts: -------------------------------------------------------------------------------- 1 | export const getPrivacyPolicyPageUrl = () => { 2 | return '/privacy-policy' 3 | } 4 | 5 | export const getPrivacyPolicyUpdatePageUrl = () => { 6 | return '/privacy-policy/update' 7 | } 8 | -------------------------------------------------------------------------------- /utils/refundPolicy.ts: -------------------------------------------------------------------------------- 1 | export const getRefundPolicyPageUrl = () => { 2 | return '/refund-policy' 3 | } 4 | 5 | export const getRefundPolicyUpdatePageUrl = () => { 6 | return '/refund-policy/update' 7 | } 8 | -------------------------------------------------------------------------------- /utils/securityQuestion.ts: -------------------------------------------------------------------------------- 1 | export const getSecurityQuestionPageUrl = () => { 2 | return '/security-question' 3 | } 4 | 5 | export const getSecurityQuestionUpdatePageUrl = (id?: number) => { 6 | return `/security-question/update${id ? `?id=${id}` : ''}` 7 | } 8 | -------------------------------------------------------------------------------- /utils/shippingPolicy.ts: -------------------------------------------------------------------------------- 1 | export const getShippingPolicyPageUrl = () => { 2 | return '/shipping-policy' 3 | } 4 | 5 | export const getShippingPolicyUpdatePageUrl = () => { 6 | return '/shipping-policy/update' 7 | } 8 | -------------------------------------------------------------------------------- /utils/supportedRegions.ts: -------------------------------------------------------------------------------- 1 | import { SupportedRegionType } from '../contract/constants' 2 | 3 | export const getSupportedRegionsPageUrl = () => { 4 | return '/supported-regions' 5 | } 6 | export const getSupportedRegionsUpdatePageUrl = (supportedRegionType: SupportedRegionType, id?: number) => { 7 | return `/supported-regions/${supportedRegionType}/update${id ? `?id=${id}` : ''}` 8 | } 9 | -------------------------------------------------------------------------------- /utils/tnc.ts: -------------------------------------------------------------------------------- 1 | export const getTnCPageUrl = () => { 2 | return '/terms-conditions' 3 | } 4 | 5 | export const getTnCUpdatePageUrl = () => { 6 | return '/terms-conditions/update' 7 | } 8 | -------------------------------------------------------------------------------- /utils/user.ts: -------------------------------------------------------------------------------- 1 | export const getUserPageUrl = () => { 2 | return '/user' 3 | } 4 | 5 | export const getUserSearchPageUrl = () => { 6 | return '/user/search' 7 | } 8 | 9 | export const getUserDetailPageUrl = (id: number) => { 10 | return `/user/detail?id=${id}` 11 | } 12 | 13 | export const getUserAddressPageUrl = (id: number) => { 14 | return `/user/address?id=${id}` 15 | } 16 | 17 | export const getUserWishlistPageUrl = (id: number) => { 18 | return `/user/wishlist?id=${id}` 19 | } 20 | 21 | export const getUserCartPageUrl = (id: number) => { 22 | return `/user/cart?id=${id}` 23 | } 24 | 25 | export const getUserOrderPageUrl = (id: number) => { 26 | return `/user/order?id=${id}` 27 | } 28 | 29 | export const getUserLoginHistoryPageUrl = (id: number) => { 30 | return `/user/login-history?id=${id}` 31 | } 32 | --------------------------------------------------------------------------------