├── .env ├── .gitignore ├── README.md ├── codegen.ts ├── index.html ├── package.json ├── postcss.config.js ├── public ├── error.log ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── __generated__ │ ├── fragment-masking.ts │ ├── gql.ts │ ├── graphql.ts │ └── index.ts ├── api │ ├── api │ │ └── account │ │ │ └── info.ts │ ├── config │ │ ├── fetch.config.ts │ │ └── route.config.ts │ ├── exception │ │ ├── index.ts │ │ └── validation │ │ │ ├── validation.exception.factory.ts │ │ │ └── validation.exception.ts │ ├── graphql │ │ └── terminals.ts │ ├── hooks │ │ ├── use.api.ts │ │ ├── use.load.data.ts │ │ ├── use.load.item.ts │ │ └── use.load.list.ts │ ├── model │ │ ├── barcode.ts │ │ ├── brand.ts │ │ ├── cart.item.ts │ │ ├── category.ts │ │ ├── closing.ts │ │ ├── common.ts │ │ ├── coupn.ts │ │ ├── customer.payment.ts │ │ ├── customer.ts │ │ ├── department.ts │ │ ├── device.ts │ │ ├── discount.ts │ │ ├── expense.ts │ │ ├── hydra.ts │ │ ├── media.ts │ │ ├── order.discount.ts │ │ ├── order.item.ts │ │ ├── order.payment.ts │ │ ├── order.tax.ts │ │ ├── order.ts │ │ ├── payment.type.ts │ │ ├── product.price.ts │ │ ├── product.store.ts │ │ ├── product.ts │ │ ├── product.variant.ts │ │ ├── purchase.item.ts │ │ ├── purchase.order.item.ts │ │ ├── purchase.order.ts │ │ ├── purchase.ts │ │ ├── setting.ts │ │ ├── store.ts │ │ ├── supplier.payment.ts │ │ ├── supplier.ts │ │ ├── tax.ts │ │ ├── terminal.ts │ │ ├── user.ts │ │ └── validation │ │ │ └── index.ts │ ├── request │ │ ├── header.ts │ │ └── request.ts │ └── routing │ │ ├── routes │ │ ├── admin.backend.app.ts │ │ └── backend.app.ts │ │ └── url.ts ├── app-admin │ ├── app.tsx │ ├── containers │ │ ├── dashboard │ │ │ ├── dashboard.tsx │ │ │ ├── profile │ │ │ │ └── profile.tsx │ │ │ └── users │ │ │ │ └── index.tsx │ │ ├── forgot │ │ │ └── forgot.tsx │ │ ├── layout │ │ │ ├── dashboard.layout.tsx │ │ │ ├── footer.tsx │ │ │ ├── layout.tsx │ │ │ ├── navbar.tsx │ │ │ └── sidebar.tsx │ │ └── login │ │ │ └── login.tsx │ └── routes │ │ └── frontend.routes.ts ├── app-common │ └── components │ │ ├── confirm │ │ ├── confirm.alert.tsx │ │ └── notification.ts │ │ ├── container │ │ └── trap.focus.tsx │ │ ├── dynamic.value │ │ └── dynamic.value.tsx │ │ ├── error │ │ └── 404.tsx │ │ ├── ga │ │ └── ga4.tsx │ │ ├── input │ │ ├── button.tsx │ │ ├── checkbox.intermediate.tsx │ │ ├── checkbox.tsx │ │ ├── custom.react.select.tsx │ │ ├── input.tsx │ │ ├── keyboard.input.tsx │ │ ├── keyboard.tsx │ │ ├── react.keyboard.tsx │ │ ├── shortcut.tsx │ │ ├── stores.tsx │ │ ├── switch.tsx │ │ └── textarea.tsx │ │ ├── loader │ │ └── loader.tsx │ │ ├── modal │ │ └── modal.tsx │ │ ├── react-aria │ │ └── dropdown.menu.tsx │ │ ├── table │ │ ├── keyboard.table.tsx │ │ └── table.tsx │ │ └── tabs │ │ └── tabs.tsx ├── app-frontend │ ├── app.tsx │ ├── components │ │ ├── cart │ │ │ ├── cart.container.tsx │ │ │ ├── cart.controls.tsx │ │ │ ├── cart.item.tsx │ │ │ └── order.totals.tsx │ │ ├── confirm.box.tsx │ │ ├── customers │ │ │ ├── customer.payments.tsx │ │ │ └── customers.tsx │ │ ├── inventory │ │ │ ├── purchase-orders │ │ │ │ ├── create.purchase.order.tsx │ │ │ │ └── purchase.orders.tsx │ │ │ ├── purchase.tabs.tsx │ │ │ ├── purchase │ │ │ │ ├── create.purchase.tsx │ │ │ │ ├── purchases.tsx │ │ │ │ └── view.purchase.tsx │ │ │ └── supplier │ │ │ │ ├── create.supplier.tsx │ │ │ │ ├── supplier.ledger.tsx │ │ │ │ └── suppliers.tsx │ │ ├── logout.tsx │ │ ├── modes │ │ │ ├── footer.tsx │ │ │ ├── payment.tsx │ │ │ ├── pos.tsx │ │ │ └── topbar.right.tsx │ │ ├── print.tsx │ │ ├── sale │ │ │ ├── apply.discount.tsx │ │ │ ├── apply.tax.tsx │ │ │ ├── clear.sale.tsx │ │ │ ├── expenses.tsx │ │ │ ├── sale.closing.tsx │ │ │ ├── sale.find.tsx │ │ │ ├── sale.history.tsx │ │ │ ├── sale.inline.tsx │ │ │ ├── sale.print.tsx │ │ │ ├── sale.tsx │ │ │ └── view.order.tsx │ │ ├── search │ │ │ ├── sale.brands.tsx │ │ │ ├── sale.categories.tsx │ │ │ ├── sale.departments.tsx │ │ │ ├── search.table.tsx │ │ │ ├── search.variants.tsx │ │ │ └── speech.search.tsx │ │ ├── settings │ │ │ ├── brands │ │ │ │ ├── brands.tsx │ │ │ │ └── create.brand.tsx │ │ │ ├── categories │ │ │ │ ├── categories.tsx │ │ │ │ └── create.category.tsx │ │ │ ├── departments │ │ │ │ ├── create.department.tsx │ │ │ │ └── departments.tsx │ │ │ ├── discounts │ │ │ │ ├── create.discount.tsx │ │ │ │ └── discount.types.tsx │ │ │ ├── dynamic-barcodes │ │ │ │ └── index.tsx │ │ │ ├── items │ │ │ │ ├── export.items.tsx │ │ │ │ ├── import.items.tsx │ │ │ │ ├── item.tsx │ │ │ │ ├── items.tsx │ │ │ │ ├── manage-item │ │ │ │ │ └── items.create.tsx │ │ │ │ └── products │ │ │ │ │ ├── create.variants.tsx │ │ │ │ │ ├── variant.group.tsx │ │ │ │ │ └── variants.tsx │ │ │ ├── more.tsx │ │ │ ├── payment-types │ │ │ │ ├── create.payment.type.tsx │ │ │ │ └── payment.types.tsx │ │ │ ├── stores │ │ │ │ ├── create.store.tsx │ │ │ │ └── stores.tsx │ │ │ ├── taxes │ │ │ │ ├── create.tax.tsx │ │ │ │ └── tax.types.tsx │ │ │ ├── terminals │ │ │ │ ├── create.terminal.tsx │ │ │ │ └── terminals.tsx │ │ │ └── users │ │ │ │ ├── create.user.tsx │ │ │ │ └── users.tsx │ │ └── shortcuts.tsx │ ├── containers │ │ ├── dashboard │ │ │ ├── dashboard.tsx │ │ │ └── pos.tsx │ │ ├── forgot │ │ │ ├── forgot.tsx │ │ │ └── reset.tsx │ │ ├── layout │ │ │ ├── dashboard.layout.tsx │ │ │ └── layout.tsx │ │ └── login │ │ │ └── login.tsx │ └── routes │ │ └── frontend.routes.ts ├── assets │ ├── fonts │ │ ├── Comfortaa-Regular.ttf │ │ ├── Manrope-Medium.ttf │ │ ├── MonaspaceRadon-Medium.woff │ │ └── digital-7.ttf │ └── images │ │ ├── 1120171.jpg │ │ ├── background.svg │ │ ├── bars-solid.svg │ │ ├── close.svg │ │ ├── delete.svg │ │ ├── download.png │ │ ├── ec3mqt9a91j81.jpg │ │ ├── login.jpg │ │ ├── minus.svg │ │ ├── not-found.svg │ │ ├── plus.svg │ │ ├── spinner.svg │ │ └── tachometer-alt-solid.svg ├── css │ ├── index.scss │ └── ltr.scss ├── duck │ ├── _root │ │ ├── root.reducer.ts │ │ ├── root.saga.ts │ │ └── root.state.ts │ ├── app │ │ ├── app.action.ts │ │ ├── app.reducer.ts │ │ ├── app.saga.ts │ │ ├── app.selector.ts │ │ └── app.state.ts │ ├── auth │ │ ├── auth.action.ts │ │ ├── auth.reducer.ts │ │ ├── auth.saga.ts │ │ ├── auth.selector.ts │ │ ├── auth.state.ts │ │ ├── hooks │ │ │ └── useLogout.ts │ │ └── model │ │ │ └── authorized.user.ts │ ├── entity │ │ ├── entity.action.ts │ │ ├── entity.middleware.ts │ │ ├── entity.model.ts │ │ ├── entity.reducer.ts │ │ ├── entity.schema.ts │ │ ├── entity.state.ts │ │ └── selector │ │ │ ├── _entity.ts │ │ │ └── user.account.ts │ ├── progress │ │ ├── progress.action.ts │ │ ├── progress.reducer.ts │ │ ├── progress.selector.ts │ │ └── progress.state.ts │ ├── shortcuts │ │ ├── shortcut.action.ts │ │ ├── shortcut.reducer.ts │ │ ├── shortcut.selector.ts │ │ └── shortcut.state.ts │ ├── store │ │ ├── store.action.ts │ │ ├── store.reducer.ts │ │ ├── store.selector.ts │ │ └── store.state.ts │ ├── terminal │ │ ├── terminal.action.ts │ │ ├── terminal.reducer.ts │ │ ├── terminal.selector.ts │ │ └── terminal.state.ts │ └── touch │ │ ├── touch.action.ts │ │ ├── touch.reducer.ts │ │ ├── touch.selector.ts │ │ └── touch.state.ts ├── ga.js ├── i18next.ts ├── index.tsx ├── language │ └── lang.en.json ├── lib │ ├── .gitignore │ ├── currency │ │ └── currency.ts │ ├── error │ │ └── error.tsx │ ├── http │ │ ├── exception │ │ │ ├── error.exception.ts │ │ │ ├── http.exception.factory.ts │ │ │ └── http.exception.ts │ │ ├── header │ │ │ ├── compose.ts │ │ │ └── header.ts │ │ ├── mime │ │ │ └── mime.ts │ │ └── request.ts │ ├── localforage │ │ └── localforage.ts │ ├── location │ │ └── query.string.ts │ ├── uuid │ │ └── v4.ts │ └── validator │ │ └── validation.result.ts ├── logo.svg ├── react-app-env.d.ts ├── reportWebVitals.js ├── setupTests.js ├── store │ ├── jotai.ts │ └── store.factory.ts └── types.d.ts ├── tailwind.config.js ├── tsconfig.json ├── vite.config.js └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | VITE_WEBSITE_NAME=POS 2 | VITE_API_HOST=https://api.pos.ezportal.online 3 | VITE_API_BASE_URL=$VITE_API_HOST/api 4 | VITE_BASE_URL= 5 | 6 | VITE_CURRENCY=USD 7 | VITE_LOCALE=en-US 8 | VITE_DECIMAL_PLACES=1 9 | #valid options are "frontend", "admin" 10 | VITE_APP_TYPE=frontend 11 | 12 | VITE_DATE_FORMAT="dd-MM-yyyy" 13 | VITE_TIME_FORMAT="hh:mm a" 14 | VITE_DATE_TIME_FORMAT="$VITE_DATE_FORMAT $VITE_TIME_FORMAT" 15 | VITE_DATE_TIME_HUMAN_FORMAT="ff" 16 | VITE_GOOGLE_ANALYTICS= 17 | -------------------------------------------------------------------------------- /.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .idea 26 | dist 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Point of sale system 2 | 3 | ### Demo 4 | [https://pos.ezportal.online](https://pos.ezportal.online) 5 | 6 | Username: admin 7 | 8 | Password: admin 9 | 10 | A point of sale system built using React.js and [Symfony as a backend](https://github.com/ahmedali5530/pos). 11 | ### Features 12 | 13 | - Order management 14 | - Inventory 15 | - Expenses 16 | - Multiple stores with multiple terminals 17 | - Can support variants. i.e. sizes, colors, anything etc... 18 | - Supports shortcuts for faster operations 19 | - Day closing 20 | - Supports multiple taxes 21 | - Supports multiple discounts 22 | - Customers management 23 | - Suppliers management 24 | - Supports Refunds 25 | 26 | ## Other projects 27 | If you are interested in for restaurant based software, [Checkout my other](https://github.com/ahmedali5530/react-posr) software for demo. 28 | 29 | ## Requirements 30 | - NodeJs >= 14 31 | - Any text editor for updating configuration files 32 | ## Installation 33 | - Download or clone this project 34 | - run `yarn install` to install all third party libraries 35 | - After setting up and running [symfony](https://github.com/ahmedali5530/pos) instance open `.env` file and add your `VITE_API_HOST` variable according to your symfony installation. 36 | - Then run following Available scripts to run application. 37 | - Use `VITE_CURRENCY` and `VITE_LOCALE` variable to setup your currency settings. 38 | - Use `VITE_DATE_FORMAT` and `VITE_TIME_FORMAT` for time formats, I am using [luxon](https://moment.github.io/luxon/#/formatting) for date and time formatting. 39 | 40 | ## Available Scripts 41 | 42 | In the project directory, you can run: 43 | 44 | ### `yarn start` 45 | 46 | Runs the app in the development mode.\ 47 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 48 | 49 | The page will reload if you make edits.\ 50 | You will also see any lint errors in the console. 51 | 52 | ### `yarn test` 53 | 54 | Launches the test runner in the interactive watch mode.\ 55 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 56 | 57 | ### `yarn build` 58 | 59 | Builds the app for production to the `build` folder.\ 60 | It correctly bundles React in production mode and optimizes the build for the best performance. 61 | 62 | The build is minified and the filenames include the hashes.\ 63 | Your app is ready to be deployed! 64 | 65 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 66 | 67 | ### `yarn eject` 68 | 69 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 70 | 71 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 72 | 73 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 74 | 75 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 76 | 77 | ## Learn More 78 | 79 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 80 | 81 | To learn React, check out the [React documentation](https://reactjs.org/). 82 | -------------------------------------------------------------------------------- /codegen.ts: -------------------------------------------------------------------------------- 1 | import { CodegenConfig } from '@graphql-codegen/cli'; 2 | 3 | const config: CodegenConfig = { 4 | schema: 'http://polymer-admin.localhost/api/graphql', 5 | // this assumes that all your source files are in a top-level `src/` directory - you might need to adjust this to your file structure 6 | documents: ['src/**/*.{ts,tsx}'], 7 | generates: { 8 | './src/__generated__/': { 9 | preset: 'client', 10 | plugins: [], 11 | presetConfig: { 12 | gqlTagName: 'gql', 13 | } 14 | } 15 | }, 16 | ignoreNoDocuments: true, 17 | }; 18 | 19 | export default config; 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | POS System 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pos-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@apollo/client": "^3.8.4", 7 | "@fortawesome/fontawesome-svg-core": "^6.1.2", 8 | "@fortawesome/free-regular-svg-icons": "^6.1.2", 9 | "@fortawesome/free-solid-svg-icons": "^6.1.2", 10 | "@fortawesome/react-fontawesome": "^0.2.0", 11 | "@hookform/resolvers": "^3.1.0", 12 | "@nivo/bar": "^0.79.1", 13 | "@nivo/core": "^0.79.0", 14 | "@nivo/pie": "^0.79.1", 15 | "@tanstack/react-query": "^4.29.7", 16 | "@tanstack/react-query-devtools": "^4.29.7", 17 | "@tanstack/react-table": "^8.9.2", 18 | "antd": "^5.4.7", 19 | "classnames": "^2.3.1", 20 | "dotenv": "^16.3.1", 21 | "fuse.js": "^6.6.2", 22 | "graphql": "^16.8.1", 23 | "i18next": "^21.8.16", 24 | "jotai": "^2.4.3", 25 | "js-cookie": "^3.0.1", 26 | "localforage": "^1.10.0", 27 | "lodash": "^4.17.21", 28 | "luxon": "^3.2.1", 29 | "nanoid": "^4.0.0", 30 | "normalizr": "^3.6.2", 31 | "qs": "^6.11.0", 32 | "react": "^18.2.0", 33 | "react-aria": "^3.31.1", 34 | "react-aria-components": "^1.0.1", 35 | "react-dom": "^18.2.0", 36 | "react-ga4": "^2.1.0", 37 | "react-highlight-words": "^0.18.0", 38 | "react-hook-form": "^7.34.0", 39 | "react-i18next": "^11.18.3", 40 | "react-indiana-drag-scroll": "^2.2.0", 41 | "react-modal": "^3.15.1", 42 | "react-mousetrap": "^0.2.0", 43 | "react-number-format": "^5.2.2", 44 | "react-perfect-scrollbar": "^1.5.8", 45 | "react-popper": "^2.3.0", 46 | "react-query": "^3.39.3", 47 | "react-redux": "^8.0.2", 48 | "react-responsive": "^9.0.2", 49 | "react-router": "^6.3.0", 50 | "react-router-dom": "^6.3.0", 51 | "react-select": "^5.8.0", 52 | "react-simple-keyboard": "^3.4.217", 53 | "react-speech-recognition": "^3.9.1", 54 | "react-table": "^7.8.0", 55 | "react-window": "^1.8.7", 56 | "redux": "^4.2.0", 57 | "redux-actions": "^2.6.5", 58 | "redux-devtools-extension": "^2.13.9", 59 | "redux-saga": "^1.1.3", 60 | "regenerator-runtime": "^0.13.9", 61 | "reselect": "^4.1.6", 62 | "sass": "^1.54.5", 63 | "tailwindcss-react-aria-components": "^1.1.0", 64 | "typescript": "^4.4.2", 65 | "vite-plugin-env-compatible": "^1.1.1", 66 | "vite-plugin-svgr": "^3.2.0", 67 | "web-vitals": "^2.1.0", 68 | "yup": "^1.1.1" 69 | }, 70 | "scripts": { 71 | "start": "vite", 72 | "build": "vite build", 73 | "compile": "graphql-codegen" 74 | }, 75 | "eslintConfig": { 76 | "extends": [ 77 | "react-app", 78 | "react-app/jest" 79 | ] 80 | }, 81 | "browserslist": { 82 | "production": [ 83 | ">0.2%", 84 | "not dead", 85 | "not op_mini all" 86 | ], 87 | "development": [ 88 | "last 1 chrome version", 89 | "last 1 firefox version", 90 | "last 1 safari version" 91 | ] 92 | }, 93 | "devDependencies": { 94 | "@graphql-codegen/cli": "^5.0.0", 95 | "@graphql-codegen/client-preset": "^4.1.0", 96 | "@graphql-typed-document-node/core": "^3.2.0", 97 | "@testing-library/jest-dom": "^5.14.1", 98 | "@testing-library/react": "^13.0.0", 99 | "@testing-library/user-event": "^13.2.1", 100 | "@types/dom-speech-recognition": "^0.0.1", 101 | "@types/jest": "^27.0.1", 102 | "@types/js-cookie": "^3.0.2", 103 | "@types/lodash": "^4.14.182", 104 | "@types/luxon": "^3.0.1", 105 | "@types/node": "^16.7.13", 106 | "@types/qs": "^6.9.7", 107 | "@types/react": "^18.0.0", 108 | "@types/react-datepicker": "^4.4.2", 109 | "@types/react-dom": "^18.0.0", 110 | "@types/react-highlight-words": "^0.16.4", 111 | "@types/react-modal": "^3.13.1", 112 | "@types/react-resizable": "^3.0.4", 113 | "@types/react-select": "^5.0.1", 114 | "@types/react-speech-recognition": "^3.9.0", 115 | "@types/react-table": "^7.7.14", 116 | "@types/react-window": "^1.8.5", 117 | "@types/redux-actions": "^2.6.2", 118 | "@vitejs/plugin-react": "^2.0.1", 119 | "autoprefixer": "^10.4.8", 120 | "postcss": "^8.4.31", 121 | "tailwindcss": "^3.1.8", 122 | "vite": "^4.4.2" 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedali5530/pos-frontend/134ec2624d6135e65c82a92a2029c4deb850cb5f/public/error.log -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedali5530/pos-frontend/134ec2624d6135e65c82a92a2029c4deb850cb5f/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedali5530/pos-frontend/134ec2624d6135e65c82a92a2029c4deb850cb5f/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedali5530/pos-frontend/134ec2624d6135e65c82a92a2029c4deb850cb5f/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/__generated__/fragment-masking.ts: -------------------------------------------------------------------------------- 1 | import { ResultOf, DocumentTypeDecoration, TypedDocumentNode } from '@graphql-typed-document-node/core'; 2 | import { FragmentDefinitionNode } from 'graphql'; 3 | import { Incremental } from './graphql'; 4 | 5 | 6 | export type FragmentType> = TDocumentType extends DocumentTypeDecoration< 7 | infer TType, 8 | any 9 | > 10 | ? [TType] extends [{ ' $fragmentName'?: infer TKey }] 11 | ? TKey extends string 12 | ? { ' $fragmentRefs'?: { [key in TKey]: TType } } 13 | : never 14 | : never 15 | : never; 16 | 17 | // return non-nullable if `fragmentType` is non-nullable 18 | export function useFragment( 19 | _documentNode: DocumentTypeDecoration, 20 | fragmentType: FragmentType> 21 | ): TType; 22 | // return nullable if `fragmentType` is nullable 23 | export function useFragment( 24 | _documentNode: DocumentTypeDecoration, 25 | fragmentType: FragmentType> | null | undefined 26 | ): TType | null | undefined; 27 | // return array of non-nullable if `fragmentType` is array of non-nullable 28 | export function useFragment( 29 | _documentNode: DocumentTypeDecoration, 30 | fragmentType: ReadonlyArray>> 31 | ): ReadonlyArray; 32 | // return array of nullable if `fragmentType` is array of nullable 33 | export function useFragment( 34 | _documentNode: DocumentTypeDecoration, 35 | fragmentType: ReadonlyArray>> | null | undefined 36 | ): ReadonlyArray | null | undefined; 37 | export function useFragment( 38 | _documentNode: DocumentTypeDecoration, 39 | fragmentType: FragmentType> | ReadonlyArray>> | null | undefined 40 | ): TType | ReadonlyArray | null | undefined { 41 | return fragmentType as any; 42 | } 43 | 44 | 45 | export function makeFragmentData< 46 | F extends DocumentTypeDecoration, 47 | FT extends ResultOf 48 | >(data: FT, _fragment: F): FragmentType { 49 | return data as FragmentType; 50 | } 51 | export function isFragmentReady( 52 | queryNode: DocumentTypeDecoration, 53 | fragmentNode: TypedDocumentNode, 54 | data: FragmentType, any>> | null | undefined 55 | ): data is FragmentType { 56 | const deferredFields = (queryNode as { __meta__?: { deferredFields: Record } }).__meta__ 57 | ?.deferredFields; 58 | 59 | if (!deferredFields) return true; 60 | 61 | const fragDef = fragmentNode.definitions[0] as FragmentDefinitionNode | undefined; 62 | const fragName = fragDef?.name?.value; 63 | 64 | const fields = (fragName && deferredFields[fragName]) || []; 65 | return fields.length > 0 && fields.every(field => data && field in data); 66 | } 67 | -------------------------------------------------------------------------------- /src/__generated__/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./fragment-masking"; 2 | export * from "./gql"; -------------------------------------------------------------------------------- /src/api/api/account/info.ts: -------------------------------------------------------------------------------- 1 | import {jsonRequest} from '../../request/request'; 2 | import {User} from "../../model/user"; 3 | import {AUTH_INFO} from '../../routing/routes/backend.app'; 4 | import {HttpException, UnauthorizedException} from '../../exception'; 5 | 6 | export interface AuthInfoResponse { 7 | code: number; 8 | message?: string; 9 | user: User 10 | } 11 | 12 | export class UserNotAuthorizedException { 13 | constructor(public readonly httpException: HttpException) { 14 | } 15 | } 16 | 17 | /** 18 | * @throws UserNotAuthorizedException 19 | */ 20 | export async function getAuthInfo(role?: string): Promise { 21 | let url = AUTH_INFO; 22 | 23 | if (role) { 24 | url += '?role=' + role; 25 | } else { 26 | url += '?role=' + (import.meta.env.VITE_APP_TYPE === 'frontend' ? 'ROLE_USER' : 'ROLE_ADMIN') 27 | } 28 | 29 | let response: Response; 30 | 31 | try { 32 | response = await jsonRequest(url); 33 | } catch (exception) { 34 | if (exception instanceof UnauthorizedException) { 35 | throw new UserNotAuthorizedException(exception); 36 | } 37 | 38 | throw exception; 39 | } 40 | 41 | return response.json(); 42 | } 43 | -------------------------------------------------------------------------------- /src/api/config/fetch.config.ts: -------------------------------------------------------------------------------- 1 | export const fetchConfig: RequestInit = { 2 | credentials: 'include', 3 | mode: 'cors' 4 | }; -------------------------------------------------------------------------------- /src/api/config/route.config.ts: -------------------------------------------------------------------------------- 1 | export const routeConfig = { 2 | baseUrl: import.meta.env.VITE_API_BASE_URL 3 | }; 4 | -------------------------------------------------------------------------------- /src/api/exception/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../../lib/http/exception/http.exception'; -------------------------------------------------------------------------------- /src/api/exception/validation/validation.exception.factory.ts: -------------------------------------------------------------------------------- 1 | import { ValidationResult } from '../../model/validation'; 2 | import { ValidationException } from './validation.exception'; 3 | import { UnprocessableEntityException } from '../'; 4 | 5 | export class ValidationExceptionFactory { 6 | 7 | static createFromValidationResult(validationResult: ValidationResult) { 8 | return ValidationException.createFromValidationResult(validationResult); 9 | } 10 | 11 | static async createFromUnprocessableEntityException(exception: UnprocessableEntityException) { 12 | const validationResult = await exception.response.json() as ValidationResult; 13 | 14 | return ValidationExceptionFactory.createFromValidationResult(validationResult); 15 | } 16 | } -------------------------------------------------------------------------------- /src/api/exception/validation/validation.exception.ts: -------------------------------------------------------------------------------- 1 | import { ConstraintViolation, ValidationResult } from '../../model/validation'; 2 | 3 | export class ValidationException { 4 | 5 | violations: ConstraintViolation[] = []; 6 | errorMessage?: string; 7 | 8 | static createFromValidationResult(validationResult: ValidationResult) { 9 | const object = new ValidationException(); 10 | 11 | object.violations = validationResult.violations || []; 12 | object.errorMessage = validationResult.errorMessage; 13 | 14 | return object; 15 | } 16 | 17 | static createFromErrorMessage(errorMessage: string) { 18 | const object = new ValidationException(); 19 | 20 | object.errorMessage = errorMessage; 21 | 22 | return object; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/api/graphql/terminals.ts: -------------------------------------------------------------------------------- 1 | import { gql } from "../../__generated__"; 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/api/hooks/use.api.ts: -------------------------------------------------------------------------------- 1 | import {useState} from 'react'; 2 | import {useQuery, useQueryClient, UseQueryOptions, UseQueryResult} from '@tanstack/react-query'; 3 | import {jsonRequest} from "../request/request"; 4 | import {QueryString} from "../../lib/location/query.string"; 5 | import {notify} from "../../app-common/components/confirm/notification"; 6 | 7 | type Filters = Record; 8 | type Sort = string; 9 | type SortMode = 'asc' | 'desc'; 10 | 11 | export interface UseApiResult { 12 | data: T | undefined; 13 | isLoading: boolean; 14 | isFetching: boolean; 15 | isError: boolean; 16 | error: unknown; 17 | filters: Filters; 18 | sort: Sort; 19 | sortMode: SortMode, 20 | page: number; 21 | pageSize: number; 22 | handleFilterChange: (newFilters: Filters) => void; 23 | handleSortChange: (newSort: Sort) => void; 24 | handleSortModeChange: (newSortMode: SortMode) => void; 25 | handlePageChange: (newPage: number) => void; 26 | handlePageSizeChange: (newPageSize: number) => void; 27 | fetchData: () => void; 28 | fetch: () => void; 29 | resetFilters: () => void; 30 | } 31 | 32 | function useApi( 33 | key: string, 34 | url: string, 35 | initialFilters: Filters = {}, 36 | initialSort: Sort = '', 37 | initialSortMode: SortMode = 'asc', 38 | initialPage: number = 1, 39 | initialPageSize: number = 10, 40 | fetchOptions?: any, 41 | useApiOptions?: any 42 | ): UseApiResult { 43 | const [filters, setFilters] = useState(initialFilters); 44 | const [sort, setSort] = useState(initialSort); 45 | const [sortMode, setSortMode] = useState(initialSortMode); 46 | const [page, setPage] = useState(initialPage); 47 | const [pageSize, setPageSize] = useState(initialPageSize); 48 | 49 | const queryClient = useQueryClient(); 50 | 51 | const fetchFilteredData = async (): Promise => { 52 | let query: any = { 53 | ...filters, 54 | page: page, 55 | itemsPerPage: pageSize 56 | }; 57 | 58 | const newUrl = new URL(url); 59 | for(const item in query){ 60 | newUrl.searchParams.append(item, query[item]); 61 | } 62 | 63 | if (sort) { 64 | newUrl.searchParams.append(`order[${sort}]`, sortMode); 65 | } 66 | 67 | const response = await jsonRequest(newUrl.toString(), { 68 | method: 'GET', 69 | ...fetchOptions 70 | }); 71 | 72 | if (!response.ok) { 73 | notify({ 74 | type: 'error', 75 | title: `There was some error while fetching the ${key}` 76 | }) 77 | throw new Error(`There was some error while fetching the ${key}`); 78 | } 79 | return response.json(); 80 | }; 81 | 82 | const { 83 | data, 84 | isLoading, 85 | isError, 86 | isFetching, 87 | error, 88 | refetch, 89 | }: UseQueryResult = useQuery([key, filters, sort, sortMode, page, pageSize], fetchFilteredData, { 90 | refetchOnWindowFocus: false, 91 | networkMode: 'always', 92 | ...useApiOptions, 93 | }); 94 | 95 | const resetFilters = () => { 96 | setFilters({}); 97 | setPage(1); 98 | } 99 | 100 | const handleFilterChange = (newFilters: Filters): void => { 101 | setFilters({...filters, ...newFilters}); 102 | setPage(1); 103 | }; 104 | 105 | const handleSortChange = (newSort: Sort): void => { 106 | setSort(newSort); 107 | setPage(1); 108 | }; 109 | 110 | const handleSortModeChange = (newSortMode: SortMode): void => { 111 | setSortMode(newSortMode); 112 | } 113 | 114 | const handlePageChange = (newPage: number): void => { 115 | setPage(newPage); 116 | }; 117 | 118 | const handlePageSizeChange = (newPageSize: number): void => { 119 | setPageSize(newPageSize); 120 | } 121 | 122 | const manualFetch = (): void => { 123 | queryClient.prefetchQuery([key, filters, sort, sortMode, page, pageSize], fetchFilteredData); 124 | } 125 | 126 | return { 127 | data, 128 | isLoading, 129 | isFetching, 130 | isError, 131 | error, 132 | filters, 133 | sort, sortMode, 134 | page, pageSize, 135 | handleFilterChange, 136 | handleSortChange, handleSortModeChange, 137 | handlePageChange, handlePageSizeChange, 138 | fetchData: refetch, 139 | fetch: manualFetch, 140 | resetFilters 141 | }; 142 | } 143 | 144 | export default useApi; 145 | -------------------------------------------------------------------------------- /src/api/hooks/use.load.item.ts: -------------------------------------------------------------------------------- 1 | import {useState} from "react"; 2 | import {jsonRequest} from "../request/request"; 3 | import {HttpException, UnauthorizedException} from "../../lib/http/exception/http.exception"; 4 | import {QueryString} from "../../lib/location/query.string"; 5 | export interface LoadItemState { 6 | isLoading: boolean; 7 | item?: T; 8 | error?: string; 9 | } 10 | 11 | export interface LoadItemActions { 12 | loadItem: (params?: any) => Promise; 13 | } 14 | 15 | export const useLoadItem = (url: string): [LoadItemState, LoadItemActions] => { 16 | const [isLoading, setIsLoading] = useState(false); 17 | const [foundError, setFoundError] = useState(); 18 | const [item, setItem] = useState(); 19 | 20 | const loadItem = async (params?: any) => { 21 | setIsLoading(true); 22 | setFoundError(undefined); 23 | 24 | const a = new URL(url); 25 | a.search = QueryString.stringify(params); 26 | 27 | try { 28 | const response = await jsonRequest(a.toString()); 29 | const json = await response.json(); 30 | 31 | setItem(json); 32 | 33 | }catch (exception){ 34 | if(exception instanceof UnauthorizedException){ 35 | const res = await exception.response.json(); 36 | setFoundError(res.message); 37 | } 38 | 39 | if(exception instanceof HttpException){ 40 | setFoundError(exception.message); 41 | } 42 | 43 | throw exception; 44 | }finally { 45 | setIsLoading(false); 46 | } 47 | }; 48 | 49 | return [{ isLoading, item, error: foundError }, { loadItem }]; 50 | }; 51 | -------------------------------------------------------------------------------- /src/api/model/barcode.ts: -------------------------------------------------------------------------------- 1 | import { HydraId, HydraType } from "./hydra"; 2 | import { Product } from "./product"; 3 | import { ProductVariant } from "./product.variant"; 4 | 5 | export interface Barcode extends HydraId, HydraType { 6 | id: string; 7 | barcode: string; 8 | item: Product; 9 | variant?: ProductVariant; 10 | measurement: string; 11 | unit?: string; 12 | price?: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/api/model/brand.ts: -------------------------------------------------------------------------------- 1 | import {Store} from "./store"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface Brand extends HydraId, HydraType{ 5 | id: string; 6 | name: string; 7 | stores: Store[]; 8 | isActive: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /src/api/model/cart.item.ts: -------------------------------------------------------------------------------- 1 | import {Product} from "./product"; 2 | import {ProductVariant} from "./product.variant"; 3 | import {Tax} from "./tax"; 4 | import {HydraId, HydraType} from "./hydra"; 5 | 6 | export interface CartItem extends HydraId, HydraType{ 7 | item: Product; 8 | quantity: number; 9 | price: number; 10 | variant?: ProductVariant; 11 | discount?: number; 12 | taxes: Tax[]; 13 | checked?: boolean; 14 | void?: boolean; 15 | taxIncluded?: boolean; 16 | discountIncluded?: boolean; 17 | stock?: number; 18 | } 19 | -------------------------------------------------------------------------------- /src/api/model/category.ts: -------------------------------------------------------------------------------- 1 | import {Store} from "./store"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface Category extends HydraId, HydraType { 5 | id: number; 6 | name: string; 7 | type: string; 8 | isActive: boolean; 9 | stores: Store[]; 10 | } 11 | -------------------------------------------------------------------------------- /src/api/model/closing.ts: -------------------------------------------------------------------------------- 1 | import {User} from "./user"; 2 | import {Store} from "./store"; 3 | import {Terminal} from "./terminal"; 4 | import {HydraId, HydraType} from "./hydra"; 5 | 6 | export interface Closing extends HydraId, HydraType{ 7 | id: string; 8 | dateFrom?: { datetime: string }; 9 | dateTo?: { datetime: string }; 10 | closedAt?: string; 11 | closedBy?: User; 12 | openingBalance?: number; 13 | closingBalance?: number; 14 | cashAdded?: number; 15 | cashWithdrawn?: number; 16 | openedBy?: User; 17 | data?: any; 18 | store: Store; 19 | denominations?: any; 20 | createdAt: {datetime: string}; 21 | terminal: Terminal; 22 | } 23 | -------------------------------------------------------------------------------- /src/api/model/common.ts: -------------------------------------------------------------------------------- 1 | export interface ReactSelectOptionProps{ 2 | value: string, 3 | label: string, 4 | [key: string]: string 5 | } 6 | -------------------------------------------------------------------------------- /src/api/model/coupn.ts: -------------------------------------------------------------------------------- 1 | import { HydraId, HydraType } from "./hydra"; 2 | 3 | export interface Coupon extends HydraId, HydraType { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/api/model/customer.payment.ts: -------------------------------------------------------------------------------- 1 | import {Order} from "./order"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface CustomerPayment extends HydraId, HydraType { 5 | id: string; 6 | amount: number; 7 | description: string; 8 | order?: Omit; 9 | createdAt: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/api/model/customer.ts: -------------------------------------------------------------------------------- 1 | import {Order} from "./order"; 2 | import {CustomerPayment} from "./customer.payment"; 3 | import {HydraId, HydraType} from "./hydra"; 4 | 5 | export interface Customer extends HydraId, HydraType { 6 | id: string; 7 | name: string; 8 | email?: string; 9 | phone?: string; 10 | address?: string; 11 | lat?: number; 12 | lng?: number; 13 | sale?: number; 14 | paid?: number; 15 | outstanding: number; 16 | cnic?: string; 17 | payments: CustomerPayment[]; 18 | orders: Omit[]; 19 | openingBalance?: number; 20 | allowCreditSale?: boolean; 21 | creditLimit?: string; 22 | } 23 | -------------------------------------------------------------------------------- /src/api/model/department.ts: -------------------------------------------------------------------------------- 1 | import {Store} from "./store"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface Department extends HydraId, HydraType { 5 | id: string; 6 | name: string; 7 | description?: string; 8 | uuid: string; 9 | createdAt: {datetime: string}; 10 | updatedAt: {datetime: string}; 11 | store?: Store; 12 | } 13 | -------------------------------------------------------------------------------- /src/api/model/device.ts: -------------------------------------------------------------------------------- 1 | import {HydraId, HydraType} from "./hydra"; 2 | 3 | export interface Device extends HydraId, HydraType { 4 | id: string; 5 | name: string; 6 | ipAddress: string; 7 | port: string; 8 | prints: number; 9 | } 10 | -------------------------------------------------------------------------------- /src/api/model/discount.ts: -------------------------------------------------------------------------------- 1 | import {Store} from "./store"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface Discount extends HydraId, HydraType { 5 | id: number; 6 | name: string; 7 | rate?: number; 8 | rateType?: string; 9 | scope?: string; 10 | stores: Store[]; 11 | isActive: boolean; 12 | } 13 | 14 | export enum DiscountRate { 15 | RATE_FIXED = 'fixed', 16 | RATE_PERCENT = 'percent' 17 | } 18 | 19 | export enum DiscountScope { 20 | SCOPE_OPEN = 'open', 21 | SCOPE_EXACT = 'exact' 22 | } 23 | -------------------------------------------------------------------------------- /src/api/model/expense.ts: -------------------------------------------------------------------------------- 1 | import {Store} from "./store"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface Expense extends HydraId, HydraType { 5 | id: string; 6 | description: string; 7 | amount: number; 8 | createdAt: string; 9 | store: Store; 10 | } 11 | -------------------------------------------------------------------------------- /src/api/model/hydra.ts: -------------------------------------------------------------------------------- 1 | export interface HydraCollection extends HydraId, HydraType{ 2 | '@context': string; 3 | 'hydra:totalItems': number; 4 | 'hydra:member': T[]; 5 | 'hydra:view': { 6 | '@id': string; 7 | '@type': string; 8 | }; 9 | } 10 | 11 | export interface HydraError extends HydraType{ 12 | '@context': string; 13 | 'hydra:description': string; 14 | 'hydra:title': string; 15 | trace: HydraErrorTrace[] 16 | } 17 | 18 | export interface HydraErrorTrace { 19 | args: string[]; 20 | class: string; 21 | file: string; 22 | line: number; 23 | namespace: string; 24 | short_class: string; 25 | type: string; 26 | } 27 | 28 | export interface HydraId { 29 | '@id'?: string; 30 | } 31 | 32 | export interface HydraType { 33 | '@type'?: string; 34 | } 35 | -------------------------------------------------------------------------------- /src/api/model/media.ts: -------------------------------------------------------------------------------- 1 | import {HydraId, HydraType} from "./hydra"; 2 | 3 | export interface Media extends HydraId, HydraType{ 4 | id: string; 5 | name: string; 6 | originalName: string; 7 | size: number; 8 | mimeType?: string; 9 | extension?: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/api/model/order.discount.ts: -------------------------------------------------------------------------------- 1 | import {Discount} from "./discount"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface OrderDiscount extends HydraId, HydraType { 5 | id: string; 6 | rate?: number; 7 | amount?: number; 8 | type?: Discount; 9 | } 10 | -------------------------------------------------------------------------------- /src/api/model/order.item.ts: -------------------------------------------------------------------------------- 1 | import {Product} from "./product"; 2 | import {ProductVariant} from "./product.variant"; 3 | import {Tax} from "./tax"; 4 | import {HydraId, HydraType} from "./hydra"; 5 | 6 | export interface OrderItem extends HydraId, HydraType { 7 | id: string; 8 | product: Product; 9 | variant?: ProductVariant; 10 | quantity: number; 11 | price: number; 12 | isSuspended?: boolean; 13 | isDeleted?: boolean; 14 | isReturned?: boolean; 15 | taxes: Tax[]; 16 | taxesTotal: number; 17 | discount: number; 18 | createdAt: string; 19 | updatedAt: string; 20 | } 21 | 22 | export interface OrderItemSimple extends HydraId, HydraType { 23 | id: string; 24 | product?: string; 25 | variant?: string; 26 | quantity: number; 27 | price: number; 28 | isSuspended?: boolean; 29 | isDeleted?: boolean; 30 | isReturned?: boolean; 31 | taxes: string[]; 32 | taxesTotal: number; 33 | discount: number; 34 | createdAt: string; 35 | updatedAt: string; 36 | order?: string; 37 | } 38 | -------------------------------------------------------------------------------- /src/api/model/order.payment.ts: -------------------------------------------------------------------------------- 1 | import {PaymentType} from "./payment.type"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface OrderPayment extends HydraId, HydraType { 5 | total: number; 6 | received: number; 7 | due: number; 8 | type?: PaymentType; 9 | } 10 | -------------------------------------------------------------------------------- /src/api/model/order.tax.ts: -------------------------------------------------------------------------------- 1 | import {Tax} from "./tax"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface OrderTax extends HydraId, HydraType { 5 | id: string; 6 | rate?: number; 7 | amount?: number; 8 | type?: Tax; 9 | } 10 | -------------------------------------------------------------------------------- /src/api/model/order.ts: -------------------------------------------------------------------------------- 1 | import { User } from "./user"; 2 | import { OrderPayment } from "./order.payment"; 3 | import { Customer } from "./customer"; 4 | import { OrderItem } from "./order.item"; 5 | import { OrderDiscount } from "./order.discount"; 6 | import { OrderTax } from "./order.tax"; 7 | import { Store } from "./store"; 8 | import { Terminal } from "./terminal"; 9 | import { HydraId, HydraType } from "./hydra"; 10 | 11 | export interface Order extends HydraId, HydraType { 12 | id: string; 13 | orderId?: string; 14 | customer?: Customer; 15 | isSuspended?: boolean; 16 | isDeleted?: boolean; 17 | isReturned?: boolean; 18 | isDispatched?: boolean; 19 | user?: User; 20 | items: OrderItem[]; 21 | discount?: OrderDiscount; 22 | tax?: OrderTax; 23 | payments: OrderPayment[]; 24 | createdAt: string; 25 | status: string; 26 | returnedFrom?: Pick; 27 | notes?: string; 28 | store: Store; 29 | terminal: Terminal; 30 | itemTaxes: number; 31 | adjustment?: number; 32 | } 33 | 34 | export enum OrderStatus { 35 | COMPLETED = "Completed", 36 | ON_HOLD = "On Hold", 37 | DELETED = "Deleted", 38 | DISPATCHED = "Dispatched", 39 | PENDING = "Pending", 40 | RETURNED = "Returned", 41 | } 42 | -------------------------------------------------------------------------------- /src/api/model/payment.type.ts: -------------------------------------------------------------------------------- 1 | import {Store} from "./store"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface PaymentType extends HydraId, HydraType { 5 | id: string; 6 | name: string; 7 | type: string; 8 | canHaveChangeDue?: boolean; 9 | stores: Store[]; 10 | isActive: boolean; 11 | } 12 | -------------------------------------------------------------------------------- /src/api/model/product.price.ts: -------------------------------------------------------------------------------- 1 | import {HydraId, HydraType} from "./hydra"; 2 | 3 | export interface ProductPrice extends HydraId, HydraType { 4 | id: number; 5 | date?: string; 6 | time?: string; 7 | timeTo?: string; 8 | day?: string; 9 | week?: number; 10 | month?: number; 11 | quarter?: number; 12 | rate?: number; 13 | minQuantity?: number; 14 | maxQuantity?: number; 15 | basePrice?: number; 16 | baseQuantity?: number; 17 | } 18 | -------------------------------------------------------------------------------- /src/api/model/product.store.ts: -------------------------------------------------------------------------------- 1 | import { HydraId, HydraType } from "./hydra"; 2 | import { Store } from "./store"; 3 | import { Product } from "./product"; 4 | 5 | export interface ProductStore extends HydraId, HydraType { 6 | id: number; 7 | store: Store; 8 | product: Product; 9 | quantity: string; 10 | location?: string; 11 | reOrderLevel?: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/api/model/product.ts: -------------------------------------------------------------------------------- 1 | import {Category} from "./category"; 2 | import {ProductVariant} from "./product.variant"; 3 | import {ProductPrice} from "./product.price"; 4 | import {Brand} from "./brand"; 5 | import {Supplier} from "./supplier"; 6 | import {Store} from "./store"; 7 | import {Department} from "./department"; 8 | import {Terminal} from "./terminal"; 9 | import {Tax} from "./tax"; 10 | import {HydraId, HydraType} from "./hydra"; 11 | import { ProductStore } from "./product.store"; 12 | 13 | export interface Product extends HydraId, HydraType{ 14 | id: number; 15 | name: string; 16 | sku?: string; 17 | barcode?: string; 18 | baseQuantity?: number; 19 | isAvailable?: boolean; 20 | quantity?: number; 21 | basePrice: number; 22 | categories: Category[]; 23 | variants: ProductVariant[]; 24 | prices: ProductPrice[]; 25 | isActive: boolean; 26 | uuid?: string; 27 | purchaseUnit?: string; 28 | saleUnit?: string; 29 | cost?: number; 30 | brands: Brand[]; 31 | suppliers: Supplier[]; 32 | stores: ProductStore[]; 33 | department?: Department; 34 | terminals: Terminal[]; 35 | taxes: Tax[]; 36 | manageInventory?: boolean 37 | } 38 | 39 | export interface SearchableProduct { 40 | isVariant?: boolean; 41 | variant?: ProductVariant; 42 | item: Product 43 | } 44 | -------------------------------------------------------------------------------- /src/api/model/product.variant.ts: -------------------------------------------------------------------------------- 1 | import {ProductPrice} from "./product.price"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface ProductVariant extends HydraId, HydraType { 5 | id: number; 6 | name?: string; 7 | attributeName?: string; 8 | attributeValue?: string; 9 | barcode?: string; 10 | price?: number; 11 | prices?: ProductPrice[]; 12 | quantity?: number; 13 | } 14 | -------------------------------------------------------------------------------- /src/api/model/purchase.item.ts: -------------------------------------------------------------------------------- 1 | import {HydraId, HydraType} from "./hydra"; 2 | import {Product} from "./product"; 3 | import {ProductVariant} from "./product.variant"; 4 | import { Purchase } from "./purchase"; 5 | 6 | export interface PurchaseItem extends HydraId, HydraType { 7 | id: number; 8 | item: Product; 9 | quantity: string; 10 | quantityRequested?: string; 11 | purchasePrice: string; 12 | purchaseUnit?: string; 13 | barcode?: string; 14 | comments?: string; 15 | variants: PurchaseItemVariant[]; 16 | createdAt: string; 17 | purchase: Pick 18 | } 19 | 20 | export interface PurchaseItemVariant extends HydraId, HydraType{ 21 | id: number; 22 | variant: ProductVariant; 23 | quantity: string; 24 | quantityRequested?: string; 25 | purchasePrice: string; 26 | purchaseUnit?: string; 27 | barcode?: string; 28 | comments?: string; 29 | } 30 | -------------------------------------------------------------------------------- /src/api/model/purchase.order.item.ts: -------------------------------------------------------------------------------- 1 | import {HydraId, HydraType} from "./hydra"; 2 | import {Product} from "./product"; 3 | import {ProductVariant} from "./product.variant"; 4 | 5 | export interface PurchaseOrderItem extends HydraId, HydraType { 6 | id: number; 7 | item: Product; 8 | quantity: string; 9 | price: string; 10 | unit?: string; 11 | comments?: string; 12 | variants: PurchaseOrderItemVariant[]; 13 | } 14 | 15 | export interface PurchaseOrderItemVariant extends HydraId, HydraType { 16 | id: number; 17 | quantity: string; 18 | variant: ProductVariant; 19 | purchasePrice: string; 20 | purchaseUnit?: string; 21 | comments?: string; 22 | } 23 | -------------------------------------------------------------------------------- /src/api/model/purchase.order.ts: -------------------------------------------------------------------------------- 1 | import {HydraId, HydraType} from "./hydra"; 2 | import {Supplier} from "./supplier"; 3 | import {PurchaseOrderItem} from "./purchase.order.item"; 4 | import {Store} from "./store"; 5 | 6 | export interface PurchaseOrder extends HydraId, HydraType { 7 | id: number; 8 | createdAt: string; 9 | supplier?: Supplier; 10 | items: PurchaseOrderItem[]; 11 | store?: Store; 12 | poNumber?: string; 13 | isUsed?: boolean; 14 | } 15 | -------------------------------------------------------------------------------- /src/api/model/purchase.ts: -------------------------------------------------------------------------------- 1 | import {HydraId, HydraType} from "./hydra"; 2 | import {Supplier} from "./supplier"; 3 | import {Store} from "./store"; 4 | import {PurchaseItem} from "./purchase.item"; 5 | import {User} from "./user"; 6 | import {PurchaseOrder} from "./purchase.order"; 7 | import {PaymentType} from "./payment.type"; 8 | 9 | export interface Purchase extends HydraId, HydraType { 10 | id: number; 11 | createdAt: string; 12 | supplier?: Supplier; 13 | store: Store; 14 | items: PurchaseItem[]; 15 | updateStocks?: boolean; 16 | updatePrice?: boolean; 17 | purchasedBy?: User; 18 | purchaseOrder?: PurchaseOrder; 19 | purchaseNumber?: number; 20 | purchaseMode?: string; 21 | paymentType?: PaymentType; 22 | total: number; 23 | } 24 | -------------------------------------------------------------------------------- /src/api/model/setting.ts: -------------------------------------------------------------------------------- 1 | import {User} from "./user"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface Setting extends HydraId, HydraType { 5 | id: string; 6 | name: string; 7 | value?: string; 8 | description?: string; 9 | type?: string; 10 | user?: User; 11 | } 12 | 13 | export enum SettingTypes { 14 | TYPE_RECEIPT = 'sale_receipt' 15 | } 16 | -------------------------------------------------------------------------------- /src/api/model/store.ts: -------------------------------------------------------------------------------- 1 | import {Terminal} from "./terminal"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface Store extends HydraId, HydraType{ 5 | id: string; 6 | name: string; 7 | location?: string; 8 | terminals: Terminal[]; 9 | isActive: boolean 10 | } 11 | -------------------------------------------------------------------------------- /src/api/model/supplier.payment.ts: -------------------------------------------------------------------------------- 1 | import {HydraId, HydraType} from "./hydra"; 2 | import {Purchase} from "./purchase"; 3 | 4 | export interface SupplierPayment extends HydraId, HydraType{ 5 | id: string; 6 | amount: number; 7 | description?: string; 8 | createdAt: string; 9 | purchase?: Omit 10 | } 11 | -------------------------------------------------------------------------------- /src/api/model/supplier.ts: -------------------------------------------------------------------------------- 1 | import {Store} from "./store"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | import {Purchase} from "./purchase"; 4 | import {PurchaseOrder} from "./purchase.order"; 5 | import {SupplierPayment} from "./supplier.payment"; 6 | 7 | export interface Supplier extends HydraId, HydraType{ 8 | id: string; 9 | name: string; 10 | phone?: string; 11 | email?: string; 12 | fax?: string; 13 | whatsApp?: string; 14 | address?: string; 15 | stores: Store[]; 16 | openingBalance?: number; 17 | payments: SupplierPayment[]; 18 | purchases: Purchase[]; 19 | purchaseOrder: PurchaseOrder[]; 20 | outstanding: number; 21 | purchaseTotal: number; 22 | paid: number; 23 | } 24 | -------------------------------------------------------------------------------- /src/api/model/tax.ts: -------------------------------------------------------------------------------- 1 | import {Store} from "./store"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface Tax extends HydraId, HydraType { 5 | id: number; 6 | name: string; 7 | rate: number; 8 | stores: Store[]; 9 | isActive: boolean; 10 | } 11 | -------------------------------------------------------------------------------- /src/api/model/terminal.ts: -------------------------------------------------------------------------------- 1 | import {Store} from "./store"; 2 | import {ReactSelectOptionProps} from "./common"; 3 | import {HydraId, HydraType} from "./hydra"; 4 | import {Product} from "./product"; 5 | 6 | export interface Terminal extends HydraId, HydraType { 7 | id: string; 8 | code: string; 9 | store?: Store; 10 | description?: string; 11 | uuid: string; 12 | createdAt: {datetime: string}; 13 | updatedAt: {datetime: string}; 14 | products: Product[]; 15 | isActive: boolean 16 | } 17 | -------------------------------------------------------------------------------- /src/api/model/user.ts: -------------------------------------------------------------------------------- 1 | import {Store} from "./store"; 2 | import {HydraId, HydraType} from "./hydra"; 3 | 4 | export interface User extends HydraId, HydraType { 5 | username: string; 6 | displayName: string; 7 | id: number; 8 | email: string; 9 | roles: string[]; 10 | stores: Store[]; 11 | isActive: boolean 12 | } 13 | -------------------------------------------------------------------------------- /src/api/model/validation/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../../../lib/validator/validation.result'; 2 | 3 | export enum ValidationMessage { 4 | Required = 'This value should not be blank.', 5 | NotNull = 'This value should not be null.', 6 | Number = 'This value should be a valid number.', 7 | Email = 'This value is not a valid email address.', 8 | Positive = 'This value should be positive.', 9 | PositiveOrZero = 'This value should be either positive or zero.', 10 | Negative = 'This value should be negative.', 11 | NegativeOrZero = 'This value should be either negative or zero.', 12 | False = 'This value should be false.', 13 | True = 'This value should be true.', 14 | ValidChoice = 'The value you selected is not a valid choice.', 15 | Between = 'This value should be between :min and :max.', 16 | Min = 'This value should be greater then :min', 17 | Max = 'This valud should be less then :max' 18 | } 19 | -------------------------------------------------------------------------------- /src/api/request/header.ts: -------------------------------------------------------------------------------- 1 | import { acceptHeader, contentTypeHeader } from '../../lib/http/header/header'; 2 | import {APPLICATION_JSON, FORM_DATA} from '../../lib/http/mime/mime'; 3 | 4 | export const jsonContentTypeHeader = () => contentTypeHeader(APPLICATION_JSON); 5 | 6 | export const jsonAcceptHeader = () => acceptHeader(APPLICATION_JSON); 7 | 8 | export const formContentTypeHeader = () => contentTypeHeader(FORM_DATA); 9 | 10 | -------------------------------------------------------------------------------- /src/api/request/request.ts: -------------------------------------------------------------------------------- 1 | import { request as httpRequest } from '../../lib/http/request'; 2 | import { fetchConfig } from '../config/fetch.config'; 3 | import { composeHeaders } from '../../lib/http/header/compose'; 4 | import {formContentTypeHeader, jsonAcceptHeader, jsonContentTypeHeader} from './header'; 5 | import Cookies from 'js-cookie'; 6 | 7 | /** 8 | * @see httpRequest 9 | */ 10 | export const request = async (input: RequestInfo, init: RequestInit = {}): Promise => { 11 | const defaultHeaders = fetchConfig.headers || {}; 12 | const initHeaders = init.headers || {}; 13 | const authHeader = { 14 | 'Authorization': 'Bearer ' + Cookies.get('JWT') || '' 15 | }; 16 | 17 | init = { 18 | ...fetchConfig, 19 | ...init, 20 | headers: composeHeaders(defaultHeaders, initHeaders, authHeader) 21 | }; 22 | 23 | return httpRequest(input, init); 24 | }; 25 | 26 | /** 27 | * @see httpRequest 28 | */ 29 | export const jsonRequest = async (input: RequestInfo, init: RequestInit = {}) => { 30 | const initHeaders = init.headers || {}; 31 | 32 | const headers = composeHeaders( 33 | initHeaders, jsonContentTypeHeader() 34 | ); 35 | 36 | init = { 37 | ...init, 38 | headers 39 | }; 40 | 41 | return request(input, init); 42 | }; 43 | 44 | export const fetchJson = async (input: RequestInfo, init: RequestInit = {}) => { 45 | 46 | const response = await jsonRequest(input, init); 47 | return await response.json(); 48 | }; 49 | 50 | export const formRequest = async (input: RequestInfo, init: RequestInit = {}) => { 51 | const initHeaders = init.headers || {}; 52 | 53 | const headers = composeHeaders( 54 | initHeaders, formContentTypeHeader(), jsonAcceptHeader() 55 | ); 56 | 57 | init = { 58 | ...init, 59 | headers 60 | }; 61 | 62 | return request(input, init); 63 | }; 64 | -------------------------------------------------------------------------------- /src/api/routing/routes/admin.backend.app.ts: -------------------------------------------------------------------------------- 1 | import { url } from '../url'; 2 | 3 | const scopeUrl = (path: string) => url('/' + path); 4 | const adminScopeUrl = (route: string) => scopeUrl('admin/' + route); 5 | 6 | export const LOGIN = scopeUrl('auth/login_check'); 7 | export const LOGOUT = scopeUrl('auth/logout'); 8 | export const AUTH_INFO = scopeUrl('auth/info'); 9 | export const FORGOT_PASSWORD = scopeUrl('auth/forgot-password'); 10 | export const UPDATE_LOCALE = scopeUrl('locale'); 11 | 12 | export const PROFILE = adminScopeUrl('profile/update'); 13 | 14 | export const GROUP_LIST = adminScopeUrl('group/list'); 15 | export const GROUP_CREATE = adminScopeUrl('group/create'); 16 | export const GROUP_GET = adminScopeUrl('group/:id'); 17 | export const GROUP_EDIT = adminScopeUrl('group/:id'); 18 | export const GROUP_DELETE = adminScopeUrl('group/:id'); 19 | 20 | export const NUMBER_LIST = adminScopeUrl('number/list'); 21 | export const NUMBER_CREATE = adminScopeUrl('number/create'); 22 | export const NUMBER_GET = adminScopeUrl('number/:id'); 23 | export const NUMBER_EDIT = adminScopeUrl('number/:id'); 24 | export const NUMBER_DELETE = adminScopeUrl('number/:id'); 25 | 26 | export const SENT_LIST = adminScopeUrl('sent/list'); 27 | export const SENT_CREATE = adminScopeUrl('sent/create'); 28 | export const SENT_GET = adminScopeUrl('sent/:id'); 29 | export const SENT_EDIT = adminScopeUrl('sent/:id'); 30 | export const SENT_DELETE = adminScopeUrl('sent/:id'); 31 | 32 | export const TEMPLATE_LIST = adminScopeUrl('template/list'); 33 | export const TEMPLATE_CREATE = adminScopeUrl('template/create'); 34 | export const TEMPLATE_GET = adminScopeUrl('template/:id'); 35 | export const TEMPLATE_EDIT = adminScopeUrl('template/:id'); 36 | export const TEMPLATE_DELETE = adminScopeUrl('template/:id'); 37 | 38 | export const USER_LIST = adminScopeUrl('user/list'); 39 | export const USER_CREATE = adminScopeUrl('user/create'); 40 | export const USER_GET = adminScopeUrl('user/:id'); 41 | export const USER_EDIT = adminScopeUrl('user/:id'); 42 | export const USER_DELETE = adminScopeUrl('user/:id'); 43 | 44 | export const MEDIA_UPLOAD = adminScopeUrl('media/upload'); 45 | export const MEDIA_DOWNLOAD = scopeUrl('media/d/:name'); 46 | 47 | export const SENDER_NAME_LIST = adminScopeUrl('sender-name/list'); 48 | export const SENDER_NAME_CREATE = adminScopeUrl('sender-name/create'); 49 | export const SENDER_NAME_GET = adminScopeUrl('sender-name/:id'); 50 | export const SENDER_NAME_EDIT = adminScopeUrl('sender-name/:id'); 51 | export const SENDER_NAME_DELETE = adminScopeUrl('sender-name/:id'); 52 | 53 | export const PACKAGE_LIST = adminScopeUrl('package/list'); 54 | export const PACKAGE_CREATE = adminScopeUrl('package/create'); 55 | export const PACKAGE_GET = adminScopeUrl('package/:id'); 56 | export const PACKAGE_EDIT = adminScopeUrl('package/:id'); 57 | export const PACKAGE_DELETE = adminScopeUrl('package/:id'); 58 | -------------------------------------------------------------------------------- /src/api/routing/url.ts: -------------------------------------------------------------------------------- 1 | import { routeConfig } from '../config/route.config'; 2 | 3 | export const url = (uri: string) => routeConfig.baseUrl + uri; -------------------------------------------------------------------------------- /src/app-admin/app.tsx: -------------------------------------------------------------------------------- 1 | import Login from './containers/login/login'; 2 | import {BrowserRouter as Router, Route, useLocation} from "react-router-dom"; 3 | import {DASHBOARD, FORGOT_PASSWORD, LOGIN, PROFILE, USERS, USERS_CREATE, USERS_EDIT,} from "./routes/frontend.routes"; 4 | import {connect, useSelector} from "react-redux"; 5 | import {RootState} from "../duck/_root/root.state"; 6 | import {isUserLoggedIn} from "../duck/auth/auth.selector"; 7 | import {getBootstrapError, hasBootstrapped} from "../duck/app/app.selector"; 8 | import {bootstrap} from "../duck/app/app.action"; 9 | import {userLoggedOut} from "../duck/auth/auth.action"; 10 | import {bindActionCreators, Dispatch} from 'redux'; 11 | import {FunctionComponent, useEffect} from "react"; 12 | import {Dashboard} from "./containers/dashboard/dashboard"; 13 | import {useLogout} from "../duck/auth/hooks/useLogout"; 14 | import {Navigate, Routes} from 'react-router'; 15 | import {Error404} from "../app-common/components/error/404"; 16 | import {Users} from "./containers/dashboard/users"; 17 | import {ForgotPassword} from "./containers/forgot/forgot"; 18 | import {Profile} from "./containers/dashboard/profile/profile"; 19 | 20 | export interface AppProps { 21 | bootstrap: () => void; 22 | userLoggedOut: () => void; 23 | isLoggedIn?: boolean; 24 | hasBootstrapped?: boolean; 25 | bootstrapError?: Error; 26 | } 27 | 28 | 29 | const AppComponent: FunctionComponent = (props) => { 30 | 31 | const [logoutState, logoutAction] = useLogout(); 32 | 33 | useEffect(() => { 34 | props.bootstrap(); 35 | 36 | function handleException(e: any) { 37 | if (e.reason.code === 401) { 38 | logoutAction(); 39 | } 40 | } 41 | 42 | window.addEventListener('unhandledrejection', handleException); 43 | 44 | return () => window.removeEventListener('unhandledrejection', handleException); 45 | }, []); 46 | 47 | const {isLoggedIn, hasBootstrapped, bootstrapError} = props; 48 | 49 | if (!!bootstrapError) { 50 | return
An error occurred while initializing application
; 51 | } 52 | 53 | if (!hasBootstrapped) { 54 | return null; 55 | } 56 | 57 | 58 | return ( 59 | 60 | 61 | 63 | {isLoggedIn ? : } 64 | 65 | }/> 66 | 67 | 69 | {isLoggedIn ? : } 70 | 71 | }/> 72 | 73 | {/*Protected routes*/} 74 | }/> 75 | }/> 76 | 77 | }/> 78 | }/> 79 | }/> 80 | 81 | {/*if nothing matches show 404*/} 82 | }/> 83 | 84 | 85 | ); 86 | }; 87 | 88 | export const App = connect( 89 | (state: RootState) => ({ 90 | isLoggedIn: isUserLoggedIn(state), 91 | hasBootstrapped: hasBootstrapped(state), 92 | bootstrapError: getBootstrapError(state), 93 | }), 94 | (dispatch: Dispatch) => 95 | bindActionCreators( 96 | { 97 | bootstrap: bootstrap, 98 | userLoggedOut, 99 | }, 100 | dispatch 101 | ) 102 | )(AppComponent); 103 | 104 | export const RequireAuth = ({children}: { children: JSX.Element }) => { 105 | let location = useLocation(); 106 | const userLoggedIn = useSelector(isUserLoggedIn); 107 | 108 | if (!userLoggedIn) { 109 | return ; 114 | } 115 | 116 | return children; 117 | } 118 | -------------------------------------------------------------------------------- /src/app-admin/containers/dashboard/users/index.tsx: -------------------------------------------------------------------------------- 1 | import {DashboardLayout} from "../../layout/dashboard.layout"; 2 | import {useTranslation} from "react-i18next"; 3 | import {DASHBOARD} from "../../../routes/frontend.routes"; 4 | import React from "react"; 5 | 6 | 7 | export const Users = () => { 8 | const {t} = useTranslation(); 9 | 10 | return ( 11 | 18 |
19 |
20 |
21 |
22 |
{t('List')}
23 |
24 |
25 |
26 |
27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/app-admin/containers/layout/dashboard.layout.tsx: -------------------------------------------------------------------------------- 1 | import {FC, PropsWithChildren, ReactNode} from "react"; 2 | import Layout from "./layout"; 3 | import classNames from "classnames"; 4 | import {Link} from "react-router-dom"; 5 | import {useTranslation} from "react-i18next"; 6 | 7 | export interface BreadCrumb { 8 | title: string; 9 | link?: string; 10 | current?: boolean; 11 | } 12 | 13 | export interface DashboardLayoutProps extends PropsWithChildren { 14 | title?: string; 15 | breadCrumbs?: BreadCrumb[]; 16 | buttons?: ReactNode[]; 17 | } 18 | 19 | export const DashboardLayout: FC = ({ 20 | title, breadCrumbs, children, buttons 21 | }) => { 22 | const {t} = useTranslation(); 23 | 24 | return ( 25 | 26 |
27 |
28 |
29 |

{t(title || '')}

30 | {!!breadCrumbs && ( 31 | 47 | )} 48 |
49 |
50 |
51 | {buttons} 52 |
53 |
54 |
55 | {children} 56 |
57 |
58 | ); 59 | }; 60 | -------------------------------------------------------------------------------- /src/app-admin/containers/layout/footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const Footer = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |

Do you have questions?

11 |

Call or visit us.

12 |
13 | + (231) 777432694 14 |
15 |

16 | Duport Road, Paynesville, Liberia 17 |

18 |

19 | info@liberrands.com 20 |

21 |
22 |
23 |
24 |
25 |

Useful Links

26 |
    27 |
  • 28 | About Us 29 |
  • 30 |
  • 31 | Pricing 32 |
  • 33 |
  • 34 | Privacy Policy 35 |
  • 36 |
  • 37 | Terms & Conditions 38 |
  • 39 |
  • 40 | Cookies Policy 41 |
  • 42 |
43 |
44 |
45 |
46 |
47 |
    48 |
  • 49 | Disclaimer 50 |
  • 51 |
  • 52 | Contact 53 |
  • 54 |
55 |
56 |
57 |
58 |

Connect With Us

59 |

Join us on Our Social Platforms for latest news & Updates

60 |
    61 |
  • Facebook
  • 62 |
  • Twitter
  • 63 |
  • Instagram
  • 64 |
  • LinkedIn
  • 65 |
66 |
67 |
68 |
69 | Copyright 2021 liberrands.com All Rights Reserved | Developed by: Smile Solutions, Inc. 70 |
71 |
72 |
73 |
74 |
75 | ); 76 | }; -------------------------------------------------------------------------------- /src/app-admin/containers/layout/layout.tsx: -------------------------------------------------------------------------------- 1 | import React, {FunctionComponent, PropsWithChildren} from 'react'; 2 | import Navigation from "./navbar"; 3 | import {Sidebar} from "./sidebar"; 4 | import {useSelector} from "react-redux"; 5 | import {isUserLoggedIn} from "../../../duck/auth/auth.selector"; 6 | 7 | export interface Props extends PropsWithChildren { 8 | isLoading?: boolean; 9 | } 10 | 11 | const Layout: FunctionComponent = (props) => { 12 | const isLoggedIn = useSelector(isUserLoggedIn); 13 | 14 | return ( 15 | <> 16 | 17 | 18 |
19 | {props.children} 20 |
21 | 22 | ); 23 | }; 24 | 25 | export default Layout; 26 | -------------------------------------------------------------------------------- /src/app-admin/containers/layout/sidebar.tsx: -------------------------------------------------------------------------------- 1 | import {useSelector} from "react-redux"; 2 | import {isUserLoggedIn} from "../../../duck/auth/auth.selector"; 3 | import React from "react"; 4 | import {Link, useLocation} from "react-router-dom"; 5 | import {DASHBOARD} from "../../routes/frontend.routes"; 6 | import classNames from "classnames"; 7 | import {useTranslation} from "react-i18next"; 8 | 9 | export const Sidebar = () => { 10 | const isLoggedIn = useSelector(isUserLoggedIn); 11 | const {t} = useTranslation(); 12 | 13 | const location = useLocation(); 14 | 15 | if (!isLoggedIn) { 16 | return (<>); 17 | } 18 | 19 | return ( 20 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/app-admin/routes/frontend.routes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * open routes 3 | */ 4 | const staticRoute = (route: string) => route; 5 | 6 | export const LOGIN = staticRoute('/'); 7 | export const FORGOT_PASSWORD = staticRoute('/forgot-password'); 8 | 9 | /** 10 | * protected routes 11 | */ 12 | export const DASHBOARD = staticRoute('/dashboard'); 13 | 14 | export const USERS = staticRoute('/users'); 15 | export const USERS_CREATE = staticRoute('/users/create'); 16 | export const USERS_EDIT = staticRoute('/users/edit/:id'); 17 | 18 | export const PROFILE = staticRoute('/profile'); 19 | -------------------------------------------------------------------------------- /src/app-common/components/confirm/confirm.alert.tsx: -------------------------------------------------------------------------------- 1 | import {Popover} from "antd"; 2 | import { PropsWithChildren, ReactNode, useState } from "react"; 3 | 4 | interface ConfirmAlertProps extends PropsWithChildren{ 5 | onConfirm: () => void, 6 | confirmText?: string, 7 | cancelText?: string, 8 | description?: ReactNode, 9 | title: ReactNode 10 | } 11 | 12 | export const ConfirmAlert = (params: ConfirmAlertProps) => { 13 | const {onConfirm, confirmText, cancelText, description, title, children} = params; 14 | const [open, setOpen] = useState(false); 15 | 16 | return ( 17 | {title}} 20 | content={ 21 | { 27 | setOpen(false); 28 | }} 29 | /> 30 | } 31 | trigger="click" 32 | onOpenChange={setOpen} 33 | open={open} 34 | > 35 | {children} 36 | 37 | ); 38 | }; 39 | 40 | interface ConfirmAlertButtonsProps{ 41 | description?: ReactNode; 42 | confirmText?: string; 43 | cancelText?: string; 44 | onConfirm: () => void; 45 | onCancel?: () => void; 46 | } 47 | 48 | const ConfirmAlertButtons = ({ 49 | description, confirmText, onConfirm, onCancel, cancelText 50 | }: ConfirmAlertButtonsProps) => { 51 | return ( 52 | <> 53 | {description && ( 54 |

{description}

55 | )} 56 |
57 | 58 | 59 |
60 | 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /src/app-common/components/confirm/notification.ts: -------------------------------------------------------------------------------- 1 | import { notification} from 'antd'; 2 | import {ReactNode} from "react"; 3 | import {IconType, NotificationPlacement} from "antd/es/notification/interface"; 4 | 5 | interface NotifyProps{ 6 | title?: ReactNode; 7 | description?: ReactNode; 8 | onClick?: () => void; 9 | icon?: ReactNode; 10 | type?: IconType; 11 | placement?: NotificationPlacement; 12 | duration?: number; 13 | } 14 | export const notify = ({ 15 | title, description, onClick, type, placement, duration 16 | }: NotifyProps) => { 17 | notification.open({ 18 | message: title || type?.toUpperCase(), 19 | description: description, 20 | type: type, 21 | onClick, 22 | placement: placement || 'bottomRight', 23 | key: 'persistent', 24 | duration: duration || 2, 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /src/app-common/components/container/trap.focus.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from "react"; 2 | 3 | interface Props extends PropsWithChildren { 4 | inputRef: HTMLInputElement | null; 5 | } 6 | 7 | export const TrapFocus = (props: Props) => { 8 | const trap = (event: any) => { 9 | if (document.body.classList.contains("ReactModal__Body--open")) return; // skip in modal 10 | 11 | const inputNodes = ["INPUT", "SELECT", "TEXTAREA"]; 12 | const inputClasses = [ 13 | 'rs-__input', 'rs-__input-container', 'rs-__control--is-focused', 'rs-__placeholder', 'rs-__single-value' 14 | ]; 15 | 16 | const oneOfClasses = () => { 17 | for(const inputCls of inputClasses){ 18 | if(event.target.classList.contains(inputCls)){ 19 | 20 | return true; 21 | } 22 | } 23 | return false; 24 | } 25 | 26 | if(oneOfClasses()){ 27 | return; 28 | } 29 | 30 | if ( 31 | event.target !== props.inputRef && 32 | (oneOfClasses() || inputNodes.includes(event.target.nodeName)) 33 | ) { 34 | return; 35 | } 36 | 37 | if (props.inputRef !== null) { 38 | props.inputRef.select(); 39 | } 40 | }; 41 | 42 | return ( 43 |
44 | {props.children} 45 |
46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /src/app-common/components/dynamic.value/dynamic.value.tsx: -------------------------------------------------------------------------------- 1 | import useApi from "../../../api/hooks/use.api"; 2 | import { useEffect } from "react"; 3 | // @ts-ignore 4 | import Loader from '../../../assets/images/spinner.svg'; 5 | 6 | interface Props { 7 | cacheKey: string; 8 | url?: string; 9 | displayLoader?: boolean; 10 | render?: (data: any) => void; 11 | properties?: string[]; 12 | } 13 | 14 | export const DynamicValue = ({ 15 | url, displayLoader, render, properties, cacheKey 16 | }: Props) => { 17 | // disable default firing 18 | const useLoadHook = useApi(cacheKey, import.meta.env.VITE_API_HOST + url || 'fake-url', 19 | {}, '', 'asc', 1, 1, {}, {enabled: false} 20 | ); 21 | 22 | useEffect(() => { 23 | if(url !== null) { 24 | useLoadHook.fetch(); 25 | } 26 | }, [url]); 27 | 28 | return ( 29 | <> 30 | {displayLoader && useLoadHook.isFetching && ( 31 | loader 32 | )} 33 | {render && render(useLoadHook)} 34 | {!useLoadHook.isFetching && !render && properties?.map(item => { 35 | if( useLoadHook.data?.hasOwnProperty(item) ) { 36 | return useLoadHook.data?.[item] 37 | } 38 | })} 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /src/app-common/components/error/404.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Layout from "../../../app-frontend/containers/layout/layout"; 3 | import {useTranslation} from "react-i18next"; 4 | import Image from '../../../assets/images/not-found.svg'; 5 | import {Link} from "react-router-dom"; 6 | 7 | export const Error404 = () => { 8 | const {t} = useTranslation(); 9 | return ( 10 | 11 |
12 |
13 |

404

14 |

{t("The page you are looking for doesn't exist.")}

15 | {t('Back to home')} 16 | Page Not Found 17 |
18 |
19 |
20 | 21 |
22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/app-common/components/ga/ga4.tsx: -------------------------------------------------------------------------------- 1 | import { FunctionComponent, useEffect } from "react"; 2 | import ReactGA from '../../../ga'; 3 | 4 | export interface GoogleAnalyticsProps { 5 | page: string; 6 | } 7 | 8 | export const GoogleAnalytics: FunctionComponent = (props) => { 9 | useEffect(() => { 10 | try { 11 | ReactGA.send({ 12 | hitType: 'pageview', 13 | page: props.page 14 | }); 15 | } catch ( e ) { 16 | console.log('ga error', e) 17 | } 18 | }, [props.page]); 19 | 20 | return null; 21 | }; 22 | -------------------------------------------------------------------------------- /src/app-common/components/input/button.tsx: -------------------------------------------------------------------------------- 1 | import {ButtonHTMLAttributes} from "react"; 2 | import classNames from "classnames"; 3 | 4 | interface ButtonProps extends ButtonHTMLAttributes { 5 | size?: "lg" | "xl" | "sm" 6 | active?: boolean; 7 | variant?: 'primary'|'secondary'|'danger'|'warning'|'success'|string; 8 | iconButton?: boolean; 9 | } 10 | 11 | export const Button = (props: ButtonProps) => { 12 | const {active, variant, size, iconButton, ...rest} = props; 13 | return ( 14 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/app-common/components/input/checkbox.intermediate.tsx: -------------------------------------------------------------------------------- 1 | import React, {HTMLProps} from "react"; 2 | 3 | export function IndeterminateCheckbox({ 4 | indeterminate, 5 | className = '', 6 | ...rest 7 | }: { indeterminate?: boolean } & HTMLProps) { 8 | const ref = React.useRef(null!) 9 | 10 | React.useEffect(() => { 11 | if (typeof indeterminate === 'boolean') { 12 | ref.current.indeterminate = !rest.checked && indeterminate 13 | } 14 | }, [ref, indeterminate]) 15 | 16 | return ( 17 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/app-common/components/input/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import {HTMLProps, useEffect, useRef} from "react"; 2 | import classNames from "classnames"; 3 | import _ from "lodash"; 4 | 5 | interface InputProps extends HTMLProps{ 6 | indeterminate?: boolean; 7 | } 8 | 9 | export const Checkbox = (props: InputProps) => { 10 | let ref = useRef(null); 11 | const {indeterminate, ...rest} = props; 12 | 13 | useEffect(() => { 14 | if(ref.current !== null){ 15 | ref.current.indeterminate = false; 16 | if(_.isBoolean(indeterminate)) { 17 | ref.current.indeterminate = indeterminate; 18 | } 19 | } 20 | }, [indeterminate, props.checked]); 21 | 22 | return ( 23 | 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/app-common/components/input/custom.react.select.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Select, { GroupBase, Props } from "react-select"; 3 | import { Theme } from "react-select/dist/declarations/src/"; 4 | // @ts-ignore 5 | import Spinner from "../../../assets/images/spinner.svg"; 6 | 7 | const primaryColor = "0 70 254"; 8 | const focusRingColor = "152 189 254"; 9 | 10 | export const themeConfig = (theme: Theme) => ({ 11 | ...theme, 12 | borderRadius: 8, 13 | colors: { 14 | ...theme.colors, 15 | primary: `rgb(${primaryColor})`, 16 | primary25: `rgb(${primaryColor} / 25%)`, 17 | primary50: `rgb(${primaryColor} / 50%)`, 18 | primary75: `rgb(${primaryColor} / 75%)`, 19 | }, 20 | }); 21 | 22 | export const styleConfig = { 23 | control: (base: any, props: any) => { 24 | return { 25 | ...base, 26 | '--min-height': props.selectProps.size === 'lg' ? '48px' : '40px', 27 | minHeight: 'var(--min-height)', 28 | borderColor: `rgb(${primaryColor})`, 29 | borderWidth: 2, 30 | ":hover": { 31 | borderColor: `rgb(${primaryColor})`, 32 | }, 33 | "--tw-shadow": "0 0 #0000", 34 | "--tw-shadow-colored": "0 0 #0000", 35 | "--tw-ring-offset-shadow": 36 | "var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)", 37 | "--tw-ring-color": `rgb(${focusRingColor})`, 38 | "--tw-ring-shadow": 39 | "var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)", 40 | outline: "2px solid transparent", 41 | outlineOffset: "2px", 42 | boxShadow: props.isFocused 43 | ? "var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)" 44 | : "none", 45 | } 46 | }, 47 | }; 48 | 49 | export const classNamePrefix = "rs-"; 50 | 51 | const LoadingIndicator = (props: any) => { 52 | return loading...; 53 | }; 54 | 55 | export function ReactSelect< 56 | Option, 57 | IsMulti extends boolean = false, 58 | Group extends GroupBase