├── .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 |
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 |
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 |
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 |
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 |
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
;
53 | };
54 |
55 | export function ReactSelect<
56 | Option,
57 | IsMulti extends boolean = false,
58 | Group extends GroupBase