├── LICENSE.md ├── README.md ├── client ├── .editorconfig ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── overlay.png │ ├── robots.txt │ └── validation-key.txt ├── src │ ├── App.tsx │ ├── animations.ts │ ├── assets │ │ ├── fonts │ │ │ └── Roboto-font │ │ │ │ ├── Apache License.txt │ │ │ │ ├── Roboto-Black.ttf │ │ │ │ ├── Roboto-BlackItalic.ttf │ │ │ │ ├── Roboto-Bold.ttf │ │ │ │ ├── Roboto-BoldItalic.ttf │ │ │ │ ├── Roboto-Italic.ttf │ │ │ │ ├── Roboto-Light.ttf │ │ │ │ ├── Roboto-LightItalic.ttf │ │ │ │ ├── Roboto-Medium.ttf │ │ │ │ ├── Roboto-MediumItalic.ttf │ │ │ │ ├── Roboto-Regular.ttf │ │ │ │ ├── Roboto-Thin.ttf │ │ │ │ └── Roboto-ThinItalic.ttf │ │ ├── icons │ │ │ ├── addressIcon.svg │ │ │ ├── backgroundIcon.svg │ │ │ ├── cameraIcon.svg │ │ │ ├── checkIcon.svg │ │ │ ├── clearIcon.svg │ │ │ ├── editIcon.svg │ │ │ ├── filter.svg │ │ │ ├── index.tsx │ │ │ ├── leftArrow.svg │ │ │ ├── logOut.svg │ │ │ ├── mapIcon.svg │ │ │ ├── nameIcon.svg │ │ │ ├── nigerianFlag.svg │ │ │ ├── pailot.svg │ │ │ ├── pailotSettings.svg │ │ │ ├── phoneIcon.svg │ │ │ ├── piIcon.svg │ │ │ ├── piSymbol.svg │ │ │ ├── pictureIcon.svg │ │ │ ├── plusIcon.svg │ │ │ ├── profileAvatarIcon.svg │ │ │ ├── profileIcon.svg │ │ │ ├── scan.svg │ │ │ ├── settingIcon.svg │ │ │ ├── tick.svg │ │ │ ├── userIcon.svg │ │ │ ├── walletIcon.svg │ │ │ ├── wordIcon.svg │ │ │ └── worldIcon.svg │ │ └── images │ │ │ ├── chart.svg │ │ │ ├── defaultUser.svg │ │ │ ├── defaultUser2.svg │ │ │ ├── delivery.svg │ │ │ ├── delivery2.svg │ │ │ ├── deliveryBikeMan.svg │ │ │ ├── deliveryLady.svg │ │ │ ├── deliveryMan.svg │ │ │ ├── index.tsx │ │ │ ├── logistics1.svg │ │ │ ├── logo.svg │ │ │ ├── logo__noBg.svg │ │ │ ├── pailotPurpleLogo.svg │ │ │ ├── slideImg1.png │ │ │ ├── summaryImage.svg │ │ │ └── userImg.jpg │ ├── components │ │ ├── ActiveDelivery │ │ │ ├── ActivePayment.module.css │ │ │ ├── ActivePayment.tsx │ │ │ ├── OnlineDelivery.module.css │ │ │ └── OnlineDelivery.tsx │ │ ├── Common │ │ │ ├── AlertModal │ │ │ │ ├── AlertModal.module.css │ │ │ │ ├── index.tsx │ │ │ │ └── useAlertModal.ts │ │ │ ├── Button │ │ │ │ ├── Button.module.css │ │ │ │ └── Button.tsx │ │ │ ├── CourierCard │ │ │ │ ├── CourierCard.module.css │ │ │ │ └── CourierCard.tsx │ │ │ ├── CourierFormMods │ │ │ │ ├── CourierFormMods.module.css │ │ │ │ └── CourierFormMods.tsx │ │ │ ├── DeliveryCard │ │ │ │ ├── DeliveryCard.module.css │ │ │ │ └── DeliveryCard.tsx │ │ │ ├── FingerPrint │ │ │ │ ├── FingerPrint.module.css │ │ │ │ └── index.tsx │ │ │ ├── FooterNavBar │ │ │ │ ├── FooterNavBar.module.css │ │ │ │ └── index.tsx │ │ │ ├── Header │ │ │ │ ├── Header.module.css │ │ │ │ └── index.tsx │ │ │ └── HomePlus │ │ │ │ ├── HomePlus.module.css │ │ │ │ └── index.tsx │ │ ├── CourierDeliveryList │ │ │ ├── CourierDeliveryList.module.css │ │ │ └── CourierDeliveryList.tsx │ │ ├── CustomizedDelivery │ │ │ ├── DeliveryDetails.module.css │ │ │ ├── DeliveryDetails.tsx │ │ │ ├── DeliveryLocation.module.css │ │ │ ├── DeliveryLocation.tsx │ │ │ ├── DeliveryPayment.module.css │ │ │ ├── DeliveryPayment.tsx │ │ │ ├── DeliverySummary.module.css │ │ │ ├── DeliverySummary.tsx │ │ │ ├── DeliveryWeightSize.module.css │ │ │ ├── DeliveryWeightSize.tsx │ │ │ ├── LocationModal.module.css │ │ │ ├── LocationModal.tsx │ │ │ ├── ModeOfDelivery.module.css │ │ │ ├── ModeOfDelivery.tsx │ │ │ ├── OrderSucessful.module.css │ │ │ ├── OrderSucessful.tsx │ │ │ ├── UploadDeliveryImage.module.css │ │ │ └── UploadDeliveryImage.tsx │ │ ├── DashBoardPopups │ │ │ ├── ModPopup.module.css │ │ │ ├── ModPopup.tsx │ │ │ ├── PricingPopup.module.css │ │ │ └── PricingPopup.tsx │ │ ├── DispatchersList │ │ │ ├── DispatchersList.module.css │ │ │ └── DispatchersList.tsx │ │ ├── LoadingSpinner │ │ │ ├── LoadingSpinner.module.css │ │ │ └── LoadingSpinner.tsx │ │ ├── Settings │ │ │ ├── SettingsHome │ │ │ │ ├── SettingsHome.module.css │ │ │ │ └── index.tsx │ │ │ └── Wallet │ │ │ │ ├── Wallet.module.css │ │ │ │ ├── index.tsx │ │ │ │ └── useWallet.ts │ │ ├── SplashScreen │ │ │ ├── ScreenOne │ │ │ │ ├── ScreenOne.module.css │ │ │ │ └── index.tsx │ │ │ └── ScreenTwo │ │ │ │ ├── ScreenTwo.module.css │ │ │ │ └── index.tsx │ │ ├── WelcomeScreen │ │ │ ├── AllowPi │ │ │ │ ├── AllowPi.module.css │ │ │ │ └── index.tsx │ │ │ └── SelectLanguage │ │ │ │ ├── SelectLanguage.module.css │ │ │ │ └── index.tsx │ │ └── index.tsx │ ├── constants │ │ └── url.constants.ts │ ├── hooks │ │ └── useApi.ts │ ├── index.css │ ├── index.tsx │ ├── pages │ │ ├── ActiveDelivery │ │ │ ├── ActiveDelivery.module.css │ │ │ └── ActiveDelivery.tsx │ │ ├── CourierDashBoard │ │ │ ├── CourierDashBoard.module.css │ │ │ └── CourierDashBoard.tsx │ │ ├── CourierForm │ │ │ ├── CourierForm.module.css │ │ │ └── CourierForm.tsx │ │ ├── CustomizedDelivery │ │ │ ├── CustomizedDelivery.module.css │ │ │ └── CustomizedDelivery.tsx │ │ ├── Home │ │ │ ├── Home.module.css │ │ │ └── Home.tsx │ │ ├── OnboardingCompleted │ │ │ ├── OnboardingCompleted.module.css │ │ │ └── OnboardingCompleted.tsx │ │ ├── PrivacyPolicy │ │ │ ├── PrivacyPolicy.module.css │ │ │ └── PrivacyPolicy.tsx │ │ ├── Settings │ │ │ ├── Settings.module.css │ │ │ └── Settings.tsx │ │ ├── ShareLocation │ │ │ ├── ShareLocation.module.css │ │ │ └── ShareLocation.tsx │ │ ├── SplashScreen │ │ │ ├── SplashScreen.module.css │ │ │ └── SplashScreen.tsx │ │ ├── TermsAndConditions │ │ │ ├── TermsAndCondition.tsx │ │ │ └── TermsAndConditions.module.css │ │ ├── WelcomeScreen │ │ │ ├── Welcome.module.css │ │ │ └── Welcome.tsx │ │ └── index.tsx │ ├── react-app-env.d.ts │ ├── store │ │ └── store.ts │ ├── typedefs.d.ts │ ├── types │ │ ├── SplashScreenState.ts │ │ ├── payment.ts │ │ ├── transaction.ts │ │ └── user.ts │ └── utils │ │ ├── paymentsCallback.ts │ │ └── utils.ts ├── tsconfig.json └── yarn.lock └── server ├── .editorconfig ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── docker-compose.yaml ├── log └── .gitkeep ├── package.json ├── src ├── constants │ ├── environments.ts │ └── result.ts ├── db │ ├── dataSource.ts │ └── entity │ │ ├── Courier.ts │ │ ├── Earning.ts │ │ ├── Payout.ts │ │ ├── Transaction.ts │ │ ├── User.ts │ │ └── UserCourier.ts ├── index.ts ├── interfaces │ ├── payment.ts │ ├── result.ts │ ├── transaction.ts │ └── user.ts ├── middlewares │ ├── auth.ts │ ├── cloudinary.ts │ ├── cors.ts │ ├── generateCode.ts │ └── multer.ts ├── modules │ ├── payment │ │ ├── handlers │ │ │ ├── AccountToUserPayment.ts │ │ │ ├── approveU2APayment.ts │ │ │ ├── cancelledU2APayment.ts │ │ │ ├── completeU2APayment.ts │ │ │ └── incompleteU2APayment.ts │ │ ├── payment.controller.ts │ │ └── services │ │ │ └── payment.services.ts │ ├── router.ts │ ├── transaction │ │ ├── handlers │ │ │ ├── acceptPendingTransaction.ts │ │ │ ├── createTransaction.ts │ │ │ ├── deleteTransaction.ts │ │ │ ├── getAllPendingTransaction.ts │ │ │ ├── getAllTransactions.ts │ │ │ ├── getAllTransactionsForCourier.ts │ │ │ ├── getAllTransactionsForReceiver.ts │ │ │ ├── getAllTransactionsForSender.ts │ │ │ ├── getTransaction.ts │ │ │ ├── updateTransaction.ts │ │ │ └── updateTransactionStatus.ts │ │ ├── services │ │ │ └── transaction.services.ts │ │ └── transaction.controller.ts │ └── user │ │ ├── handlers │ │ ├── createCourier.ts │ │ ├── createUser.ts │ │ ├── deleteCourier.ts │ │ ├── deleteUser.ts │ │ ├── getAllCourierUsers.ts │ │ ├── getAllUser.ts │ │ ├── getUser.ts │ │ ├── getUserByUsername.ts │ │ ├── signInUser.ts │ │ ├── updateCourier.ts │ │ └── updateUser.ts │ │ ├── services │ │ └── user.services.ts │ │ └── user.controller.ts └── utils │ └── platformAPIClient.ts ├── tsconfig.build.json ├── tsconfig.json ├── yarn-error.log └── yarn.lock /LICENSE.md: -------------------------------------------------------------------------------- 1 | PiOS License 2 | 3 | Copyright (C) 4 | 5 | Permission is hereby granted by the application software developer (“Software Developer”), free 6 | of charge, to any person obtaining a copy of this application, software and associated 7 | documentation files (the “Software”), which was developed by the Software Developer for use on 8 | Pi Network, whereby the purpose of this license is to permit the development of derivative works 9 | based on the Software, including the right to use, copy, modify, merge, publish, distribute, 10 | sub-license, and/or sell copies of such derivative works and any Software components incorporated 11 | therein, and to permit persons to whom such derivative works are furnished to do so, in each case, 12 | solely to develop, use and market applications for the official Pi Network. For purposes of this 13 | license, Pi Network shall mean any application, software, or other present or future platform 14 | developed, owned or managed by Pi Community Company, and its parents, affiliates or subsidiaries, 15 | for which the Software was developed, or on which the Software continues to operate. However, 16 | you are prohibited from using any portion of the Software or any derivative works thereof in any 17 | manner (a) which infringes on any Pi Network intellectual property rights, (b) to hack any of Pi 18 | Network’s systems or processes or (c) to develop any product or service which is competitive with 19 | the Pi Network. 20 | 21 | The above copyright notice and this permission notice shall be included in all copies or 22 | substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 25 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 26 | AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS, PUBLISHERS, OR COPYRIGHT HOLDERS OF THIS 27 | SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL 28 | DAMAGES (INCLUDING, BUT NOT LIMITED TO BUSINESS INTERRUPTION, LOSS OF USE, DATA OR PROFITS) 29 | HOWEVER CAUSED AND UNDER ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 30 | TORT (INCLUDING NEGLIGENCE) ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 31 | OR OTHER DEALINGS IN THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 32 | 33 | Pi, Pi Network and the Pi logo are trademarks of the Pi Community Company. 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pailot App 2 | -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /client/.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_BACKEND_URL=http://localhost:3333 2 | REACT_APP_SANDBOX_SDK=true 3 | -------------------------------------------------------------------------------- /client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "env": { 4 | "browser": true, 5 | "es2021": true, 6 | "jest": true 7 | }, 8 | "extends": ["eslint:recommended", "plugin:react/recommended"], 9 | "overrides": [], 10 | "parserOptions": { 11 | "sourceType": "module" 12 | }, 13 | "plugins": ["react"], 14 | "rules": { 15 | "react/react-in-jsx-scope": "off" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/.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 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "endOfLine": "lf", 5 | "printWidth": 100, 6 | "tabWidth": 2, 7 | "useTabs": true 8 | } 9 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Pailot App Frontend 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn build` 18 | 19 | Builds the app for production to the `build` folder.\ 20 | It correctly bundles React in production mode and optimizes the build for the best performance. 21 | 22 | The build is minified and the filenames include the hashes.\ 23 | Your app is ready to be deployed! 24 | 25 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 26 | 27 | ### `yarn eject` 28 | 29 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 30 | 31 | 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. 32 | 33 | 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. 34 | 35 | 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. 36 | 37 | ## Learn More 38 | 39 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 40 | 41 | To learn React, check out the [React documentation](https://reactjs.org/). 42 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.9.3", 7 | "@testing-library/jest-dom": "^5.14.1", 8 | "@testing-library/react": "^13.0.0", 9 | "@testing-library/user-event": "^13.2.1", 10 | "@types/jest": "^27.0.1", 11 | "@types/node": "^16.7.13", 12 | "@types/react": "^18.0.0", 13 | "@types/react-dom": "^18.0.0", 14 | "axios": "^1.3.3", 15 | "framer-motion": "^8.5.3", 16 | "lodash": "^4.17.21", 17 | "react": "^18.2.0", 18 | "react-dom": "^18.2.0", 19 | "react-icons": "^4.7.1", 20 | "react-redux": "^8.0.5", 21 | "react-router-dom": "^6.7.0", 22 | "react-scripts": "5.0.1", 23 | "typescript": "^4.4.2", 24 | "web-vitals": "^2.1.0" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject", 31 | "lint": "npx eslint \"src/**/*.{js,jsx,ts,tsx,html}\" --quiet --fix", 32 | "format:check": "prettier --check .", 33 | "format:write": "prettier --write ." 34 | }, 35 | "eslintConfig": { 36 | "extends": [ 37 | "react-app", 38 | "react-app/jest" 39 | ], 40 | "rules": { 41 | "react-hooks/exhaustive-deps": [ 42 | "warn", 43 | {} 44 | ] 45 | } 46 | }, 47 | "browserslist": { 48 | "production": [ 49 | ">0.2%", 50 | "not dead", 51 | "not op_mini all" 52 | ], 53 | "development": [ 54 | "last 1 chrome version", 55 | "last 1 firefox version", 56 | "last 1 safari version" 57 | ] 58 | }, 59 | "devDependencies": { 60 | "@types/lodash": "^4.14.191", 61 | "@types/react-redux": "^7.1.25", 62 | "eslint-config-react-app": "^7.0.1", 63 | "eslint-plugin-react-hooks": "^4.6.0", 64 | "prettier": "^2.8.3" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 | 38 | 39 | 40 | 41 | 42 | 43 | 52 | Pailot App 53 | 54 | 55 | 56 |
57 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/public/logo512.png -------------------------------------------------------------------------------- /client/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 | -------------------------------------------------------------------------------- /client/public/overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/public/overlay.png -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/public/validation-key.txt: -------------------------------------------------------------------------------- 1 | 9e34d4ff11b07d6ca4aacde83cca0a8471f43f536ca761bed1ad3b8795100097f34712f2b6333602e2c0da5c22894a80e9e9c35f75437ab05cb835cfd72717f6 2 | -------------------------------------------------------------------------------- /client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Home, 4 | Settings, 5 | SplashScreen, 6 | WelcomeScreen, 7 | // ShareLocation, 8 | OnboardingCompleted, 9 | CustomizedDelivery, 10 | ActiveDelivery, 11 | CourierForm, 12 | CourierDashBoard, 13 | PrivacyPolicy, 14 | TermsAndConditions, 15 | } from './pages'; 16 | import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; 17 | import { motion, AnimatePresence } from 'framer-motion'; 18 | import { fadeInFast } from './animations'; 19 | 20 | function App() { 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | } /> 28 | } /> 29 | } /> 30 | } /> 31 | {/* } /> */} 32 | } /> 33 | } /> 34 | } /> 35 | } /> 36 | } /> 37 | } /> 38 | } /> 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | } 46 | 47 | export default App; 48 | -------------------------------------------------------------------------------- /client/src/animations.ts: -------------------------------------------------------------------------------- 1 | export const scaleRotate = { 2 | scale: [10, 1, 1, 1, 20], 3 | rotate: [0, 0, 270, 270, 0], 4 | borderRadius: ['20%', '20%', '50%', '50%', '50%'], 5 | transition: { 6 | scale: { duration: 12 }, 7 | rotate: { duration: 4 }, 8 | borderRadius: { duration: 8 }, 9 | }, 10 | }; 11 | 12 | export const scale = (delay: number, duration: number) => { 13 | const animateScale = { 14 | scale: [0, 1.5, 1], 15 | transition: { 16 | scale: { duration, delay, ease: 'easeInOut' }, 17 | }, 18 | }; 19 | return animateScale; 20 | }; 21 | 22 | export const fadeIn = { 23 | opacity: [0, 0, 1, 1, 0], 24 | transition: { 25 | opacity: { duration: 7 }, 26 | }, 27 | }; 28 | 29 | export const fadeInFast = { 30 | opacity: [0, 1], 31 | transition: { 32 | opacity: { duration: 1, exit: 0 }, 33 | }, 34 | }; 35 | 36 | export const slideUp = { 37 | y: [100, 0], 38 | transition: { 39 | y: { duration: 0.3 }, 40 | }, 41 | }; 42 | 43 | export const slideLeft = { 44 | x: ['0%', '100%'], 45 | opacity: [1, 0], 46 | exit: { 47 | x: ['-100%'], 48 | opacity: [0], 49 | }, 50 | transition: { 51 | exit: { duration: 2 }, 52 | x: { duration: 2 }, 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-font/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/fonts/Roboto-font/Roboto-Black.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-font/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/fonts/Roboto-font/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-font/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/fonts/Roboto-font/Roboto-Bold.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-font/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/fonts/Roboto-font/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-font/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/fonts/Roboto-font/Roboto-Italic.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-font/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/fonts/Roboto-font/Roboto-Light.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-font/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/fonts/Roboto-font/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-font/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/fonts/Roboto-font/Roboto-Medium.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-font/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/fonts/Roboto-font/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-font/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/fonts/Roboto-font/Roboto-Regular.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-font/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/fonts/Roboto-font/Roboto-Thin.ttf -------------------------------------------------------------------------------- /client/src/assets/fonts/Roboto-font/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/fonts/Roboto-font/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /client/src/assets/icons/addressIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/backgroundIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/cameraIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/checkIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/src/assets/icons/clearIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/editIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/filter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as WorldLogo } from './wordIcon.svg'; 2 | export { default as MapIcon } from './mapIcon.svg'; 3 | export { default as Pailot } from './pailot.svg'; 4 | export { default as Scan } from './scan.svg'; 5 | export { default as LeftArrow } from './leftArrow.svg'; 6 | export { default as LogOut } from './logOut.svg'; 7 | export { default as ProfileIcon } from './profileIcon.svg'; 8 | export { default as AddressIcon } from './addressIcon.svg'; 9 | export { default as BackgroundIcon } from './backgroundIcon.svg'; 10 | export { default as EditIcon } from './editIcon.svg'; 11 | export { default as NameIcon } from './nameIcon.svg'; 12 | export { default as PhoneIcon } from './phoneIcon.svg'; 13 | export { default as PictureIcon } from './pictureIcon.svg'; 14 | export { default as PiIcon } from './piIcon.svg'; 15 | export { default as PlusIcon } from './plusIcon.svg'; 16 | export { default as ProfileAvatarIcon } from './profileAvatarIcon.svg'; 17 | export { default as SettingIcon } from './settingIcon.svg'; 18 | export { default as UserIcon } from './userIcon.svg'; 19 | export { default as WalletIcon } from './walletIcon.svg'; 20 | export { default as CameraIcon } from './cameraIcon.svg'; 21 | export { default as ClearIcon } from './clearIcon.svg'; 22 | export { default as CheckIcon } from './checkIcon.svg'; 23 | export { default as piSymbol } from './piSymbol.svg'; 24 | export { default as filter } from './filter.svg'; 25 | export { default as nigerianFlag } from './nigerianFlag.svg'; 26 | export { default as pailotSettings } from './pailotSettings.svg'; 27 | export { RiFingerprintFill, RiCalendarTodoLine, RiLogoutCircleRLine } from 'react-icons/ri'; 28 | export { GrFormClose, GrStatusWarning } from 'react-icons/gr'; 29 | export { BiError, BiErrorAlt } from 'react-icons/bi'; 30 | export { BsPentagon, BsCheckLg, BsInfoLg } from 'react-icons/bs'; 31 | export { AiOutlineShoppingCart, AiOutlineWallet, AiOutlineArrowLeft } from 'react-icons/ai'; 32 | export { MdClose } from 'react-icons/md'; 33 | export { default as WorldIcon } from './worldIcon.svg'; 34 | export { IoMdArrowRoundBack } from 'react-icons/io'; 35 | -------------------------------------------------------------------------------- /client/src/assets/icons/leftArrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/logOut.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/mapIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /client/src/assets/icons/nameIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/nigerianFlag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /client/src/assets/icons/pailot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/pailotSettings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/src/assets/icons/phoneIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/piSymbol.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/pictureIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/plusIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/profileAvatarIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/profileIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/scan.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/settingIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/tick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/userIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/walletIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/icons/wordIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/images/chart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/images/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as logo } from './logo.svg'; 2 | export { default as userImage } from './userImg.jpg'; 3 | export { default as sliderImg1 } from './slideImg1.png'; 4 | export { default as delivery } from './delivery.svg'; 5 | export { default as delivery2 } from './delivery2.svg'; 6 | export { default as logistics1 } from './logistics1.svg'; 7 | export { default as deliveryLady } from './deliveryLady.svg'; 8 | export { default as deliveryMan } from './deliveryMan.svg'; 9 | export { default as deliveryBikeMan } from './deliveryBikeMan.svg'; 10 | export { default as pailotPurpleLogo } from './pailotPurpleLogo.svg'; 11 | export { default as logo__noBg } from './logo__noBg.svg'; 12 | export { default as summaryImage } from './summaryImage.svg'; 13 | export { default as defaultUser } from './defaultUser.svg'; 14 | export { default as defaultUser2 } from './defaultUser2.svg'; 15 | export { default as chart } from './chart.svg'; 16 | -------------------------------------------------------------------------------- /client/src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/src/assets/images/logo__noBg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/src/assets/images/pailotPurpleLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/src/assets/images/slideImg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/images/slideImg1.png -------------------------------------------------------------------------------- /client/src/assets/images/userImg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/assets/images/userImg.jpg -------------------------------------------------------------------------------- /client/src/components/ActiveDelivery/ActivePayment.tsx: -------------------------------------------------------------------------------- 1 | import styles from './ActivePayment.module.css'; 2 | import React from 'react'; 3 | import { IoMdArrowRoundBack } from 'react-icons/io'; 4 | import { motion } from 'framer-motion'; 5 | import { defaultUser } from '../../assets/images'; 6 | import { useSelector } from 'react-redux'; 7 | import { RootState } from '../../store/store'; 8 | 9 | interface Props { 10 | // eslint-disable-next-line no-unused-vars 11 | setProgress: (value: number) => void; 12 | uploadedImage: File | undefined; 13 | } 14 | 15 | export const ActivePayment: React.FC = ({ setProgress }) => { 16 | const deliveryDetails = useSelector((state: RootState) => state.deliveryDetails.deliveryDetails); 17 | return ( 18 |
19 |
20 |
21 | { 24 | setProgress(6); 25 | }} 26 | /> 27 |
28 | Payment 29 |
30 |
31 |

32 | Final stop! Tell Pailot how much Pi you are willing to pay 33 |

34 |

Pay with Pi

35 |
36 | Couriers Profile picture 37 | 38 | {deliveryDetails.courierDetails.user.username}{' '} 39 | 40 | {deliveryDetails.courierDetails.newUser && ( 41 | New user 42 | )} 43 | {deliveryDetails.courierDetails.status === 'pending' && ( 44 | Pending 45 | )} 46 | {deliveryDetails.courierDetails.status === 'pick' && ( 47 | Picked 48 | )} 49 |
50 |
51 | 60 |

Amount

61 | 0.021 62 |
63 | Free fees on Pailot *TBD 64 |
65 |
66 |
67 |
68 |

Platform fee:

69 | 0.001 70 |
71 |
72 |
73 |

Total

74 | 0.004 75 |
76 |
77 |
78 | 87 | 96 | 97 |
98 | ); 99 | }; 100 | -------------------------------------------------------------------------------- /client/src/components/ActiveDelivery/OnlineDelivery.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | } 6 | 7 | .top__bar { 8 | width: 100%; 9 | display: flex; 10 | flex: 0; 11 | align-items: center; 12 | justify-content: space-evenly; 13 | padding: 17px 24px; 14 | background-color: var(--light-purple); 15 | } 16 | 17 | .top__bar > div:first-child { 18 | justify-self: flex-start; 19 | } 20 | .top__bar > span:nth-child(2) { 21 | flex: 1; 22 | text-align: center; 23 | } 24 | 25 | .progress { 26 | display: flex; 27 | align-items: center; 28 | justify-content: center; 29 | gap: 2px; 30 | } 31 | 32 | .inactive__progress { 33 | height: 3px; 34 | width: 5px; 35 | background-color: var(--purple); 36 | border-radius: 4px; 37 | } 38 | 39 | .active__progress { 40 | height: 3px; 41 | width: 21px; 42 | background-color: var(--orange); 43 | border-radius: 4px; 44 | } 45 | 46 | .body { 47 | padding: 1rem; 48 | display: flex; 49 | flex-direction: column; 50 | gap: 1rem; 51 | flex: 1; 52 | padding-bottom: 24px; 53 | overflow: scroll; 54 | } 55 | 56 | .search__container { 57 | width: 100%; 58 | display: flex; 59 | align-items: center; 60 | gap: 1rem; 61 | } 62 | 63 | .search { 64 | display: flex; 65 | align-items: center; 66 | gap: 0.5rem; 67 | flex: 1; 68 | color: var(--black); 69 | background-color: rgba(240, 240, 240, 1); 70 | padding: 0.5rem 1rem; 71 | border-radius: 100px; 72 | } 73 | 74 | .search__icon { 75 | opacity: 0.5; 76 | } 77 | 78 | .search > .input { 79 | font-size: 8px; 80 | border: none; 81 | outline: none; 82 | width: 100%; 83 | background-color: transparent; 84 | } 85 | 86 | .header { 87 | display: flex; 88 | align-items: center; 89 | justify-content: space-between; 90 | } 91 | 92 | .header h3 { 93 | font-size: 14px; 94 | } 95 | 96 | .header > p { 97 | font-size: 12px; 98 | display: flex; 99 | align-items: center; 100 | gap: 0.3rem; 101 | } 102 | -------------------------------------------------------------------------------- /client/src/components/Common/AlertModal/AlertModal.module.css: -------------------------------------------------------------------------------- 1 | .modalOverlay { 2 | position: absolute; 3 | background-color: rgba(0, 0, 0, 0.268); 4 | height: 100vh; 5 | width: 100vw; 6 | top: 0; 7 | display: flex; 8 | align-items: center; 9 | justify-content: center; 10 | } 11 | 12 | .modalContainer { 13 | background-color: #fff; 14 | margin: 10%; 15 | width: 100%; 16 | max-width: 70%; 17 | border-radius: 5px; 18 | box-shadow: rgba(0, 0, 0, 0.2) 0px 12px 28px 0px, rgba(0, 0, 0, 0.1) 0px 2px 4px 0px, 19 | rgba(255, 255, 255, 0.05) 0px 0px 0px 1px inset; 20 | position: relative; 21 | } 22 | 23 | .modalInnerContainer { 24 | display: flex; 25 | align-items: center; 26 | flex-direction: column; 27 | justify-content: center; 28 | gap: 10px; 29 | padding: 5%; 30 | } 31 | 32 | .messages { 33 | margin: 3% 0; 34 | text-align: center; 35 | color: rgb(150, 147, 147); 36 | } 37 | 38 | .modalInnerContainer p, 39 | .modalInnerContainer h3 { 40 | margin: 0; 41 | } 42 | 43 | .alertIconBg1 { 44 | color: #fff; 45 | padding: 10px; 46 | display: flex; 47 | align-items: center; 48 | justify-content: center; 49 | border-radius: 50px; 50 | } 51 | 52 | .alertIconBg2 { 53 | padding: 10px; 54 | height: 50px; 55 | width: 50px; 56 | border-radius: 50px; 57 | display: flex; 58 | align-items: center; 59 | justify-content: center; 60 | } 61 | 62 | .alertIconBg3 { 63 | border: 2px solid white; 64 | padding: 5px; 65 | width: 25px; 66 | height: 25px; 67 | border-radius: 50px; 68 | display: flex; 69 | align-items: center; 70 | justify-content: center; 71 | } 72 | 73 | .successAlert1 { 74 | background-color: #91e499; 75 | } 76 | .successAlert2 { 77 | background-color: #14944e; 78 | } 79 | 80 | .infoAlert1 { 81 | background-color: #9298db; 82 | } 83 | 84 | .infoAlert2 { 85 | background-color: #222eb7; 86 | } 87 | 88 | .warningAlert1 { 89 | background-color: #e7e7d5; 90 | } 91 | .warningAlert2 { 92 | background-color: #ffff00; 93 | } 94 | 95 | .errorAlert1 { 96 | background-color: #f4b0b0; 97 | } 98 | 99 | .errorAlert2 { 100 | background-color: #ff0000; 101 | } 102 | .progress_bar { 103 | background-color: #304496a0; 104 | height: 4px; 105 | width: 0%; 106 | border-bottom-right-radius: 5px; 107 | border-bottom-left-radius: 5px; 108 | } 109 | 110 | .modalClose { 111 | font-size: 30px; 112 | position: absolute; 113 | right: 0; 114 | padding: 5%; 115 | color: rgb(137, 135, 135); 116 | } 117 | -------------------------------------------------------------------------------- /client/src/components/Common/AlertModal/useAlertModal.ts: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | 3 | function useAlertModal( 4 | setCloseModal: React.Dispatch>, 5 | alertType: string 6 | ) { 7 | const [convertedAlertType, setConvertedAlertType] = useState(alertType); 8 | const handleClose = () => { 9 | setCloseModal(false); 10 | }; 11 | useEffect(() => { 12 | setConvertedAlertType( 13 | convertedAlertType.charAt(0).toUpperCase() + convertedAlertType.slice(1).toLowerCase() 14 | ); 15 | }, [convertedAlertType]); 16 | return { handleClose, convertedAlertType }; 17 | } 18 | 19 | export default useAlertModal; 20 | -------------------------------------------------------------------------------- /client/src/components/Common/Button/Button.module.css: -------------------------------------------------------------------------------- 1 | .button { 2 | background-color: #30007e; 3 | color: #ff9927; 4 | border: none; 5 | padding: 10px; 6 | font-size: 12px; 7 | border-radius: 4px; 8 | min-height: 21px; 9 | min-width: 63px; 10 | } 11 | -------------------------------------------------------------------------------- /client/src/components/Common/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './Button.module.css'; 3 | 4 | interface Props { 5 | value: string; 6 | } 7 | export const Button = ({ value }: Props) => { 8 | return ; 9 | }; 10 | -------------------------------------------------------------------------------- /client/src/components/Common/CourierCard/CourierCard.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | align-items: flex-start; 4 | gap: 0.5rem; 5 | padding: 0.5rem; 6 | padding-right: 0.5rem; 7 | border-radius: 4px; 8 | background-color: rgba(0, 0, 0, 0.05); 9 | } 10 | 11 | .picked__container { 12 | background-color: var(--light-purple); 13 | } 14 | 15 | .courier__photo__container { 16 | display: grid; 17 | place-content: center; 18 | position: relative; 19 | } 20 | 21 | .courier__photo__container > img { 22 | height: 24px; 23 | } 24 | 25 | .courier__photo__container > .online { 26 | position: absolute; 27 | top: 1px; 28 | right: 1px; 29 | height: 5px; 30 | width: 5px; 31 | border-radius: 100px; 32 | background-color: rgba(2, 205, 22, 1); 33 | } 34 | .courier__photo__container > .offline { 35 | position: absolute; 36 | top: 1px; 37 | right: 1px; 38 | height: 5px; 39 | width: 5px; 40 | border-radius: 100px; 41 | background-color: rgba(126, 125, 125, 1); 42 | } 43 | 44 | .content { 45 | width: 100%; 46 | display: flex; 47 | flex-direction: column; 48 | gap: 0.5rem; 49 | } 50 | 51 | .top__part { 52 | width: 100%; 53 | display: flex; 54 | align-items: center; 55 | justify-content: space-between; 56 | } 57 | 58 | .courier__details { 59 | flex: 1; 60 | display: flex; 61 | align-items: center; 62 | gap: 0.5em; 63 | } 64 | 65 | .username { 66 | font-size: 12px; 67 | } 68 | 69 | .new__user { 70 | font-size: 8px; 71 | color: var(--purple); 72 | font-weight: 700; 73 | background-color: rgba(229, 212, 255, 1); 74 | border-radius: 4px; 75 | padding: 2.5px 5px; 76 | } 77 | 78 | .cta__container { 79 | display: flex; 80 | align-items: center; 81 | gap: 0.5rem; 82 | } 83 | 84 | .pending__cta { 85 | width: 54px; 86 | font-size: 8px; 87 | padding: 5px 10px; 88 | background-color: var(--orange); 89 | border: none; 90 | outline: none; 91 | border-radius: 4px; 92 | letter-spacing: 0.4375px; 93 | font-weight: 700; 94 | } 95 | .picked__cta { 96 | width: 54px; 97 | font-size: 8px; 98 | padding: 5px 10px; 99 | background-color: rgba(2, 205, 22, 1); 100 | border: none; 101 | outline: none; 102 | border-radius: 4px; 103 | letter-spacing: 0.4375px; 104 | font-weight: 700; 105 | color: var(--white); 106 | } 107 | .pick__cta { 108 | width: 54px; 109 | font-size: 8px; 110 | padding: 5px 10px; 111 | background-color: var(--purple); 112 | color: var(--white); 113 | border: none; 114 | outline: none; 115 | border-radius: 4px; 116 | letter-spacing: 0.4375px; 117 | font-weight: 700; 118 | } 119 | 120 | .bottom__part { 121 | display: flex; 122 | align-items: center; 123 | gap: 2rem; 124 | font-size: 8px; 125 | } 126 | 127 | .amount, 128 | .time, 129 | .vehicle { 130 | display: flex; 131 | align-items: center; 132 | gap: 0.5rem; 133 | } 134 | 135 | .icon { 136 | font-size: 12px; 137 | } 138 | -------------------------------------------------------------------------------- /client/src/components/Common/CourierFormMods/CourierFormMods.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 100vw; 6 | height: 100vh; 7 | position: absolute; 8 | top: 0px; 9 | left: 0px; 10 | z-index: 100; 11 | } 12 | 13 | .modal { 14 | background-color: var(--white); 15 | z-index: 120; 16 | width: 70vw; 17 | aspect-ratio: 1; 18 | display: grid; 19 | grid-template-columns: 1fr 1fr; 20 | gap: 5vw; 21 | padding: 5vw; 22 | border-radius: 12px; 23 | } 24 | 25 | .label { 26 | display: flex; 27 | align-items: center; 28 | justify-content: center; 29 | position: relative; 30 | gap: 0.5rem; 31 | font-size: clamp(1rem, 2.5vw, 4rem); 32 | border-radius: 6px; 33 | color: var(--black); 34 | cursor: pointer; 35 | -webkit-box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.1); 36 | -moz-box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.1); 37 | box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.1); 38 | transition: all 250ms ease-in-out; 39 | } 40 | 41 | .input { 42 | position: absolute; 43 | opacity: 0; 44 | } 45 | 46 | .active__label { 47 | background-color: var(--purple); 48 | color: var(--white); 49 | } 50 | 51 | .backdrop { 52 | inset: 0; 53 | background-color: #000; 54 | opacity: 0.7; 55 | position: absolute; 56 | } 57 | -------------------------------------------------------------------------------- /client/src/components/Common/DeliveryCard/DeliveryCard.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | width: 100%; 4 | flex-direction: column; 5 | border-radius: 4px; 6 | background-color: rgba(0, 0, 0, 0.03); 7 | } 8 | 9 | .accepted { 10 | background-color: var(--light-purple); 11 | } 12 | 13 | .img__and__details { 14 | padding: 0.5rem; 15 | display: flex; 16 | gap: 1rem; 17 | align-items: flex-start; 18 | } 19 | 20 | .img__and__details img { 21 | width: 27px; 22 | aspect-ratio: 1; 23 | } 24 | 25 | .delivery__details { 26 | flex: 1; 27 | display: flex; 28 | flex-direction: column; 29 | } 30 | 31 | .top__line { 32 | display: flex; 33 | width: 100%; 34 | align-items: center; 35 | justify-content: space-between; 36 | } 37 | 38 | .sender__details { 39 | display: flex; 40 | align-items: center; 41 | gap: 0.5rem; 42 | } 43 | 44 | .username { 45 | font-size: 12px; 46 | } 47 | 48 | .new__user { 49 | font-size: 8px; 50 | padding: 0.3rem; 51 | color: var(--purple); 52 | font-weight: 600; 53 | border-radius: 4px; 54 | background-color: rgba(229, 212, 255, 1); 55 | } 56 | 57 | .amount { 58 | display: flex; 59 | align-items: center; 60 | gap: 0.5rem; 61 | font-size: 12px; 62 | } 63 | 64 | .bottom__line { 65 | width: 100%; 66 | display: flex; 67 | align-items: center; 68 | justify-content: space-between; 69 | } 70 | 71 | .time__and__category { 72 | font-size: 8px; 73 | display: flex; 74 | align-items: center; 75 | gap: 1rem; 76 | } 77 | 78 | .time, 79 | .category { 80 | display: flex; 81 | align-items: center; 82 | gap: 0.5rem; 83 | } 84 | 85 | .time__icon, 86 | .basket__icon { 87 | font-size: 12px; 88 | } 89 | 90 | .cta__accept { 91 | min-width: 54px; 92 | display: grid; 93 | place-items: center; 94 | padding: 0.5rem; 95 | border-radius: 4px; 96 | font-size: 8px; 97 | border: none; 98 | background-color: rgba(163, 163, 163, 1); 99 | font-weight: 600; 100 | } 101 | 102 | .cta__accepted { 103 | min-width: 54px; 104 | display: grid; 105 | place-items: center; 106 | padding: 0.5rem; 107 | border-radius: 4px; 108 | font-size: 8px; 109 | border: none; 110 | background-color: var(--green); 111 | color: var(--white); 112 | font-weight: 600; 113 | } 114 | 115 | .modes__of__delivery { 116 | display: flex; 117 | align-items: center; 118 | gap: 1rem; 119 | border-top: 1px dashed var(--grey); 120 | padding: 0.5rem; 121 | } 122 | 123 | .mod { 124 | display: flex; 125 | align-items: center; 126 | gap: 0.5rem; 127 | font-size: 8px; 128 | } 129 | 130 | .mod__icon { 131 | font-size: 12px; 132 | } 133 | -------------------------------------------------------------------------------- /client/src/components/Common/FingerPrint/FingerPrint.module.css: -------------------------------------------------------------------------------- 1 | .finger_print_wrapper { 2 | background-color: #e2e8ff; 3 | border-radius: 16px 18px 0px 0px; 4 | height: 50vh; 5 | width: 100%; 6 | position: absolute; 7 | bottom: 0; 8 | text-align: center; 9 | box-sizing: border-box; 10 | padding: 5%; 11 | } 12 | .finger_print_wrapper h3 { 13 | color: var(--purple); 14 | font-size: 5vw; 15 | margin: 2% 0; 16 | } 17 | 18 | .finger_print_wrapper > p { 19 | font-size: 3vw; 20 | } 21 | 22 | .finger_print_wrapper h4 { 23 | margin-top: 15%; 24 | font-size: 4vw; 25 | font-weight: 400; 26 | } 27 | 28 | .closeWrapper { 29 | width: 100%; 30 | text-align: right; 31 | } 32 | .closeIcon { 33 | font-size: x-large; 34 | } 35 | 36 | .fingerPrintIcon { 37 | width: 19vw; 38 | height: 19vw; 39 | color: var(--black); 40 | margin: 2% 0; 41 | } 42 | 43 | .error { 44 | display: flex; 45 | color: red; 46 | align-items: center; 47 | font-size: 12px; 48 | justify-content: center; 49 | } 50 | 51 | .errorIcon { 52 | font-size: 2rem; 53 | } 54 | -------------------------------------------------------------------------------- /client/src/components/Common/FingerPrint/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './FingerPrint.module.css'; 3 | import { RiFingerprintFill, GrFormClose, BiError } from '../../../assets/icons'; 4 | 5 | type Props = { 6 | setCloseFingerPrint: React.Dispatch>; 7 | }; 8 | 9 | export const FingerPrint = ({ setCloseFingerPrint }: Props) => { 10 | const handleClose = () => { 11 | setCloseFingerPrint(false); 12 | }; 13 | return ( 14 |
15 |
16 | 17 |
18 | 19 |

Device Protection

20 |

21 | We want to make sure your account is protected by confirming your device fingerprints, 22 | patterns, or pincode 23 |

24 |

Scan your fingerprint

25 | 26 |
27 | 28 | Try again! Place your finger properly in the sensor 29 |
30 |
31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /client/src/components/Common/FooterNavBar/FooterNavBar.module.css: -------------------------------------------------------------------------------- 1 | .footer__nav { 2 | background-color: rgba(103, 80, 164, 0.08); 3 | padding: 3.4px 7px; 4 | width: 100%; 5 | } 6 | 7 | .list { 8 | display: flex; 9 | justify-content: space-around; 10 | gap: 7px; 11 | } 12 | 13 | .list__item { 14 | cursor: pointer; 15 | 16 | display: flex; 17 | flex-direction: column; 18 | align-items: center; 19 | gap: 1rem; 20 | padding-block: 16px; 21 | width: 79.5px; 22 | color: var(--black); 23 | opacity: 0.75; 24 | } 25 | 26 | .list__item img { 27 | height: 13.2px; 28 | width: 18px; 29 | } 30 | 31 | .list__item span { 32 | font-size: 10px; 33 | width: 100%; 34 | text-align: center; 35 | } 36 | 37 | .active__item { 38 | cursor: pointer; 39 | 40 | display: flex; 41 | flex-direction: column; 42 | align-items: center; 43 | gap: 1rem; 44 | padding-block: 16px; 45 | color: var(--purple); 46 | width: 79.5px; 47 | } 48 | 49 | .active__item img { 50 | height: 13.2px; 51 | width: 18px; 52 | } 53 | 54 | .active__item span { 55 | font-size: 10px; 56 | font-weight: 700; 57 | width: 100%; 58 | text-align: center; 59 | } 60 | -------------------------------------------------------------------------------- /client/src/components/Common/FooterNavBar/index.tsx: -------------------------------------------------------------------------------- 1 | import styles from './FooterNavBar.module.css'; 2 | import { pailotPurpleLogo } from '../../../assets/images'; 3 | import { RiShoppingCart2Line } from 'react-icons/ri'; 4 | import { RiWallet2Line } from 'react-icons/ri'; 5 | import { RiQrScan2Line } from 'react-icons/ri'; 6 | import { useNavigate } from 'react-router-dom'; 7 | import React from 'react'; 8 | import { pailotSettings } from '../../../assets/icons'; 9 | 10 | interface Props { 11 | active: string; 12 | } 13 | 14 | export const FooterNavBar: React.FC = ({ active }) => { 15 | const navigate = useNavigate(); 16 | return ( 17 | 47 | ); 48 | }; 49 | -------------------------------------------------------------------------------- /client/src/components/Common/Header/Header.module.css: -------------------------------------------------------------------------------- 1 | #wrapper { 2 | display: flex; 3 | color: var(--white); 4 | justify-content: space-between; 5 | background: var(--purple); 6 | width: 100%; 7 | /* height: 40px; */ 8 | padding: 1rem; 9 | /* padding: 3% 6%; */ 10 | box-sizing: border-box; 11 | align-items: center; 12 | /* position: fixed; */ 13 | z-index: 1; 14 | } 15 | #wrapper > div { 16 | display: flex; 17 | align-items: center; 18 | gap: 10px; 19 | } 20 | 21 | #wrapper > div > div { 22 | display: flex; 23 | align-items: center; 24 | } 25 | 26 | .image_wrapper { 27 | border-radius: 50%; 28 | } 29 | 30 | .title { 31 | font-size: 14px; 32 | } 33 | 34 | .image_size { 35 | width: 14px; 36 | height: 13.61px; 37 | border-radius: 50%; 38 | } 39 | 40 | .right__icon { 41 | height: 15px; 42 | } 43 | -------------------------------------------------------------------------------- /client/src/components/Common/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | 4 | import styles from './Header.module.css'; 5 | interface Props { 6 | left_icon: string; 7 | right_icon: string; 8 | left_route_location: string; 9 | right_route_location: string; 10 | title: string; 11 | } 12 | 13 | export const Header = ({ 14 | left_icon, 15 | right_icon, 16 | left_route_location, 17 | right_route_location, 18 | title, 19 | }: Props) => { 20 | const navigate = useNavigate(); 21 | 22 | const handleNavigation = () => { 23 | if (title === 'Settings') { 24 | sessionStorage.removeItem('token'); 25 | sessionStorage.removeItem('user'); 26 | navigate('/welcome'); 27 | } else { 28 | navigate(right_route_location); 29 | } 30 | } 31 | return ( 32 |
33 |
34 |
navigate(left_route_location)}> 35 | left icon 36 |
37 | {title} 38 |
39 |
40 | right icon 46 |
47 |
48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /client/src/components/Common/HomePlus/HomePlus.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | /* position: relative; */ 3 | z-index: 1; 4 | } 5 | 6 | .icon__container { 7 | /* position: relative; */ 8 | aspect-ratio: 1; 9 | width: 38px; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | background-color: rgba(226, 238, 252, 1); 14 | border-radius: 100%; 15 | font-weight: 800; 16 | font-size: medium; 17 | transition: scale 250ms ease-in-out, rotate 250ms ease-in-out; 18 | } 19 | 20 | .active { 21 | /* position: relative; */ 22 | aspect-ratio: 1; 23 | width: 38px; 24 | display: flex; 25 | justify-content: center; 26 | align-items: center; 27 | background-color: rgba(226, 238, 252, 1); 28 | border-radius: 100%; 29 | font-weight: 800; 30 | font-size: medium; 31 | rotate: 45deg; 32 | position: relative; 33 | z-index: 10; 34 | scale: 1.3; 35 | transition: scale 250ms ease-in-out, rotate 250ms ease-in-out; 36 | } 37 | 38 | .backdrop { 39 | position: absolute; 40 | background-color: var(--black); 41 | opacity: 0.68; 42 | /* height: 50vh; 43 | width: 50vw; */ 44 | top: 0vh; 45 | inset: 0; 46 | z-index: 5; 47 | /* pointer-events: none; */ 48 | } 49 | 50 | .cta__container { 51 | padding: 1rem; 52 | position: absolute; 53 | background-color: rgba(243, 237, 247, 1); 54 | rotate: -45deg; 55 | right: 3.4rem; 56 | top: -3.7rem; 57 | display: flex; 58 | flex-direction: column; 59 | gap: 1rem; 60 | height: 7.75rem; 61 | border-radius: 4px; 62 | } 63 | 64 | .cta__container::after { 65 | content: ''; 66 | position: absolute; 67 | height: 15px; 68 | width: 15px; 69 | background-color: rgba(243, 237, 247, 1); 70 | bottom: -5.5px; 71 | right: 0.5rem; 72 | rotate: 45deg; 73 | } 74 | 75 | .cta { 76 | display: flex; 77 | align-items: center; 78 | gap: 0.5rem; 79 | font-size: 12px; 80 | width: 170px; 81 | padding: 10px 15px; 82 | border-radius: 4px; 83 | background-color: var(--purple); 84 | color: var(--light-purple); 85 | border: none; 86 | } 87 | 88 | .cta > span { 89 | display: grid; 90 | place-content: center; 91 | } 92 | 93 | .cta > span:first-child { 94 | font-size: large; 95 | } 96 | -------------------------------------------------------------------------------- /client/src/components/Common/HomePlus/index.tsx: -------------------------------------------------------------------------------- 1 | import styles from './HomePlus.module.css'; 2 | import { BsPlusLg } from 'react-icons/bs'; 3 | import { useState } from 'react'; 4 | import { AnimatePresence, motion } from 'framer-motion'; 5 | import { HiOutlinePlusCircle } from 'react-icons/hi'; 6 | import { useNavigate } from 'react-router-dom'; 7 | import { useDispatch } from 'react-redux'; 8 | import { deliveryTypeActions } from '../../../store/store'; 9 | 10 | export const HomePlus = () => { 11 | const [active, setActive] = useState(false); 12 | const navigate = useNavigate(); 13 | const dispatch = useDispatch(); 14 | return ( 15 | 22 |
{ 25 | setActive(!active); 26 | }} 27 | > 28 | {active && ( 29 | 38 | { 51 | // navigate('/active-delivery'); 52 | // sessionStorage.setItem('hasMadeFirstDelivery', 'true'); 53 | // dispatch(deliveryTypeActions.setDeliveryType('active')); 54 | // }} 55 | > 56 | 57 | 58 | 59 | Online Couriers 60 | 61 | { 74 | navigate('/customized-delivery'); 75 | sessionStorage.setItem('hasMadeFirstDelivery', 'true'); 76 | dispatch(deliveryTypeActions.setDeliveryType('customized')); 77 | }} 78 | > 79 | 80 | 81 | 82 | Customized Delivery 83 | 84 | 85 | )} 86 | 87 |
88 | 89 | {active && ( 90 | { 96 | setActive(!active); 97 | }} 98 | > 99 | )} 100 | 101 |
102 | ); 103 | }; 104 | -------------------------------------------------------------------------------- /client/src/components/CourierDeliveryList/CourierDeliveryList.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .location { 7 | font-size: 12px; 8 | display: flex; 9 | align-items: center; 10 | gap: 0.5rem; 11 | } 12 | 13 | .delivery__top__bar { 14 | font-size: 12px; 15 | display: flex; 16 | justify-content: space-between; 17 | align-items: center; 18 | margin-block: 0.5rem; 19 | } 20 | 21 | .customized__delivery { 22 | padding: 0.5rem; 23 | color: var(--white); 24 | background-color: var(--purple); 25 | position: relative; 26 | border-radius: 4px; 27 | } 28 | 29 | .customized__delivery__count { 30 | font-size: 10px; 31 | width: 19px; 32 | height: 19px; 33 | display: grid; 34 | place-content: center; 35 | position: absolute; 36 | left: 100%; 37 | top: 0px; 38 | color: var(--white); 39 | background-color: rgba(227, 30, 34, 1); 40 | transform: translate(-50%, -50%); 41 | border-radius: 100px; 42 | } 43 | 44 | .delivery__preference { 45 | font-weight: 500; 46 | } 47 | 48 | .body { 49 | display: flex; 50 | flex-direction: column; 51 | gap: 1rem; 52 | } 53 | 54 | .see__all { 55 | width: 100%; 56 | text-align: center; 57 | font-size: 10px; 58 | cursor: pointer; 59 | } 60 | 61 | .interstate__orders { 62 | display: flex; 63 | align-items: center; 64 | justify-content: space-between; 65 | } 66 | 67 | .interstate__orders > p { 68 | font-size: 14px; 69 | font-weight: 600; 70 | display: flex; 71 | gap: 0.5em; 72 | align-items: center; 73 | } 74 | 75 | .interstate__orders > p > span { 76 | font-size: 10px; 77 | width: 19px; 78 | height: 19px; 79 | display: grid; 80 | place-content: center; 81 | border-radius: 100px; 82 | color: var(--white); 83 | background-color: rgba(227, 30, 34, 1); 84 | margin-block: 1rem; 85 | } 86 | -------------------------------------------------------------------------------- /client/src/components/CustomizedDelivery/LocationModal.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: absolute; 3 | inset: 0; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | } 8 | 9 | .top__bar { 10 | display: flex; 11 | align-items: center; 12 | width: 100%; 13 | justify-content: flex-end; 14 | font-weight: 700; 15 | font-size: larger; 16 | } 17 | 18 | .modal { 19 | background-color: var(--white); 20 | display: flex; 21 | flex-direction: column; 22 | align-items: center; 23 | padding: 2rem; 24 | margin-inline: 2rem; 25 | border-radius: 6px; 26 | gap: 2rem; 27 | z-index: 10; 28 | } 29 | 30 | .description { 31 | width: 90%; 32 | font-weight: 300; 33 | font-size: 0.9rem; 34 | color: var(--black); 35 | text-align: center; 36 | } 37 | 38 | .header { 39 | width: 100%; 40 | text-align: center; 41 | color: var(--purple); 42 | font-size: 0.95rem; 43 | } 44 | 45 | .terms__and__conditions { 46 | font-weight: 300; 47 | font-size: 0.9rem; 48 | 49 | text-align: center; 50 | color: var(--black); 51 | } 52 | 53 | .terms__and__conditions span { 54 | font-weight: 500; 55 | font-size: 0.9rem; 56 | color: var(--purple); 57 | } 58 | 59 | .backdrop { 60 | position: absolute; 61 | inset: 0; 62 | background-color: rgba(0, 0, 0, 0.72); 63 | } 64 | 65 | .cta__container { 66 | width: 100%; 67 | flex: 0; 68 | /* padding-inline: 24px; */ 69 | } 70 | 71 | .cta { 72 | width: 100%; 73 | padding: 10px; 74 | border-radius: 4px; 75 | border: none; 76 | background-color: var(--purple); 77 | color: var(--light-purple); 78 | font-weight: 500; 79 | } 80 | -------------------------------------------------------------------------------- /client/src/components/CustomizedDelivery/LocationModal.tsx: -------------------------------------------------------------------------------- 1 | import React, { Dispatch, SetStateAction } from 'react'; 2 | import styles from './LocationModal.module.css'; 3 | import { MdOutlineClose } from 'react-icons/md'; 4 | import { AnimatePresence, motion } from 'framer-motion'; 5 | import { useLocation, useNavigate } from 'react-router-dom'; 6 | 7 | interface Props { 8 | closeModal: () => void; 9 | setProgress: Dispatch>; 10 | deliveryDetailsSubmitHandler: () => void; 11 | } 12 | 13 | export const LocationModal: React.FC = ({ 14 | setProgress, 15 | closeModal, 16 | deliveryDetailsSubmitHandler, 17 | }) => { 18 | const navigate = useNavigate(); 19 | const { pathname } = useLocation(); 20 | return ( 21 |
22 | 23 | 30 |
31 | { 33 | navigate(pathname, { replace: true }); 34 | closeModal(); 35 | }} 36 | /> 37 |
38 |

Location Agreement

39 |

40 | Pailot do not have information of location you are about to provide for this delivery. 41 |
42 | Location is only shared within the transaction parties. 43 |

44 |

45 | Read about our{' '} 46 | 47 | 49 | navigate('/terms-and-conditions', { 50 | state: { path: '/customized-delivery', progress: 5, showModal: true }, 51 | }) 52 | } 53 | > 54 | Terms and Conditions 55 | 56 | , Policies 57 | {' '} 58 | and{' '} 59 | 60 | 62 | navigate('/privacy-policy', { 63 | state: { path: '/customized-delivery', progress: 5, showModal: true }, 64 | }) 65 | } 66 | > 67 | Privacy 68 | 69 | {' '} 70 | in your profile settings 71 |

72 |
73 | 84 |
85 |
86 | { 93 | closeModal(); 94 | }} 95 | > 96 |
97 |
98 | ); 99 | }; 100 | -------------------------------------------------------------------------------- /client/src/components/CustomizedDelivery/OrderSucessful.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | padding: 2rem 1rem; 7 | gap: 2rem; 8 | background-color: var(--white); 9 | } 10 | 11 | .header { 12 | font-size: 14px; 13 | color: var(--black); 14 | } 15 | 16 | .icon__container { 17 | background-color: var(--green); 18 | color: var(--white); 19 | aspect-ratio: 1; 20 | width: 100px; 21 | border-radius: 100%; 22 | display: grid; 23 | place-content: center; 24 | font-size: 2.5rem; 25 | position: relative; 26 | z-index: 10; 27 | } 28 | .icon__container::before { 29 | content: ''; 30 | background-color: var(--green); 31 | width: 100%; 32 | border-radius: 100%; 33 | height: 100%; 34 | z-index: -1; 35 | position: absolute; 36 | } 37 | 38 | .icon__container:after { 39 | top: 2.5%; 40 | left: 2.5%; 41 | content: ''; 42 | background-color: var(--green); 43 | width: 95%; 44 | border-radius: 100%; 45 | height: 95%; 46 | z-index: -10; 47 | position: absolute; 48 | animation: pulse 1s 0.5s linear infinite; 49 | } 50 | 51 | @keyframes pulse { 52 | from { 53 | scale: 1; 54 | opacity: 1; 55 | } 56 | to { 57 | scale: 1.4; 58 | opacity: 0; 59 | } 60 | } 61 | 62 | .description { 63 | font-size: 12px; 64 | color: var(--black); 65 | width: 65%; 66 | text-align: center; 67 | } 68 | 69 | .courier__details { 70 | display: flex; 71 | align-items: center; 72 | font-size: 0.75rem; 73 | gap: 4px; 74 | } 75 | 76 | .courier__details > img { 77 | width: 18px; 78 | } 79 | 80 | .courier__username { 81 | display: flex; 82 | font-size: 0.65rem; 83 | } 84 | 85 | .new__user { 86 | font-size: 8px; 87 | color: var(--purple); 88 | font-weight: 700; 89 | background-color: rgba(229, 212, 255, 1); 90 | border-radius: 4px; 91 | padding: 2px 4px; 92 | } 93 | 94 | .courier__status__pending { 95 | padding: 4px 8px; 96 | background-color: var(--orange); 97 | color: var(--black); 98 | border-radius: 4px; 99 | } 100 | 101 | .cta__container { 102 | display: flex; 103 | flex-direction: column; 104 | gap: 1rem; 105 | width: 100%; 106 | } 107 | 108 | .cta__top { 109 | padding: 1rem; 110 | background-color: var(--purple); 111 | color: var(--white); 112 | font-size: 0.75rem; 113 | border: none; 114 | border-radius: 4px; 115 | } 116 | .cta__mid { 117 | padding: 1rem; 118 | background-color: var(--light-purple); 119 | color: var(--purple); 120 | font-size: 0.75rem; 121 | border: none; 122 | border-radius: 4px; 123 | } 124 | .cta__bottom { 125 | padding: 1rem; 126 | background-color: transparent; 127 | color: var(--black); 128 | border: 1px solid var(--black); 129 | font-size: 0.75rem; 130 | border-radius: 4px; 131 | } 132 | -------------------------------------------------------------------------------- /client/src/components/CustomizedDelivery/OrderSucessful.tsx: -------------------------------------------------------------------------------- 1 | import styles from './OrderSucessful.module.css'; 2 | import React from 'react'; 3 | import { HiOutlineThumbUp } from 'react-icons/hi'; 4 | import { defaultUser } from '../../assets/images'; 5 | import { motion, AnimatePresence } from 'framer-motion'; 6 | import { useSelector } from 'react-redux'; 7 | import { RootState } from '../../store/store'; 8 | import { useNavigate } from 'react-router-dom'; 9 | 10 | export const OrderSucessful = () => { 11 | // get the deliveryType state from the store 12 | const deliveryType = useSelector((state: RootState) => state.deliveryType.deliveryType); 13 | const deliveryDetails = useSelector((state: RootState) => state.deliveryDetails.deliveryDetails); 14 | 15 | const navigate = useNavigate(); 16 | 17 | return ( 18 |
19 |
20 |

{deliveryType === 'active' ? 'Order Successful' : 'Delivery Request Successful'}

21 |
22 | 23 | 35 | 36 | 37 | 38 |

39 | Token of your order is available on “My Deliveries”. Be on the look for your courier to 40 | accept this delivery order 41 |

42 | {deliveryType === 'active' && deliveryDetails.courierDetails.courier && ( 43 |
44 | Couriers Profile picture 45 | 46 | {deliveryDetails.courierDetails.user.username} 47 | 48 | {deliveryDetails.courierDetails.newUser && ( 49 | New user 50 | )} 51 | Pending 52 |
53 | )} 54 |
55 | 64 | Transaction Details 65 | 66 | 75 | Make Another Delivery 76 | 77 | { 86 | navigate('/home'); 87 | }} 88 | > 89 | Back to Home 90 | 91 |
92 |
93 | ); 94 | }; 95 | -------------------------------------------------------------------------------- /client/src/components/CustomizedDelivery/UploadDeliveryImage.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | } 6 | 7 | .top__bar { 8 | width: 100%; 9 | display: flex; 10 | flex: 0; 11 | align-items: center; 12 | justify-content: space-evenly; 13 | padding: 17px 24px; 14 | background-color: var(--light-purple); 15 | } 16 | 17 | .top__bar > div:first-child { 18 | justify-self: flex-start; 19 | } 20 | .top__bar > span:nth-child(2) { 21 | flex: 1; 22 | text-align: center; 23 | } 24 | 25 | .progress { 26 | display: flex; 27 | align-items: center; 28 | justify-content: center; 29 | gap: 2px; 30 | } 31 | 32 | .inactive__progress { 33 | height: 3px; 34 | width: 5px; 35 | background-color: var(--purple); 36 | border-radius: 4px; 37 | } 38 | 39 | .active__progress { 40 | height: 3px; 41 | width: 21px; 42 | background-color: var(--orange); 43 | border-radius: 4px; 44 | } 45 | 46 | .body { 47 | flex: 1; 48 | padding: 1rem; 49 | gap: 1rem; 50 | display: flex; 51 | flex-direction: column; 52 | /* align-items: center; */ 53 | } 54 | 55 | .description { 56 | font-weight: lighter; 57 | font-size: 16px; 58 | } 59 | 60 | .input__container { 61 | position: relative; 62 | margin-top: 1rem; 63 | width: 80%; 64 | display: flex; 65 | flex-direction: column; 66 | align-items: center; 67 | align-self: center; 68 | border-radius: 4px; 69 | background-color: rgba(231, 224, 236, 1); 70 | /* padding-top: 40px; */ 71 | padding-bottom: 17px; 72 | } 73 | 74 | .input__container span { 75 | margin-top: 5px; 76 | font-size: 10px; 77 | color: rgba(126, 125, 125, 1); 78 | } 79 | 80 | .input__container > input { 81 | opacity: 0; 82 | position: absolute; 83 | inset: 0; 84 | background-color: black; 85 | } 86 | 87 | .upload__icon { 88 | margin-top: 40px; 89 | font-size: 3rem; 90 | color: rgba(126, 125, 125, 1); 91 | } 92 | 93 | .delivery__img__container { 94 | /* flex: 1; */ 95 | width: 100%; 96 | height: 100%; 97 | max-height: 30vh; 98 | } 99 | 100 | .delivery__img__container > img { 101 | flex: 1; 102 | width: 100%; 103 | height: 100%; 104 | object-fit: cover; 105 | aspect-ratio: auto; 106 | min-height: 151px; 107 | border-bottom-right-radius: 8px; 108 | border-bottom-left-radius: 8px; 109 | } 110 | 111 | .label { 112 | width: 100%; 113 | align-self: center; 114 | text-align: center; 115 | font-size: 1rem; 116 | color: var(--purple); 117 | } 118 | 119 | .file__name { 120 | font-size: 0.75rem; 121 | margin-left: 10%; 122 | } 123 | 124 | .cta__container { 125 | flex: 0; 126 | margin-bottom: 24.5px; 127 | padding-inline: 24px; 128 | } 129 | 130 | .cta { 131 | width: 100%; 132 | padding: 10px; 133 | border-radius: 4px; 134 | border: none; 135 | background-color: var(--purple); 136 | color: var(--light-purple); 137 | font-weight: 500; 138 | } 139 | 140 | .cta__disabled { 141 | width: 100%; 142 | padding: 10px; 143 | border-radius: 4px; 144 | border: none; 145 | background-color: var(--grey); 146 | font-weight: 500; 147 | } 148 | -------------------------------------------------------------------------------- /client/src/components/DashBoardPopups/ModPopup.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: calc(100vh - 5.1rem); 3 | width: 100%; 4 | top: 0px; 5 | z-index: 10; 6 | position: absolute; 7 | display: flex; 8 | align-items: flex-end; 9 | background-color: rgba(0, 0, 0, 0.6); 10 | overflow: hidden; 11 | } 12 | 13 | .modal { 14 | height: 50%; 15 | width: 100%; 16 | background-color: var(--white); 17 | border-top-left-radius: 32px; 18 | border-top-right-radius: 32px; 19 | padding: 1rem; 20 | display: flex; 21 | flex-direction: column; 22 | gap: 10%; 23 | align-items: center; 24 | } 25 | 26 | .top__section { 27 | width: 100%; 28 | display: flex; 29 | flex-direction: column; 30 | align-items: center; 31 | } 32 | 33 | .close__container { 34 | display: flex; 35 | align-items: center; 36 | justify-content: flex-end; 37 | width: 100%; 38 | font-size: clamp(1.2rem, 3vw, 4rem); 39 | } 40 | 41 | .header { 42 | color: var(--purple); 43 | font-size: clamp(14px, 3vw, 4rem); 44 | font-weight: 600; 45 | } 46 | 47 | .description { 48 | font-size: clamp(10px, 3vw, 1.5rem); 49 | text-align: center; 50 | opacity: 0.7; 51 | } 52 | 53 | .label { 54 | display: flex; 55 | flex-direction: column; 56 | width: 100%; 57 | gap: 5px; 58 | } 59 | 60 | .label > span { 61 | font-size: 12px; 62 | } 63 | 64 | .select__container { 65 | display: flex; 66 | align-items: center; 67 | width: 100%; 68 | /* height: 46px; */ 69 | background-color: var(--light-purple-variant); 70 | padding-inline: 11px; 71 | gap: 11px; 72 | border-radius: 4px; 73 | border-bottom: 1px solid var(--black); 74 | } 75 | 76 | .select__container select:focus, 77 | .select__container select:active { 78 | border: none; 79 | outline: none; 80 | } 81 | 82 | .select__container select { 83 | flex: 1; 84 | border: none; 85 | background-color: transparent; 86 | padding-block: 7px; 87 | } 88 | 89 | .mods__container { 90 | display: flex; 91 | align-items: center; 92 | gap: 2px; 93 | } 94 | 95 | .mod { 96 | display: flex; 97 | align-items: center; 98 | font-size: 8px; 99 | gap: 8px; 100 | background-color: rgba(217, 217, 217, 1); 101 | border-radius: 4px; 102 | flex: 0; 103 | padding: 3px 6.5px; 104 | } 105 | 106 | .cta { 107 | width: 100%; 108 | padding: 10px; 109 | border-radius: 4px; 110 | border: none; 111 | background-color: var(--purple); 112 | color: var(--light-purple); 113 | font-weight: 500; 114 | } 115 | -------------------------------------------------------------------------------- /client/src/components/DashBoardPopups/PricingPopup.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: calc(100vh - 5.1rem); 3 | width: 100%; 4 | top: 0px; 5 | z-index: 10; 6 | position: absolute; 7 | display: flex; 8 | align-items: flex-end; 9 | background-color: rgba(0, 0, 0, 0.6); 10 | overflow: hidden; 11 | } 12 | 13 | .modal { 14 | height: 50%; 15 | width: 100%; 16 | background-color: var(--white); 17 | border-top-left-radius: 32px; 18 | border-top-right-radius: 32px; 19 | padding: 1rem; 20 | display: flex; 21 | flex-direction: column; 22 | gap: 10%; 23 | align-items: center; 24 | } 25 | 26 | .top__section { 27 | width: 100%; 28 | display: flex; 29 | flex-direction: column; 30 | align-items: center; 31 | } 32 | 33 | .close__container { 34 | display: flex; 35 | align-items: center; 36 | justify-content: flex-end; 37 | width: 100%; 38 | font-size: clamp(1.2rem, 3vw, 4rem); 39 | } 40 | 41 | .header { 42 | color: var(--purple); 43 | font-size: clamp(14px, 3vw, 4rem); 44 | font-weight: 600; 45 | } 46 | 47 | .description { 48 | font-size: clamp(10px, 3vw, 1.5rem); 49 | text-align: center; 50 | opacity: 0.7; 51 | } 52 | 53 | .label { 54 | width: 100%; 55 | flex-direction: column; 56 | } 57 | 58 | .label > span { 59 | font-size: 12px; 60 | } 61 | 62 | .input__container { 63 | display: flex; 64 | align-items: center; 65 | width: 100%; 66 | /* gap: 0.5rem; */ 67 | padding-inline: 0.5rem; 68 | background-color: var(--light-purple-variant); 69 | border-radius: 4px; 70 | border-bottom: 1px solid var(--black); 71 | } 72 | 73 | .input { 74 | padding: 0.75rem; 75 | flex: 1; 76 | background-color: transparent; 77 | border: none; 78 | } 79 | 80 | .cta { 81 | width: 100%; 82 | background-color: var(--purple); 83 | color: var(--white); 84 | padding: 0.75rem; 85 | border-radius: 4px; 86 | border: none; 87 | } 88 | -------------------------------------------------------------------------------- /client/src/components/DashBoardPopups/PricingPopup.tsx: -------------------------------------------------------------------------------- 1 | import styles from './PricingPopup.module.css'; 2 | import { IoMdClose } from 'react-icons/io'; 3 | import { PiIcon } from '../../assets/icons'; 4 | import { useDispatch, useSelector } from 'react-redux'; 5 | import { createCourierDetailsActions, RootState } from '../../store/store'; 6 | import React, { useRef } from 'react'; 7 | import { motion } from 'framer-motion'; 8 | 9 | interface Props { 10 | // eslint-disable-next-line no-unused-vars 11 | setShowPricingPopup: (value: boolean) => void; 12 | } 13 | 14 | export const PricingPopup: React.FC = ({ setShowPricingPopup }) => { 15 | const dispatch = useDispatch(); 16 | const userCourierDetails = useSelector( 17 | (state: RootState) => state.userDetails.courier 18 | ); 19 | const inputRef = useRef(null); 20 | return ( 21 | 27 | 38 |
39 |
{ 42 | setShowPricingPopup(false); 43 | }} 44 | > 45 | 46 |
47 | Set Your Pricing 48 |
49 |

50 | Setting up your price create an avenue for you to earn more Pi while delivery goods and 51 | services around and within Pailot. we advice for you to not be too greedy as Pailot is not 52 | concerned with whatever price you choose. 53 |

54 | 61 | 76 |
77 |
78 | ); 79 | }; 80 | -------------------------------------------------------------------------------- /client/src/components/DispatchersList/DispatchersList.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .location { 7 | font-size: 12px; 8 | display: flex; 9 | align-items: center; 10 | gap: 0.5rem; 11 | } 12 | 13 | .dispatcher__top__bar { 14 | font-size: 12px; 15 | display: flex; 16 | justify-content: space-between; 17 | align-items: center; 18 | margin-block: 0.5rem; 19 | } 20 | 21 | .dispatchers__near__you { 22 | padding: 0.5rem; 23 | color: rgba(0, 0, 0, 1); 24 | font-weight: bold; 25 | font-size: 14px; 26 | display: flex; 27 | align-items: center; 28 | gap: 0.5rem; 29 | border-radius: 4px; 30 | } 31 | 32 | .dispatchers__near__you__count { 33 | font-size: 10px; 34 | width: 19px; 35 | height: 19px; 36 | display: grid; 37 | place-content: center; 38 | color: var(--white); 39 | background-color: rgba(227, 30, 34, 1); 40 | border-radius: 100px; 41 | } 42 | 43 | .delivery__preference { 44 | font-weight: 500; 45 | } 46 | 47 | .body { 48 | display: flex; 49 | flex-direction: column; 50 | gap: 1rem; 51 | } 52 | 53 | .see__all { 54 | width: 100%; 55 | text-align: center; 56 | font-size: 10px; 57 | cursor: pointer; 58 | } 59 | 60 | .interstate__orders { 61 | display: flex; 62 | align-items: center; 63 | justify-content: space-between; 64 | } 65 | 66 | .interstate__orders > p { 67 | font-size: 14px; 68 | font-weight: 600; 69 | display: flex; 70 | gap: 0.5em; 71 | align-items: center; 72 | } 73 | 74 | .interstate__orders > p > span { 75 | font-size: 10px; 76 | width: 19px; 77 | height: 19px; 78 | display: grid; 79 | place-content: center; 80 | border-radius: 100px; 81 | color: var(--white); 82 | background-color: rgba(227, 30, 34, 1); 83 | margin-block: 1rem; 84 | } 85 | -------------------------------------------------------------------------------- /client/src/components/LoadingSpinner/LoadingSpinner.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: absolute; 3 | width: 100vw; 4 | height: 100vh; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | background-color: var(--light-purple); 9 | z-index: 9999; 10 | } 11 | 12 | .logo__container { 13 | border-radius: 100px; 14 | position: relative; 15 | isolation: isolate; 16 | z-index: 10; 17 | } 18 | 19 | .logo__container:after { 20 | content: ''; 21 | position: absolute; 22 | border-radius: 100px; 23 | height: 95%; 24 | width: 95%; 25 | top: 1.9%; 26 | left: 2.5%; 27 | z-index: -1; 28 | background-color: var(--purple); 29 | animation: pulse 2s linear infinite; 30 | } 31 | 32 | .logo { 33 | border-radius: 100px; 34 | width: 10rem; 35 | aspect-ratio: 1; 36 | } 37 | 38 | @keyframes pulse { 39 | 0% { 40 | scale: 1; 41 | opacity: 1; 42 | } 43 | 100% { 44 | scale: 1.3; 45 | opacity: 0; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client/src/components/LoadingSpinner/LoadingSpinner.tsx: -------------------------------------------------------------------------------- 1 | import { logo } from '../../assets/images'; 2 | import styles from './LoadingSpinner.module.css'; 3 | 4 | export const LoadingSpinner = () => { 5 | return ( 6 |
7 |
8 | Pailot Logo 9 |
10 |
11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /client/src/components/Settings/Wallet/Wallet.module.css: -------------------------------------------------------------------------------- 1 | #wrapper { 2 | padding-top: 10vh; 3 | } 4 | .settingsTitle { 5 | color: #30007e; 6 | margin: 0; 7 | padding: 0 10%; 8 | font-weight: lighter; 9 | text-align: center; 10 | } 11 | .settingsInputWrapper { 12 | display: flex; 13 | flex-direction: column; 14 | gap: 40px; 15 | align-items: center; 16 | margin-top: 10%; 17 | padding: 0 8%; 18 | box-sizing: border-box; 19 | } 20 | .settingsInput { 21 | width: 100%; 22 | border: 1px solid #cacaca; 23 | border-radius: 4px; 24 | height: 34px; 25 | display: flex; 26 | gap: 10px; 27 | padding: 2%; 28 | position: relative; 29 | } 30 | 31 | .settingsInput input { 32 | width: 100%; 33 | border: none; 34 | height: 34px; 35 | outline: none; 36 | font-size: 18px; 37 | color: #cacaca; 38 | } 39 | 40 | .settingsInput img { 41 | width: 10%; 42 | cursor: pointer; 43 | } 44 | 45 | .settingsBtnDisabled { 46 | background-color: #d9d9d9; 47 | width: 100%; 48 | padding: 2%; 49 | border-radius: 4px; 50 | } 51 | 52 | .settingsBtnDisabled button:disabled { 53 | border: none; 54 | width: 100%; 55 | background-color: #d9d9d9; 56 | color: #989898; 57 | height: 34px; 58 | } 59 | 60 | .settingsBtn { 61 | width: 100%; 62 | padding: 2%; 63 | border-radius: 4px; 64 | background-color: #30007e; 65 | } 66 | 67 | .settingsBtn button { 68 | border: none; 69 | width: 100%; 70 | background-color: #30007e; 71 | color: #fff; 72 | height: 34px; 73 | } 74 | -------------------------------------------------------------------------------- /client/src/components/Settings/Wallet/index.tsx: -------------------------------------------------------------------------------- 1 | import { ClearIcon } from '../../../assets/icons'; 2 | import Styles from './Wallet.module.css'; 3 | import useWallet from './useWallet'; 4 | import { AlertModal } from '../../Common/AlertModal'; 5 | export const Wallet = () => { 6 | const { walletAddress, showAlert, setShowAlert, handleSubmit, handleClear, setWalletAddress } = 7 | useWallet(); 8 | return ( 9 |
10 |

Copy and paste your Pi wallet address here

11 |
12 |
13 | setWalletAddress(e.target.value)} 17 | placeholder="Example: GMAHN9830OIPRTYEUI5" 18 | /> 19 | 20 |
21 | 22 |
23 | 26 |
27 |
28 | {showAlert && ( 29 | 36 | )} 37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /client/src/components/Settings/Wallet/useWallet.ts: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | function useWallet() { 3 | const [walletAddress, setWalletAddress] = useState(''); 4 | const [showAlert, setShowAlert] = useState(false); 5 | const handleSubmit = (event: React.FormEvent) => { 6 | event.preventDefault(); 7 | setShowAlert(true); 8 | }; 9 | const handleClear = () => { 10 | setWalletAddress(''); 11 | }; 12 | return { 13 | walletAddress, 14 | showAlert, 15 | setShowAlert, 16 | handleSubmit, 17 | setWalletAddress, 18 | handleClear, 19 | }; 20 | } 21 | export default useWallet; 22 | -------------------------------------------------------------------------------- /client/src/components/SplashScreen/ScreenOne/ScreenOne.module.css: -------------------------------------------------------------------------------- 1 | #wrapper { 2 | height: 100vh; 3 | width: 100vw; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | } 8 | 9 | #logo_wrapper { 10 | background-color: var(--purple); 11 | border-radius: 50%; 12 | height: 100px; 13 | width: 100px; 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | overflow: hidden; 18 | } 19 | 20 | #logo { 21 | border-radius: 100%; 22 | } 23 | -------------------------------------------------------------------------------- /client/src/components/SplashScreen/ScreenOne/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { logo } from '../../../assets/images'; 3 | import { motion } from 'framer-motion'; 4 | import { scaleRotate, fadeIn } from '../../../animations'; 5 | import styles from './ScreenOne.module.css'; 6 | import { SplashScreenState } from '../../../types/SplashScreenState'; 7 | 8 | type Props = { 9 | setNextScreen: React.Dispatch>; 10 | }; 11 | 12 | export const ScreenOne = ({ setNextScreen }: Props) => { 13 | return ( 14 |
15 | setNextScreen('progress_bar')} 19 | > 20 | 21 | Pailot Logo 22 | 23 | 24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /client/src/components/SplashScreen/ScreenTwo/ScreenTwo.module.css: -------------------------------------------------------------------------------- 1 | #wrapper { 2 | height: 100vh; 3 | width: 100vw; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: center; 8 | gap: 3%; 9 | background-color: var(--purple); 10 | } 11 | 12 | .progress_bar_background { 13 | background-color: #d1d1d1; 14 | height: 8px; 15 | width: 15rem; 16 | border-radius: 8px; 17 | } 18 | 19 | .progress_bar { 20 | background-color: var(--orange); 21 | height: 8px; 22 | width: 0%; 23 | border-radius: 8px; 24 | } 25 | -------------------------------------------------------------------------------- /client/src/components/SplashScreen/ScreenTwo/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { logo } from '../../../assets/images'; 3 | import styles from './ScreenTwo.module.css'; 4 | import { motion } from 'framer-motion'; 5 | import { useNavigate } from 'react-router-dom'; 6 | import { useDispatch } from 'react-redux'; 7 | import { userDetailsActions } from '../../../store/store'; 8 | 9 | export const ScreenTwo = () => { 10 | const navigate = useNavigate(); 11 | const dispatch = useDispatch(); 12 | 13 | const handleAnimationComplete = () => { 14 | const userData = sessionStorage.getItem('user'); 15 | if (userData) { 16 | const data = JSON.parse(userData); 17 | dispatch(userDetailsActions.setUserDetails({ user: data.user })); 18 | dispatch(userDetailsActions.setCourierDetails({ courier: data.courier })); 19 | navigate('/home'); 20 | } else { 21 | navigate('/welcome'); 22 | } 23 | }; 24 | 25 | return ( 26 |
27 | Pailot's Logo 28 |
29 | 36 |
37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /client/src/components/WelcomeScreen/AllowPi/AllowPi.module.css: -------------------------------------------------------------------------------- 1 | .allowPi { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | justify-content: center; 6 | height: 100vh; 7 | width: 100%; 8 | text-align: center; 9 | padding: 2.4rem; 10 | gap: 1.5rem; 11 | background-color: var(--purple); 12 | } 13 | 14 | .allowPi img { 15 | width: 2.5rem; 16 | aspect-ratio: 1; 17 | } 18 | 19 | .allowPi h3 { 20 | color: var(--orange); 21 | margin: 2% 0; 22 | font-size: 1.25rem; 23 | font-weight: 500; 24 | letter-spacing: 0.1em; 25 | } 26 | 27 | .allowPi > p { 28 | font-size: 0.813rem; 29 | font-weight: 300; 30 | color: var(--white); 31 | } 32 | .cta__container { 33 | max-width: 40rem; 34 | display: flex; 35 | flex-direction: column; 36 | align-items: center; 37 | gap: 1rem; 38 | width: 100%; 39 | } 40 | 41 | .allowBtn { 42 | font-weight: bolder; 43 | border: none; 44 | width: 100%; 45 | color: #fff; 46 | padding: 10px; 47 | border-radius: 0.25rem; 48 | } 49 | 50 | .allowBtnActive { 51 | background-color: var(--orange); 52 | color: var(--purple); 53 | } 54 | 55 | .allowBtnInactive { 56 | background-color: rgba(66, 66, 66, 1); 57 | } 58 | 59 | .terms { 60 | font-size: 10px; 61 | color: var(--white); 62 | text-align: left; 63 | width: 100%; 64 | } 65 | 66 | .terms a { 67 | color: var(--orange); 68 | } 69 | 70 | .tick { 71 | display: flex; 72 | align-items: center; 73 | gap: 0.7rem; 74 | } 75 | 76 | .tick input { 77 | outline: none; 78 | accent-color: var(--white); 79 | } 80 | 81 | .tick p { 82 | font-size: 0.7rem; 83 | font-weight: 300; 84 | color: var(--white); 85 | } 86 | 87 | .error { 88 | color: #cc0000; 89 | } 90 | -------------------------------------------------------------------------------- /client/src/components/WelcomeScreen/SelectLanguage/SelectLanguage.module.css: -------------------------------------------------------------------------------- 1 | .language { 2 | display: flex; 3 | align-self: flex-start; 4 | justify-content: right; 5 | width: 100%; 6 | padding: 5%; 7 | box-sizing: border-box; 8 | } 9 | .language > div { 10 | display: flex; 11 | align-items: center; 12 | } 13 | .language select { 14 | background-color: transparent; 15 | border: none; 16 | color: #304596; 17 | font-weight: 400; 18 | } 19 | -------------------------------------------------------------------------------- /client/src/components/WelcomeScreen/SelectLanguage/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { WorldLogo } from '../../../assets/icons'; 3 | import styles from './SelectLanguage.module.css'; 4 | export const SelectLanguage = () => { 5 | return ( 6 |
7 |
8 | 9 | 14 |
15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /client/src/components/index.tsx: -------------------------------------------------------------------------------- 1 | // Common 2 | export { Button } from './Common/Button/Button'; 3 | export { FingerPrint } from './Common/FingerPrint'; 4 | export { Header } from './Common/Header'; 5 | export { FooterNavBar } from './Common/FooterNavBar'; 6 | export { AlertModal } from './Common/AlertModal'; 7 | export { HomePlus } from './Common/HomePlus'; 8 | 9 | // SplashScreen 10 | export { ScreenOne } from './SplashScreen/ScreenOne'; 11 | export { ScreenTwo } from './SplashScreen/ScreenTwo'; 12 | 13 | // WelcomeScreen 14 | export { AllowPi } from './WelcomeScreen/AllowPi'; 15 | export { SelectLanguage } from './WelcomeScreen/SelectLanguage'; 16 | 17 | // Settings Screen 18 | export { SettingsHome } from './Settings/SettingsHome'; 19 | export { Wallet } from './Settings/Wallet'; 20 | 21 | // Customize delivery 22 | export { DeliveryDetails } from './CustomizedDelivery/DeliveryDetails'; 23 | export { DeliveryLocation } from './CustomizedDelivery/DeliveryLocation'; 24 | export { DeliveryPayment } from './CustomizedDelivery/DeliveryPayment'; 25 | export { DeliverySummary } from './CustomizedDelivery/DeliverySummary'; 26 | export { DeliveryWeightSize } from './CustomizedDelivery/DeliveryWeightSize'; 27 | export { ModeOfDelivery } from './CustomizedDelivery/ModeOfDelivery'; 28 | export { UploadDeliveryImage } from './CustomizedDelivery/UploadDeliveryImage'; 29 | export { OrderSucessful } from './CustomizedDelivery/OrderSucessful'; 30 | -------------------------------------------------------------------------------- /client/src/constants/url.constants.ts: -------------------------------------------------------------------------------- 1 | const API_URL_BASE = 'https://pailot-backend.onrender.com'; 2 | // const API_URL_BASE = 'http://localhost:3333'; 3 | 4 | // Auth Endpoints 5 | export const SIGN_IN_URL = `${API_URL_BASE}/user/sign-in`; 6 | 7 | // User Endpoints 8 | export const CREATE_USER_URL = `${API_URL_BASE}/user`; 9 | export const GET_ALL_USER_URL = `${API_URL_BASE}/user`; 10 | export const GET_ALL_COURIER_USER_URL = `${API_URL_BASE}/user/couriers`; 11 | export const GET_USER_URL = `${API_URL_BASE}/user/profile`; 12 | export const GET_USER_BY_USERNAME_URL = (username: string) => `${API_URL_BASE}/user/${username}`; 13 | export const UPDATE_USER_URL = (userId: string) => `${API_URL_BASE}/user/${userId}`; 14 | export const DELETE_USER_URL = (userId: string) => `${API_URL_BASE}/user/${userId}`; 15 | 16 | // Courier Endpoints 17 | export const CREATE_COURIER_URL = `${API_URL_BASE}/user/courier`; 18 | export const UPDATE_COURIER_URL =(userId: string) => `${API_URL_BASE}/user/courier/${userId}`; 19 | export const DELETE_COURIER_URL =(userId: string) => `${API_URL_BASE}/user/courier/${userId}`; 20 | 21 | // Payments Endpoints 22 | export const INCOMPLETE_PAYMENT_URL = `${API_URL_BASE}/payment/incomplete`; 23 | export const APPROVE_PAYMENT_URL = `${API_URL_BASE}/payment/approve`; 24 | export const COMPLETE_PAYMENT_URL = `${API_URL_BASE}/payment/complete`; 25 | export const CANCELLED_PAYMENT_URL = `${API_URL_BASE}/payment/cancel`; 26 | export const WITHDRAW_URL = `${API_URL_BASE}/payment/courier/withdraw`; 27 | 28 | // Transactions Endpoints 29 | export const CREATE_TRANSACTION_URL = `${API_URL_BASE}/transaction`; 30 | export const GET_ALL_TRANSACTIONS_URL = `${API_URL_BASE}/transaction`; 31 | export const GET_PENDING_TRANSACTIONS_URL = `${API_URL_BASE}/transaction/pending`; 32 | export const GET_TRANSACTION_URL = (transactiondId: string) => `${API_URL_BASE}/transaction/${transactiondId}`; 33 | export const UPDATE_TRANSACTION_URL = (transactiondId: string) => `${API_URL_BASE}/transaction/${transactiondId}`; 34 | export const UPDATE_TRANSACTION_STATUS_URL = (transactiondId: string) => `${API_URL_BASE}/transaction/status/${transactiondId}`; 35 | export const ACCEPT_PENDING_TRANSACTION_URL = (transactiondId: string) => `${API_URL_BASE}/transaction/requests/accept-pending/${transactiondId}`; 36 | export const DELETE_TRANSACTION_URL = (transactiondId: string) => `${API_URL_BASE}/transaction/${transactiondId}`; 37 | export const GET_ALL_SENDER_TRANSACTION_URL = (transactiondId: string) => `${API_URL_BASE}/transaction/${transactiondId}`; 38 | export const GET_ALL_COURIER_TRANSACTION_URL = (transactiondId: string) => `${API_URL_BASE}/transaction/${transactiondId}`; 39 | export const GET_ALL_RECEIVER_TRANSACTION_URL = (transactiondId: string) => `${API_URL_BASE}/transaction/${transactiondId}`; 40 | export const GET_ALL_RECEIVER_TRANSACTION_BY_USERNAME_URL = (username: string) => `${API_URL_BASE}/transaction/${username}`; 41 | -------------------------------------------------------------------------------- /client/src/hooks/useApi.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosError, AxiosRequestConfig } from 'axios'; 2 | import { useEffect, useState } from 'react'; 3 | import { useNavigate } from 'react-router-dom'; 4 | 5 | interface IErrorBase { 6 | error: AxiosError | Error; 7 | type: 'axios-error' | 'error'; 8 | } 9 | 10 | export interface IAxiosError extends IErrorBase { 11 | error: AxiosError; 12 | type: 'axios-error'; 13 | } 14 | 15 | export interface IError extends IErrorBase { 16 | error: Error; 17 | type: 'error'; 18 | } 19 | interface Response { 20 | data: T | null; 21 | loading: boolean; 22 | error?: IAxiosError | IError; 23 | } 24 | 25 | const headerConfig = { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }; 26 | 27 | export const fetchWithCredentials = async ( 28 | url: string, 29 | { headers: headerProps, params: paramProps, ...rest }: AxiosRequestConfig 30 | ) => { 31 | const headers = { 32 | Authorization: "Bearer " + sessionStorage.getItem('token'), 33 | ...headerProps, 34 | }; 35 | return axios({ 36 | url, 37 | headers: { 38 | ...headerConfig, 39 | ...headers, 40 | }, 41 | params: { 42 | ...paramProps, 43 | }, 44 | ...rest, 45 | }).catch((e) => { 46 | throw e; 47 | }); 48 | }; 49 | 50 | export const useApi = ( 51 | endpoint: string, 52 | params: AxiosRequestConfig = { 53 | method: 'GET', 54 | }, 55 | dependencies: unknown[] = [] 56 | ): Response => { 57 | const api = fetchWithCredentials; 58 | const [data, setData] = useState(null); 59 | const [loading, setLoading] = useState(true); 60 | const [error, setError] = useState | IError>(); 61 | 62 | const navigate = useNavigate(); 63 | 64 | useEffect(() => { 65 | setLoading(true); 66 | setError(undefined); 67 | 68 | api(endpoint, params) 69 | .then((res) => res.data) 70 | .then(setData) 71 | .catch((err) => { 72 | if (axios.isAxiosError(err)) { 73 | if (err.response?.status === 401) { 74 | navigate('/welcome'); 75 | } 76 | setError({ 77 | type: 'axios-error', 78 | error: err, 79 | } as IAxiosError); 80 | } else { 81 | setError({ 82 | type: 'error', 83 | error: err, 84 | }); 85 | throw err; 86 | } 87 | }) 88 | .finally(() => setLoading(false)); 89 | 90 | // eslint-disable-next-line 91 | }, [endpoint, ...dependencies]); 92 | 93 | return { 94 | data, 95 | loading, 96 | error, 97 | }; 98 | } 99 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | src: url('./assets/fonts/Roboto-font/Roboto-Black.ttf') format('ttf'), 4 | url('./assets/fonts/Roboto-font/Roboto-BlackItalic.ttf') format('ttf'), 5 | url('./assets/fonts/Roboto-font/Roboto-Bold.ttf') format('ttf'), 6 | url('./assets/fonts/Roboto-font/Roboto-BoldItalic.ttf') format('ttf'), 7 | url('./assets/fonts/Roboto-font/Roboto-Bold.ttf') format('ttf'), 8 | url('./assets/fonts/Roboto-font/Roboto-BoldItalic.ttf') format('ttf'), 9 | url('./assets/fonts/Roboto-font/Roboto-Italic.ttf') format('ttf'), 10 | url('./assets/fonts/Roboto-font/Roboto-Light.ttf') format('ttf'), 11 | url('./assets/fonts/Roboto-font/Roboto-LightItalic.ttf') format('ttf'), 12 | url('./assets/fonts/Roboto-font/Roboto-Medium.ttf') format('ttf'), 13 | url('./assets/fonts/Roboto-font/Roboto-MediumItalic.ttf') format('ttf'), 14 | url('./assets/fonts/Roboto-font/Roboto-Regular.ttf') format('ttf'), 15 | url('./assets/fonts/Roboto-font/Roboto-Thin.ttf') format('ttf'); 16 | font-weight: normal; 17 | font-style: normal; 18 | } 19 | 20 | body { 21 | margin: 0; 22 | width: 100vw; 23 | font-family: 'Roboto', sans-serif; 24 | box-sizing: border-box; 25 | } 26 | 27 | :root { 28 | --purple: rgba(48, 0, 126, 1); 29 | --white: #f5f5f5; 30 | --orange: rgba(255, 149, 36, 1); 31 | --light-purple: rgba(226, 232, 255, 1); 32 | --light-purple-variant: rgba(231, 224, 236, 1); 33 | --black: rgba(66, 66, 66, 1); 34 | --grey: rgba(217, 217, 217, 1); 35 | --red: rgba(255, 0, 0, 1); 36 | --green: rgba(2, 205, 22, 1); 37 | } 38 | 39 | * { 40 | box-sizing: border-box; 41 | padding: 0px; 42 | margin: 0px; 43 | /* font-family: 'Roboto'; */ 44 | } 45 | -------------------------------------------------------------------------------- /client/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import { Provider } from 'react-redux'; 6 | import store from './store/store'; 7 | 8 | const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); 9 | root.render( 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /client/src/pages/ActiveDelivery/ActiveDelivery.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 100vh; 4 | display: flex; 5 | flex-direction: column; 6 | } 7 | 8 | .profile { 9 | display: flex; 10 | align-items: center; 11 | justify-content: space-between; 12 | /* gap: 8px; */ 13 | width: 100%; 14 | padding: 1rem; 15 | background-color: var(--purple); 16 | } 17 | 18 | .profile > div:first-child { 19 | display: flex; 20 | gap: 8px; 21 | align-items: center; 22 | } 23 | 24 | .profile__photo { 25 | aspect-ratio: 1; 26 | width: 18px; 27 | border-radius: 100%; 28 | } 29 | 30 | .profile span { 31 | color: var(--white); 32 | } 33 | 34 | .profile > span { 35 | display: flex; 36 | align-items: center; 37 | background-color: var(--white); 38 | padding: 2px 6px; 39 | border-radius: 4px; 40 | } 41 | 42 | .content { 43 | flex: 1; 44 | } 45 | -------------------------------------------------------------------------------- /client/src/pages/ActiveDelivery/ActiveDelivery.tsx: -------------------------------------------------------------------------------- 1 | import styles from './ActiveDelivery.module.css'; 2 | import { logo } from '../../assets/images/index'; 3 | import { GrMapLocation } from 'react-icons/gr'; 4 | import { useState } from 'react'; 5 | import { 6 | DeliveryDetails, 7 | DeliveryLocation, 8 | DeliveryWeightSize, 9 | UploadDeliveryImage, 10 | DeliverySummary, 11 | OrderSucessful, 12 | } from '../../components'; 13 | import { ActivePayment } from './../../components/ActiveDelivery/ActivePayment'; 14 | import { OnlineDelivery } from '../../components/ActiveDelivery/OnlineDelivery'; 15 | 16 | export const ActiveDelivery = () => { 17 | const [progress, setProgress] = useState(1); 18 | const [uploadedImage, setUploadedImage] = useState(); 19 | 20 | return ( 21 |
22 |
23 |
24 | Profile Photo 25 | Pailot 26 |
27 | 28 | 29 | 30 |
31 |
32 | {progress === 1 && } 33 | {progress === 2 && ( 34 | 39 | )} 40 | {progress === 3 && } 41 | {progress === 4 && } 42 | {progress === 5 && } 43 | {progress === 6 && } 44 | {progress === 7 && ( 45 | 46 | )} 47 | {progress === 8 && } 48 |
49 |
50 | ); 51 | }; 52 | -------------------------------------------------------------------------------- /client/src/pages/CourierForm/CourierForm.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | background-color: var(--white); 3 | display: flex; 4 | flex-direction: column; 5 | gap: 2rem; 6 | height: 100vh; 7 | position: relative; 8 | } 9 | 10 | .top__bar { 11 | background-color: var(--purple); 12 | font-size: 14px; 13 | display: flex; 14 | align-items: center; 15 | gap: 1rem; 16 | padding: 1rem; 17 | color: var(--white); 18 | } 19 | 20 | .form { 21 | width: 100%; 22 | display: flex; 23 | flex-direction: column; 24 | align-items: center; 25 | gap: 1.5rem; 26 | padding: 1rem; 27 | } 28 | 29 | .form > p { 30 | font-size: 12px; 31 | color: var(--purple); 32 | } 33 | 34 | .label { 35 | width: 100%; 36 | border: 1px solid rgba(66, 66, 66, 0.4); 37 | padding: 0.5rem; 38 | padding-inline: 1rem; 39 | border-radius: 4px; 40 | position: relative; 41 | } 42 | 43 | .label > p { 44 | font-size: 10px; 45 | color: rgba(66, 66, 66, 0.8); 46 | position: absolute; 47 | top: 0px; 48 | left: 0.7rem; 49 | transform: translateY(-50%); 50 | background-color: var(--white); 51 | padding-inline: 0.3rem; 52 | } 53 | 54 | .label > div { 55 | display: flex; 56 | align-items: center; 57 | width: 100%; 58 | color: rgba(66, 66, 66, 0.6); 59 | } 60 | 61 | .mod { 62 | width: 100%; 63 | border: 1px solid rgba(66, 66, 66, 0.4); 64 | padding: 0.5rem; 65 | padding-inline: 1rem; 66 | border-radius: 4px; 67 | display: flex; 68 | align-items: center; 69 | position: relative; 70 | } 71 | 72 | .mod > p { 73 | font-size: 10px; 74 | color: rgba(66, 66, 66, 0.8); 75 | position: absolute; 76 | top: 0px; 77 | left: 0.7rem; 78 | transform: translateY(-50%); 79 | background-color: var(--white); 80 | padding-inline: 0.3rem; 81 | } 82 | 83 | .mod > span { 84 | font-size: 14px; 85 | flex: 1; 86 | opacity: 0.6; 87 | } 88 | 89 | .mod > .cancel__icon { 90 | opacity: 0.5; 91 | } 92 | 93 | .select { 94 | width: 100%; 95 | border: none; 96 | background-color: transparent; 97 | color: rgba(66, 66, 66, 0.6); 98 | } 99 | 100 | .select:active, 101 | .select:focus { 102 | border: none; 103 | outline: none; 104 | } 105 | 106 | .input { 107 | flex: 1; 108 | border: none; 109 | outline: none; 110 | background-color: transparent; 111 | } 112 | 113 | .input:active, 114 | .input:focus { 115 | border: none; 116 | outline: none; 117 | } 118 | 119 | .time__of__service { 120 | width: 100%; 121 | display: flex; 122 | align-items: center; 123 | justify-content: space-around; 124 | gap: 1rem; 125 | } 126 | 127 | .disabled__cta { 128 | width: 100%; 129 | padding: 0.5rem; 130 | background-color: var(--grey); 131 | border: none; 132 | border-radius: 4px; 133 | color: rgba(66, 66, 66, 0.8); 134 | } 135 | 136 | .cta { 137 | width: 100%; 138 | padding: 0.5rem; 139 | background-color: var(--purple); 140 | border: none; 141 | border-radius: 4px; 142 | color: var(--white); 143 | } 144 | -------------------------------------------------------------------------------- /client/src/pages/CustomizedDelivery/CustomizedDelivery.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 100vh; 4 | display: flex; 5 | flex-direction: column; 6 | } 7 | 8 | .profile { 9 | display: flex; 10 | align-items: center; 11 | justify-content: space-between; 12 | /* gap: 8px; */ 13 | width: 100%; 14 | padding: 1rem; 15 | background-color: var(--purple); 16 | } 17 | 18 | .profile > div:first-child { 19 | display: flex; 20 | gap: 8px; 21 | align-items: center; 22 | } 23 | 24 | .profile__photo { 25 | aspect-ratio: 1; 26 | width: 18px; 27 | border-radius: 100%; 28 | } 29 | 30 | .profile span { 31 | color: var(--white); 32 | } 33 | 34 | .profile > span { 35 | display: flex; 36 | align-items: center; 37 | background-color: var(--white); 38 | padding: 2px 6px; 39 | border-radius: 4px; 40 | } 41 | 42 | .content { 43 | flex: 1; 44 | } 45 | -------------------------------------------------------------------------------- /client/src/pages/CustomizedDelivery/CustomizedDelivery.tsx: -------------------------------------------------------------------------------- 1 | import styles from './CustomizedDelivery.module.css'; 2 | import { logo } from '../../assets/images/index'; 3 | import { GrMapLocation } from 'react-icons/gr'; 4 | import { useState } from 'react'; 5 | import { 6 | DeliveryDetails, 7 | DeliveryLocation, 8 | DeliveryWeightSize, 9 | ModeOfDelivery, 10 | UploadDeliveryImage, 11 | DeliverySummary, 12 | DeliveryPayment, 13 | OrderSucessful, 14 | } from '../../components'; 15 | import { useLocation } from 'react-router-dom'; 16 | 17 | export const CustomizedDelivery = () => { 18 | const { state } = useLocation(); 19 | const [progress, setProgress] = useState(state ? state.progress : 1); 20 | const [uploadedImage, setUploadedImage] = useState(); 21 | return ( 22 |
23 |
24 |
25 | Profile Photo 26 | Pailot 27 |
28 | 29 | 30 | 31 |
32 |
33 | {progress === 1 && ( 34 | 39 | )} 40 | {progress === 2 && } 41 | {progress === 3 && } 42 | {progress === 4 && } 43 | {progress === 5 && } 44 | {progress === 6 && } 45 | {progress === 7 && ( 46 | 47 | )} 48 | {progress === 8 && } 49 |
50 |
51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /client/src/pages/OnboardingCompleted/OnboardingCompleted.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100vh; 3 | width: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | } 7 | 8 | .profile { 9 | display: flex; 10 | align-items: center; 11 | /* gap: 8px; */ 12 | width: 100%; 13 | padding: 1rem; 14 | background-color: var(--purple); 15 | } 16 | 17 | .profile > div:first-child { 18 | display: flex; 19 | align-items: center; 20 | } 21 | 22 | .photo__and__name { 23 | display: flex; 24 | align-items: center; 25 | gap: 8px; 26 | } 27 | 28 | .back { 29 | color: var(--light-purple); 30 | font-size: medium; 31 | } 32 | 33 | .profile__photo { 34 | margin-left: 16px; 35 | aspect-ratio: 1; 36 | width: 18px; 37 | border-radius: 100%; 38 | } 39 | 40 | .profile span { 41 | color: var(--white); 42 | } 43 | 44 | .page__content { 45 | padding-top: 10%; 46 | display: flex; 47 | flex: 1; 48 | flex-direction: column; 49 | gap: 10%; 50 | align-items: center; 51 | background-color: var(--light-purple); 52 | } 53 | 54 | .page__content h3 { 55 | font-size: 24px; 56 | color: var(--purple); 57 | } 58 | 59 | .page__content p { 60 | color: var(--black); 61 | width: 80%; 62 | text-align: center; 63 | } 64 | 65 | .cta__container { 66 | display: flex; 67 | flex-direction: column; 68 | align-items: center; 69 | gap: 1rem; 70 | width: 100%; 71 | margin-top: 35px; 72 | padding: 1rem; 73 | } 74 | 75 | .explore__cta { 76 | padding: 12px; 77 | font-weight: 500; 78 | font-size: 14px; 79 | color: var(--light-purple); 80 | background-color: var(--purple); 81 | border-radius: 4px; 82 | border: none; 83 | width: 100%; 84 | max-width: 40rem; 85 | } 86 | 87 | .go__to__profile__cta { 88 | padding: 12px; 89 | font-size: 14px; 90 | font-weight: 500; 91 | color: var(--purple); 92 | background-color: transparent; 93 | border-radius: 4px; 94 | border: none; 95 | } 96 | 97 | .checkmark { 98 | width: 57px; 99 | height: 57px; 100 | display: flex; 101 | align-items: center; 102 | justify-content: center; 103 | border-radius: 100%; 104 | color: var(--purple); 105 | border: 3px solid var(--orange); 106 | position: absolute; 107 | bottom: 3rem; 108 | right: 1.5rem; 109 | transform: translate(-50%, -50%); 110 | } 111 | -------------------------------------------------------------------------------- /client/src/pages/OnboardingCompleted/OnboardingCompleted.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './OnboardingCompleted.module.css'; 3 | import { logo } from '../../assets/images/index'; 4 | import { IoMdArrowRoundBack } from '../../assets/icons'; 5 | import { BsCheckLg } from '../../assets/icons'; 6 | import { useNavigate } from 'react-router-dom'; 7 | import { motion, AnimatePresence } from 'framer-motion'; 8 | 9 | export const OnboardingCompleted = () => { 10 | const navigate = useNavigate(); 11 | return ( 12 |
13 |
14 | 15 | 16 | { 19 | navigate(-1); 20 | }} 21 | /> 22 | 23 | 24 | 25 | Profile Photo 26 | Pailot 27 | 28 |
29 |
30 |

You are all set

31 |

Get ready to be Pailoted in your everyday delivery worldwide using your Pi coin

32 |
33 | [navigate('/home')]} 42 | > 43 | Explore Pailot 44 | 45 | 54 | Take me to my profile 55 | 56 |
57 | 65 | 66 | 67 |
68 |
69 | ); 70 | }; 71 | -------------------------------------------------------------------------------- /client/src/pages/PrivacyPolicy/PrivacyPolicy.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | background-color: var(--white); 3 | display: flex; 4 | flex-direction: column; 5 | gap: 1rem; 6 | height: 100vh; 7 | position: relative; 8 | } 9 | 10 | .top__bar { 11 | background-color: var(--purple); 12 | font-size: 14px; 13 | display: flex; 14 | align-items: center; 15 | gap: 1rem; 16 | padding: 1rem; 17 | color: var(--white); 18 | } 19 | 20 | .page_header { 21 | background-color: var(--grey); 22 | font-size: 14px; 23 | display: flex; 24 | justify-content: space-between; 25 | align-items: center; 26 | padding: 1rem; 27 | line-height: 0.875rem; 28 | } 29 | 30 | .page_topic { 31 | color: #424242; 32 | font-size: 1rem; 33 | font-weight: 600; 34 | } 35 | 36 | .update_time { 37 | font-size: 0.5rem; 38 | font-weight: 400; 39 | font-size: 0.5rem; 40 | color: #868686; 41 | } 42 | .article_container { 43 | width: 100%; 44 | display: flex; 45 | flex-direction: column; 46 | gap: 1.5rem; 47 | font-size: 14px; 48 | padding: 1rem; 49 | line-height: 23.5px; 50 | letter-spacing: 0.44px; 51 | } 52 | 53 | 54 | .article_container h2 { 55 | font-size: 1rem; 56 | font-weight: 600; 57 | margin-bottom: 0.5rem; 58 | } 59 | 60 | .list { 61 | margin-left: 1rem; 62 | } 63 | 64 | .last_paragraph { 65 | margin-top: 1.2rem; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /client/src/pages/Settings/Settings.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100vh; 5 | width: 100%; 6 | } 7 | 8 | .container > *:last-child { 9 | display: flex; 10 | flex-direction: column; 11 | } 12 | -------------------------------------------------------------------------------- /client/src/pages/Settings/Settings.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './Settings.module.css'; 3 | import { Header } from '../../components'; 4 | import { Route, Routes } from 'react-router-dom'; 5 | import { SettingsHome, Wallet } from '../../components'; 6 | import { LeftArrow, LogOut } from '../../assets/icons'; 7 | export const Settings = () => { 8 | return ( 9 |
10 |
17 | 18 | 19 | } /> 20 | } /> 21 | 22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /client/src/pages/ShareLocation/ShareLocation.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import styles from './ShareLocation.module.css'; 3 | import { logo } from '../../assets/images/index'; 4 | import { logo__noBg } from '../../assets/images/index'; 5 | import { GrFormClose } from '../../assets/icons'; 6 | import { WorldIcon } from '../../assets/icons'; 7 | import { useNavigate } from 'react-router-dom'; 8 | import { AnimatePresence, motion } from 'framer-motion'; 9 | 10 | export const ShareLocation = () => { 11 | const [showModal, setShowModal] = useState(false); 12 | const navigate = useNavigate(); 13 | const shareLocationHandler = () => { 14 | setShowModal(true); 15 | }; 16 | return ( 17 |
18 |
19 | 20 | Profile Photo 21 | Pailot 22 | 23 |
24 |

Explore local stores, couriers, warehouse and logistics

25 |
26 | World Icon 27 |

28 | Help Pailot display your local delivery options by granting location 29 | permission 30 |

31 |
32 |
33 | 36 | 37 |
38 | {showModal && ( 39 |
40 | 49 |
50 | setShowModal(false)} /> 51 |
52 |
53 | Profile Photo 54 |

55 | Your location helps us better the delivery process from our end to you end as well 56 | provide you the quickest delivery options for you 57 |

58 |
59 |
60 | 67 |
68 |
69 | 70 | {' '} 76 | 77 |
78 | )} 79 |
80 | ); 81 | }; 82 | -------------------------------------------------------------------------------- /client/src/pages/SplashScreen/SplashScreen.module.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/client/src/pages/SplashScreen/SplashScreen.module.css -------------------------------------------------------------------------------- /client/src/pages/SplashScreen/SplashScreen.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { ScreenOne, ScreenTwo } from '../../components'; 3 | import { SplashScreenState } from '../../types/SplashScreenState'; 4 | export const SplashScreen = () => { 5 | const [nextScreen, setNextScreen] = useState('first_screen'); 6 | 7 | return ( 8 | <> 9 | {nextScreen == 'first_screen' && } 10 | {nextScreen == 'progress_bar' && } 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /client/src/pages/TermsAndConditions/TermsAndConditions.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | background-color: var(--white); 3 | display: flex; 4 | flex-direction: column; 5 | gap: 1rem; 6 | height: 100vh; 7 | position: relative; 8 | } 9 | 10 | .top__bar { 11 | background-color: var(--purple); 12 | font-size: 14px; 13 | display: flex; 14 | align-items: center; 15 | gap: 1rem; 16 | padding: 1rem; 17 | color: var(--white); 18 | } 19 | 20 | .page_header { 21 | background-color: var(--grey); 22 | font-size: 14px; 23 | display: flex; 24 | justify-content: space-between; 25 | align-items: center; 26 | padding: 1rem; 27 | line-height: 0.875rem; 28 | } 29 | 30 | .page_topic { 31 | color: #424242; 32 | font-size: 1rem; 33 | font-weight: 600; 34 | } 35 | 36 | .update_time { 37 | font-size: 0.5rem; 38 | font-weight: 400; 39 | font-size: 0.5rem; 40 | color: #868686; 41 | } 42 | .article_container { 43 | width: 100%; 44 | display: flex; 45 | flex-direction: column; 46 | gap: 1.5rem; 47 | font-size: 14px; 48 | padding: 1rem; 49 | line-height: 23.5px; 50 | letter-spacing: 0.44px; 51 | } 52 | 53 | .article_container h2 { 54 | text-transform: uppercase; 55 | font-size: 1rem; 56 | font-weight: 600; 57 | margin-bottom: 0.5rem; 58 | } 59 | 60 | .article_container h3 { 61 | font-size: 1rem; 62 | font-weight: 600; 63 | margin-top: 0.5rem; 64 | margin-bottom: 0.5rem; 65 | } 66 | 67 | .article_container h4 { 68 | font-size: 0.9rem; 69 | margin-top: 0.5rem; 70 | margin-bottom: 0.5rem; 71 | } 72 | 73 | .list { 74 | margin-left: 1rem; 75 | } 76 | 77 | .last_paragraph { 78 | margin-top: 1.2rem; 79 | } 80 | -------------------------------------------------------------------------------- /client/src/pages/WelcomeScreen/Welcome.module.css: -------------------------------------------------------------------------------- 1 | #wrapper { 2 | height: 100vh; 3 | background-color: var(--purple); 4 | display: flex; 5 | flex-direction: column; 6 | position: relative; 7 | } 8 | -------------------------------------------------------------------------------- /client/src/pages/WelcomeScreen/Welcome.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './Welcome.module.css'; 3 | import { AllowPi } from '../../components'; 4 | import { motion } from 'framer-motion'; 5 | 6 | export const WelcomeScreen = () => { 7 | return ( 8 | 14 | 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /client/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export { Home } from './Home/Home'; 2 | export { SplashScreen } from './SplashScreen/SplashScreen'; 3 | export { WelcomeScreen } from './WelcomeScreen/Welcome'; 4 | export { Settings } from './Settings/Settings'; 5 | export { ShareLocation } from './ShareLocation/ShareLocation'; 6 | export { OnboardingCompleted } from './OnboardingCompleted/OnboardingCompleted'; 7 | export { CustomizedDelivery } from './CustomizedDelivery/CustomizedDelivery'; 8 | export { ActiveDelivery } from './ActiveDelivery/ActiveDelivery'; 9 | export { CourierForm } from './CourierForm/CourierForm'; 10 | export { CourierDashBoard } from './CourierDashBoard/CourierDashBoard'; 11 | export { PrivacyPolicy } from './PrivacyPolicy/PrivacyPolicy'; 12 | export { TermsAndConditions } from './TermsAndConditions/TermsAndCondition'; 13 | -------------------------------------------------------------------------------- /client/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /client/src/typedefs.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | declare interface Window { 3 | Pi: any; 4 | } 5 | -------------------------------------------------------------------------------- /client/src/types/SplashScreenState.ts: -------------------------------------------------------------------------------- 1 | export type SplashScreenState = 'first_screen' | 'progress_bar'; 2 | -------------------------------------------------------------------------------- /client/src/types/payment.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { ITransaction } from "./transaction"; 3 | 4 | export interface PaymentDTO { 5 | identifier: string; 6 | user_uid: string; 7 | amount: number; 8 | memo: string; 9 | metadata: object; 10 | from_address: string; 11 | to_address: string; 12 | direction: 'user_to_app' | 'app_to_user'; 13 | created_at: string; 14 | network: string; 15 | status: { 16 | developer_approved: boolean; 17 | transaction_verified: boolean; 18 | developer_completed: boolean; 19 | cancelled: boolean; 20 | user_cancelled: boolean; 21 | }; 22 | transaction: null | { 23 | txid: string; 24 | verified: boolean; 25 | _link: string; 26 | }; 27 | } 28 | 29 | export interface IEarning { 30 | id: string; 31 | delivery: ITransaction; 32 | paymentId: string; 33 | amount: number; 34 | paymentStatus: PaymentStatus; 35 | transactionId: string; 36 | } 37 | 38 | export enum PaymentStatus { 39 | CREATED = 'created', 40 | SUBMITTED = 'submitted', 41 | CANCELLED = 'cancelled', 42 | COMPLETED = 'completed', 43 | } 44 | -------------------------------------------------------------------------------- /client/src/types/transaction.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IEarning } from "./payment"; 3 | import { ICourier, IUser } from "./user"; 4 | 5 | export interface ITransaction { 6 | id: string; 7 | trackingNumber: number | null; 8 | senderUserId: IUser; 9 | courierUserId: ICourier | null; 10 | receiverUserId: IUser; 11 | preferredModeOfDelivery: string; 12 | fromAddress: string; 13 | toAddress: string; 14 | imagePublicId: string; 15 | itemImage: string; 16 | itemName: string; 17 | itemDescription: string; 18 | itemWeight: number; 19 | itemSize: number; 20 | transactionAmount: number; 21 | deliveryStatus: DeliveryStatus; 22 | itemCategory: ItemCategory; 23 | deliveryRange: DeliveryRange; 24 | estimatedDeliveryTime: Date | null; 25 | pickupDate: Date | null; 26 | deliveryDate: Date; 27 | deletedDate: string | null; 28 | deliveryCode: number; 29 | paymentId: IEarning | null; 30 | } 31 | 32 | export enum DeliveryStatus { 33 | CREATED = 'Created', 34 | PENDING = 'Pending', 35 | ACCEPTED = 'Accepted', 36 | REJECTED = 'Rejected', 37 | PICKED_UP = 'Picked up', 38 | IN_TRANSIT = 'In Transit', 39 | DELIVERED = 'Delivered', 40 | } 41 | 42 | export enum DeliveryRange { 43 | LOCAL_DELIVERY = 'Local Delivery', 44 | INTER_STATE = 'Inter State', 45 | } 46 | 47 | export enum ItemCategory { 48 | FOOD_DELIVERY = 'Food delivery', 49 | ELECTRONICS = 'Electronics', 50 | PHONES_AND_COMPUTERS = 'Phone and Computers', 51 | GROCERIES = 'Groceries', 52 | FURNITURES = 'Furnitures', 53 | FASHION = 'Fashion', 54 | BABY_PRODUCTS = 'Baby products', 55 | AUTOMOBILE = 'Automobile', 56 | OTHERS = 'Others', 57 | } 58 | -------------------------------------------------------------------------------- /client/src/types/user.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export interface IUser { 3 | userUid: string; 4 | username: string; 5 | walletAddress: string | null; 6 | profileImg: string | null; 7 | accessToken: string; 8 | imagePublicId: string | null; 9 | userRole?: UserRole; 10 | } 11 | 12 | export interface ICourier { 13 | courierUserId: string; 14 | numberOfLikes: number; 15 | rating: number; 16 | modeOfTransportation: string; 17 | regionOfOperation: string; 18 | preferredDeliveryAmount: number; 19 | isActive: boolean; 20 | earnings: number; 21 | startTime?: string; 22 | endTime?: string; 23 | } 24 | 25 | export enum UserRole { 26 | USER = 1, 27 | COURIER = 2, 28 | } 29 | 30 | export interface UpdateCourierDTO { 31 | modeOfTransportation?: string; 32 | regionOfOperation?: string; 33 | preferredDeliveryAmount?: number; 34 | numberOfLikes?: number; 35 | rating?: number; 36 | country?: string; 37 | earnings?: number; 38 | } 39 | 40 | export interface UpdateUserDTO { 41 | walletAddress?: string; 42 | profileImg?: string; 43 | accessToken?: string; 44 | } 45 | 46 | export type CreateUserDTO = { 47 | user: { 48 | uid: string; 49 | username: string; 50 | }; 51 | accessToken: string; 52 | }; 53 | 54 | export interface IUserCourier { 55 | id: string; 56 | user: IUser; 57 | courier: ICourier | null; 58 | } 59 | -------------------------------------------------------------------------------- /client/src/utils/paymentsCallback.ts: -------------------------------------------------------------------------------- 1 | import { CANCELLED_PAYMENT_URL, COMPLETE_PAYMENT_URL, INCOMPLETE_PAYMENT_URL } from "../constants/url.constants"; 2 | import { fetchWithCredentials } from "../hooks/useApi"; 3 | import { PaymentDTO } from "../types/payment"; 4 | 5 | export const onIncompletePaymentFound = async (payment: PaymentDTO) => { 6 | console.log('onIncompletePaymentFound', payment); 7 | return await fetchWithCredentials(INCOMPLETE_PAYMENT_URL, { 8 | method: 'POST', 9 | data: { payment }, 10 | }); 11 | }; 12 | 13 | export const onReadyForServerCompletion = async (paymentId: string, txid: string) => { 14 | console.log('onReadyForServerCompletion', paymentId, txid); 15 | return await fetchWithCredentials(COMPLETE_PAYMENT_URL, { 16 | method: 'POST', 17 | data: { paymentId, txid }, 18 | }); 19 | }; 20 | 21 | export const onCancel = async (paymentId: string) => { 22 | console.log('onCancel', paymentId); 23 | return await fetchWithCredentials(CANCELLED_PAYMENT_URL, { 24 | method: 'POST', 25 | data: { paymentId }, 26 | }); 27 | }; 28 | 29 | export const onError = (error: Error, payment?: PaymentDTO) => { 30 | console.log('onError', error.stack); 31 | console.log('onError', error.message); 32 | if (payment) { 33 | console.log(payment); 34 | // handle the error accordingly 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /client/src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | export const convertStringToNumber = (value: string) => { 2 | return Number(value.split(' ')[0]); 3 | } 4 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /server/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = tab 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [test/**/expected.css] 12 | insert_final_newline = false 13 | 14 | [{swagger.json, swagger_v3.json, package.json,.travis.yml,.eslintrc.json}] 15 | indent_style = space 16 | -------------------------------------------------------------------------------- /server/.env.example: -------------------------------------------------------------------------------- 1 | # Get this on the Pi Developer Portal (develop.pi in the Pi Browser) 2 | PI_API_KEY= 3 | 4 | # Platform API 5 | PLATFORM_API_URL=https://api.minepi.com 6 | 7 | # Session secret 8 | SESSION_SECRET='pailot development secret' 9 | # Database credentials 10 | DATABASE_HOST=localhost 11 | DATABASE_TYPE=postgres 12 | DATABASE_USERNAME=pailot_development 13 | DATABASE_PASSWORD=password 14 | DATABASE_PORT=5432 15 | DATABASE_NAME=pailot_development 16 | 17 | #cloudinary keys 18 | #paste the keys here 19 | CLOUDINARY_NAME= 20 | CLOUDINARY_API_KEY= 21 | CLOUDINARY_API_SECRET= 22 | 23 | # Twilio keys 24 | TWILIO_ACCOUNT_SID= 25 | TWILIO_AUTH_TOKEN= 26 | TWILIO_OTP_VERIFICATION_SID= 27 | -------------------------------------------------------------------------------- /server/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir : __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js', 'schema.ts'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | /log/* 4 | !/log/.gitkeep 5 | .env 6 | .env.local 7 | .env.development -------------------------------------------------------------------------------- /server/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "endOfLine": "lf", 5 | "printWidth": 100, 6 | "tabWidth": 2, 7 | "useTabs": true 8 | } 9 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # Pailot Backend 2 | 3 | This is a Node.js application built with TypeScript 4 | 5 | # Getting Started 6 | 7 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. 8 | 9 | # Requirement 10 | 11 | To run this application, you need to have the following software installed on your system: 12 | 13 | Node.js (version 12 or higher) 14 | npm/yarn 15 | 16 | # Installation 17 | 18 | Clone the repository: 19 | 20 | ```bash 21 | git clone https://github.com/pi-apps/pailot-for-pi.git 22 | 23 | ``` 24 | 25 | Navigate to the project directory: 26 | 27 | ```bash 28 | cd server 29 | 30 | ``` 31 | 32 | Install the dependencies: 33 | 34 | ```bash 35 | npm install OR yarn install 36 | 37 | ``` 38 | 39 | # Compiling the TypeScript code 40 | 41 | To compile the TypeScript code, run the following command in the terminal: 42 | 43 | ```bash 44 | npm run build OR yarn build 45 | 46 | ``` 47 | 48 | # Running the application 49 | 50 | To start the application, run the following command in the terminal: 51 | 52 | ```bash 53 | npm start OR yarn start 54 | 55 | ``` 56 | 57 | The application should now be running at: 58 | 59 | "http://localhost:3333" 60 | 61 | 62 | # Built with 63 | - Node.js: The JavaScript runtime 64 | - TypeScript: The typed superset of JavaScript 65 | - npm: The package manager for Node.js 66 | - yarn: Fast, reliable and secure dependency management 67 | -------------------------------------------------------------------------------- /server/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | database: 4 | image: postgres:13.6 5 | volumes: 6 | - postgres-db:/data/postgres 7 | environment: 8 | - POSTGRES_USER=pailot_development 9 | - POSTGRES_PASSWORD=password 10 | env_file: 11 | - .env 12 | ports: 13 | - "5432:5432" 14 | volumes: 15 | postgres-db: 16 | -------------------------------------------------------------------------------- /server/log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pi-apps/pailot-for-pi/70b7124412e543fa4ccfbcc34205d863c281bd2d/server/log/.gitkeep -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pailot", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "description": "Pailot is a decentralized delivery service application that is focused on piloting pioneers into full decentralized logistics, food, healthcare, etc.", 6 | "license": "~propriety~", 7 | "private": "true", 8 | "scripts": { 9 | "prebuild": "rimraf build", 10 | "build": "tsc --project ./tsconfig.build.json", 11 | "build:watch": "tsc --watch --project ./tsconfig.build.json", 12 | "prestart": "npm run build", 13 | "db:create": "ts-node ./node_modules/typeorm-extension/dist/cli/index.js db:create", 14 | "start:db": "docker-compose up database", 15 | "start:dev": "nodemon build/index", 16 | "start": "node build/index", 17 | "dev": "concurrently --raw -n \"tsc,server\" -s \"last\" -c \"magenta,yellow\" \"yarn start:db\" \"yarn build:watch\" \"yarn start:dev\"", 18 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 19 | "format:write": "prettier --write \"src/**/*.ts\"", 20 | "format:check": "prettier --check \"src/**/*.ts\"" 21 | }, 22 | "dependencies": { 23 | "axios": "^1.2.3", 24 | "cloudinary": "^1.33.0", 25 | "cors": "^2.8.5", 26 | "crypto-random-string": "^5.0.0", 27 | "dotenv": "^16.0.3", 28 | "express": "^4.18.2", 29 | "jsonwebtoken": "^9.0.0", 30 | "morgan": "^1.10.0", 31 | "multer": "^1.4.5-lts.1", 32 | "pg": "^8.9.0", 33 | "pi-backend": "^0.1.3", 34 | "reflect-metadata": "^0.1.13", 35 | "typeorm": "^0.3.11", 36 | "typeorm-extension": "^2.4.2" 37 | }, 38 | "devDependencies": { 39 | "@types/cors": "^2.8.13", 40 | "@types/express": "^4.17.15", 41 | "@types/jsonwebtoken": "^9.0.1", 42 | "@types/morgan": "^1.9.4", 43 | "@types/multer": "^1.4.7", 44 | "@types/node": "^18.11.18", 45 | "@typescript-eslint/eslint-plugin": "^5.48.2", 46 | "@typescript-eslint/parser": "^5.48.2", 47 | "concurrently": "^7.6.0", 48 | "eslint": "^8.32.0", 49 | "eslint-config-prettier": "^8.6.0", 50 | "eslint-plugin-prettier": "^4.2.1", 51 | "nodemon": "^2.0.20", 52 | "prettier": "^2.8.3", 53 | "rimraf": "^4.1.1", 54 | "ts-node": "^10.9.1", 55 | "typescript": "^4.9.4" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /server/src/constants/environments.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | 3 | console.log('NODE_ENV: ' + process.env.NODE_ENV); 4 | 5 | const result = dotenv.config(); 6 | 7 | if (result.error) { 8 | if (process.env.NODE_ENV === 'development') { 9 | console.error( 10 | '.env file not found. This is an error condition in development. Additional error is logged below' 11 | ); 12 | throw result.error; 13 | } 14 | } 15 | 16 | interface Environment { 17 | PI_API_KEY: string; 18 | PLATFORM_API_URL: string; 19 | WALLET_PRIVATE_SEED: string; 20 | SESSION_SECRET: string; 21 | DATABASE_HOST: string; 22 | DATABASE_TYPE: string; 23 | DATABASE_USERNAME: string; 24 | DATABASE_PASSWORD: string; 25 | DATABASE_PORT: number; 26 | DATABASE_NAME: string; 27 | CLOUDINARY_NAME: string; 28 | CLOUDINARY_API_KEY: string; 29 | CLOUDINARY_API_SECRET: string; 30 | TWILIO_ACCOUNT_SID: string; 31 | TWILIO_AUTH_TOKEN: string; 32 | TWILIO_OTP_VERIFICATION_SID: string; 33 | } 34 | 35 | const env: Environment = { 36 | PI_API_KEY: process.env.PI_API_KEY || '', 37 | PLATFORM_API_URL: process.env.PLATFORM_API_URL || '', 38 | WALLET_PRIVATE_SEED: process.env.WALLET_PRIVATE_SEED || '', 39 | SESSION_SECRET: process.env.SESSION_SECRET || 'pailot development secret', 40 | DATABASE_HOST: process.env.DATABASE_HOST || 'localhost', 41 | DATABASE_TYPE: process.env.DATABASE_TYPE || 'postgres', 42 | DATABASE_USERNAME: process.env.DATABASE_USERNAME || 'pailot_development', 43 | DATABASE_PASSWORD: process.env.DATABASE_PASSWORD || 'password', 44 | DATABASE_PORT: parseInt(process.env.DATABASE_PORT) || 5432, 45 | DATABASE_NAME: process.env.DATABASE_NAME || 'pailot_development', 46 | CLOUDINARY_NAME: process.env.CLOUDINARY_NAME || '', 47 | CLOUDINARY_API_KEY: process.env.CLOUDINARY_API_KEY || '', 48 | CLOUDINARY_API_SECRET: process.env.CLOUDINARY_API_SECRET || '', 49 | TWILIO_ACCOUNT_SID: process.env.TWILIO_ACCOUNT_SID || '', 50 | TWILIO_AUTH_TOKEN: process.env.TWILIO_AUTH_TOKEN || '', 51 | TWILIO_OTP_VERIFICATION_SID: process.env.TWILIO_OTP_VERIFICATION_SID || '', 52 | }; 53 | 54 | export default env; 55 | -------------------------------------------------------------------------------- /server/src/constants/result.ts: -------------------------------------------------------------------------------- 1 | export enum Result { 2 | SUCCESS = 'SUCCESS', 3 | NOT_FOUND = 'NOT_FOUND', 4 | ERROR = 'ERROR', 5 | } 6 | -------------------------------------------------------------------------------- /server/src/db/dataSource.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { DataSource } from 'typeorm'; 3 | import env from '../constants/environments'; 4 | import { Courier } from './entity/Courier'; 5 | import { Transaction } from './entity/Transaction'; 6 | import { Earning } from './entity/Earning'; 7 | import { Payout } from './entity/Payout'; 8 | import { User } from './entity/User'; 9 | import { UserCourier } from './entity/UserCourier'; 10 | 11 | export const AppDataSource = new DataSource({ 12 | type: 'postgres', 13 | host: env.DATABASE_HOST, 14 | port: env.DATABASE_PORT, 15 | username: env.DATABASE_USERNAME, 16 | password: env.DATABASE_PASSWORD, 17 | database: env.DATABASE_NAME, 18 | synchronize: false, 19 | logging: true, 20 | entities: [User, Courier, Transaction, Payout, Earning, UserCourier], 21 | migrations: [], 22 | subscribers: [], 23 | }); 24 | -------------------------------------------------------------------------------- /server/src/db/entity/Courier.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryColumn, Column, OneToMany } from 'typeorm'; 2 | import { Transaction } from './Transaction'; 3 | 4 | @Entity({ name: 'couriers' }) 5 | export class Courier { 6 | @PrimaryColumn('uuid', { name: 'courier_user_id' }) 7 | courierUserId: string; 8 | 9 | @OneToMany(() => Transaction, (transaction) => transaction.courierUserId) 10 | deliveries: Transaction[]; 11 | 12 | @Column({ type: 'bigint', name: 'number_of_likes', default: 0 }) 13 | numberOfLikes: number; 14 | 15 | @Column({ type: 'float4', name: 'rating', default: 0.0 }) 16 | rating: number; 17 | 18 | @Column({ type: 'varchar', length: 255, name: 'is_active', default: true }) 19 | isActive: boolean; 20 | 21 | @Column({ type: 'varchar', length: 255, name: 'mode_of_transportation' }) 22 | modeOfTransportation: string; 23 | 24 | @Column({ type: 'varchar', length: 255, name: 'region_of_operation' }) 25 | regionOfOperation: string; 26 | 27 | @Column({ type: 'float', name: 'preferred_delivery_amount', default: 0 }) 28 | preferredDeliveryAmount: number; 29 | 30 | @Column({ type: 'varchar', name: 'start_time', nullable: true }) 31 | startTime: string; 32 | 33 | @Column({ type: 'varchar', name: 'end_time', nullable: true }) 34 | endTime: string; 35 | 36 | @Column({ type: 'float', name: 'earnings', default: 0 }) 37 | earnings: number; 38 | } 39 | -------------------------------------------------------------------------------- /server/src/db/entity/Earning.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, OneToOne } from 'typeorm'; 2 | import { PaymentStatus } from '../../interfaces/payment'; 3 | import { Transaction } from './Transaction'; 4 | 5 | @Entity({ name: 'earnings' }) 6 | export class Earning { 7 | @PrimaryGeneratedColumn('uuid') 8 | id: string; 9 | 10 | @OneToOne(() => Transaction) 11 | delivery: Transaction; 12 | 13 | @Column({ type: 'varchar', length: 255, name: 'payment_id', nullable: true }) 14 | paymentId: string; 15 | 16 | @Column({ type: 'float' }) 17 | amount: number; 18 | 19 | @Column({ 20 | type: 'enum', 21 | name: 'payment_status', 22 | enum: PaymentStatus, 23 | default: PaymentStatus.CREATED, 24 | }) 25 | paymentStatus: PaymentStatus; 26 | 27 | @Column({ type: 'varchar', name: 'transaction_id', nullable: true }) 28 | transactionId: string; 29 | } 30 | -------------------------------------------------------------------------------- /server/src/db/entity/Payout.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, ManyToOne } from 'typeorm'; 2 | import { PaymentStatus } from '../../interfaces/payment'; 3 | import { Courier } from './Courier'; 4 | 5 | @Entity({ name: 'payouts' }) 6 | export class Payout { 7 | @PrimaryGeneratedColumn('uuid') 8 | id: string; 9 | 10 | @ManyToOne(() => Courier) 11 | @JoinColumn({ name: 'courier_user_id' }) 12 | courierUserId: Courier; 13 | 14 | @Column({ type: 'varchar', length: 255, name: 'payment_id', nullable: true }) 15 | PaymentId: string; 16 | 17 | @Column({ type: 'bigint' }) 18 | amount: number; 19 | 20 | @Column({ 21 | type: 'enum', 22 | enum: PaymentStatus, 23 | name: 'payment_status', 24 | default: PaymentStatus.CREATED, 25 | }) 26 | paymentStatus: PaymentStatus; 27 | 28 | @Column({ type: 'varchar', name: 'transaction_id', nullable: true }) 29 | transactionId: string; 30 | } 31 | -------------------------------------------------------------------------------- /server/src/db/entity/Transaction.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, ManyToOne, JoinColumn, PrimaryGeneratedColumn, OneToOne } from 'typeorm'; 2 | import { DeliveryRange, DeliveryStatus, ItemCategory } from '../../interfaces/transaction'; 3 | import { Courier } from './Courier'; 4 | import { Earning } from './Earning'; 5 | import { User } from './User'; 6 | 7 | @Entity({ name: 'transactions' }) 8 | export class Transaction { 9 | @PrimaryGeneratedColumn('uuid') 10 | id: string; 11 | 12 | @Column({ type: 'bigint', name: 'tracking_number', nullable: true }) 13 | trackingNumber: number; // Generate a tracking number when the delivery is accepted. 14 | 15 | @ManyToOne(() => User, (user) => user.userUid) 16 | @JoinColumn({ name: 'sender_user_id' }) 17 | senderUserId: User; 18 | 19 | @ManyToOne(() => Courier, (courierData) => courierData.deliveries, { 20 | nullable: true, 21 | }) 22 | @JoinColumn({ name: 'courier_user_id' }) 23 | courierUserId: Courier; 24 | 25 | @ManyToOne(() => User, (user) => user.userUid) 26 | @JoinColumn({ name: 'receiver_user_id' }) 27 | receiverUserId: User; 28 | 29 | @Column({ type: 'varchar', length: 255, name: 'preferred_mode_of_delivery' }) 30 | preferredModeOfDelivery: string; 31 | 32 | @Column({ type: 'float', name: 'transaction_amount', default: 0 }) 33 | transactionAmount: number; 34 | 35 | @Column({ type: 'varchar', name: 'from_address' }) 36 | fromAddress: string; 37 | 38 | @Column({ type: 'varchar', name: 'to_address' }) 39 | toAddress: string; 40 | 41 | @Column({ name: 'item_image', type: 'varchar' }) 42 | itemImage: string; 43 | 44 | @Column({ name: 'image_public_id', type: 'varchar' }) 45 | imagePublicId: string; 46 | 47 | @Column({ type: 'varchar', name: 'item_name' }) 48 | itemName: string; 49 | 50 | @Column({ type: 'varchar', name: 'item_description' }) 51 | itemDescription: string; 52 | 53 | @Column({ type: 'float', name: 'item_weight' }) 54 | itemWeight: number; 55 | 56 | @Column({ type: 'float', name: 'item_size' }) 57 | itemSize: number; 58 | 59 | @Column({ 60 | type: 'enum', 61 | enum: DeliveryStatus, 62 | name: 'delivery_status', 63 | default: DeliveryStatus.CREATED, 64 | }) 65 | deliveryStatus: DeliveryStatus; 66 | 67 | @Column({ 68 | type: 'enum', 69 | enum: ItemCategory, 70 | name: 'item_category', 71 | }) 72 | itemCategory: ItemCategory; 73 | 74 | @Column({ 75 | type: 'enum', 76 | enum: DeliveryRange, 77 | name: 'delivery_range', 78 | nullable: true, 79 | }) 80 | deliveryRange: DeliveryRange; 81 | 82 | @Column({ type: 'date', nullable: true, name: 'estimated_delivery_time' }) 83 | estimatedDeliveryTime: Date; 84 | 85 | @Column({ type: 'date', nullable: true, name: 'pickup_date' }) 86 | pickupDate: Date; 87 | 88 | @Column({ type: 'date', name: 'delivery_date', nullable: true }) 89 | deliveryDate: Date; 90 | 91 | @Column({ type: 'varchar', length: 50, name: 'deleted_date', nullable: true }) 92 | deletedDate: string; 93 | 94 | @Column({ type: 'int', name: 'delivery_code', unique: true, nullable: true }) 95 | deliveryCode: number; 96 | 97 | @OneToOne(() => Earning, (earning) => earning.delivery, { 98 | nullable: true, 99 | }) 100 | @JoinColumn({ name: 'payment_id' }) 101 | paymentId: Earning; 102 | } 103 | -------------------------------------------------------------------------------- /server/src/db/entity/User.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryColumn, Column, OneToMany } from 'typeorm'; 2 | import { UserRole } from '../../interfaces/user'; 3 | import { Payout } from './Payout'; 4 | import { Transaction } from './Transaction'; 5 | 6 | @Entity({ name: 'users' }) 7 | export class User { 8 | @PrimaryColumn('uuid', { name: 'user_uid' }) 9 | userUid: string; 10 | 11 | @Column({ type: 'varchar', length: 255 }) 12 | username: string; 13 | 14 | @Column({ name: 'wallet_address', type: 'varchar', length: 255, nullable: true }) 15 | walletAddress: string; 16 | 17 | @Column({ name: 'user_role', enum: UserRole, type: 'enum', default: UserRole.USER }) 18 | userRole: UserRole; 19 | 20 | @Column({ name: 'image_public_id', type: 'varchar', nullable: true }) 21 | imagePublicId: string; 22 | 23 | @Column({ name: 'profile_img', type: 'varchar', nullable: true }) 24 | profileImg: string; 25 | 26 | @Column({ name: 'access_token', type: 'varchar', length: 255, unique: true }) 27 | accessToken: string; 28 | 29 | @OneToMany(() => Transaction, (transaction) => transaction.senderUserId) 30 | senderUserId: Transaction; 31 | 32 | @OneToMany(() => Transaction, (transaction) => transaction.receiverUserId) 33 | receiverUserId: Transaction; 34 | 35 | @OneToMany(() => Payout, (payout) => payout.courierUserId) 36 | courierUserId: Payout; 37 | } 38 | -------------------------------------------------------------------------------- /server/src/db/entity/UserCourier.ts: -------------------------------------------------------------------------------- 1 | import { Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { Courier } from './Courier'; 3 | import { User } from './User'; 4 | 5 | @Entity({ name: 'user_couriers' }) 6 | export class UserCourier { 7 | @PrimaryGeneratedColumn('uuid') 8 | id: string; 9 | 10 | @OneToOne(() => User, (user) => user.userUid, { 11 | eager: true, 12 | cascade: true, 13 | nullable: true, 14 | }) 15 | @JoinColumn({ name: 'user' }) 16 | user: User; 17 | 18 | @OneToOne(() => Courier, (courier) => courier.courierUserId, { 19 | eager: true, 20 | cascade: true, 21 | nullable: true, 22 | }) 23 | @JoinColumn({ name: 'courier' }) 24 | courier: Courier; 25 | } 26 | -------------------------------------------------------------------------------- /server/src/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import express from 'express'; 4 | import logger from 'morgan'; 5 | import { corsMiddleware } from './middlewares/cors'; 6 | import { apiRouter } from './modules/router'; 7 | import { AppDataSource } from './db/dataSource'; 8 | 9 | const PORT = process.env.PORT || 3333; 10 | const app = express(); 11 | 12 | app.use(express.json()); 13 | 14 | // Log requests to the console in a compact format: 15 | app.use(logger('dev')); 16 | 17 | // Full log of all requests to /log/access.log: 18 | app.use( 19 | logger('common', { 20 | stream: fs.createWriteStream(path.join(__dirname, '..', 'log', 'access.log'), { flags: 'a' }), 21 | }) 22 | ); 23 | 24 | app.options('*', corsMiddleware); 25 | app.use(corsMiddleware); 26 | 27 | app.get('/', (req, res) => { 28 | res.status(200).json({ 29 | message: 'Hello World', 30 | }); 31 | }); 32 | 33 | app.use('/', apiRouter); 34 | 35 | AppDataSource.initialize() 36 | .then(() => { 37 | console.log('DataSource is initialized'); 38 | app.listen(PORT, () => { 39 | console.log(`App listening on port ${PORT}`); 40 | }); 41 | }) 42 | .catch((err) => { 43 | console.error('Error during DataSource initialization:', err); 44 | }); 45 | -------------------------------------------------------------------------------- /server/src/interfaces/payment.ts: -------------------------------------------------------------------------------- 1 | import { ITransaction } from './transaction'; 2 | import { ICourier } from './user'; 3 | 4 | export interface PaymentDTO { 5 | identifier: string; 6 | user_uid: string; 7 | amount: number; 8 | memo: string; 9 | metadata: object; 10 | from_address: string; 11 | to_address: string; 12 | direction: 'user_to_app' | 'app_to_user'; 13 | created_at: string; 14 | network: string; 15 | status: { 16 | developer_approved: boolean; 17 | transaction_verified: boolean; 18 | developer_completed: boolean; 19 | cancelled: boolean; 20 | user_cancelled: boolean; 21 | }; 22 | transaction: null | { 23 | txid: string; 24 | verified: boolean; 25 | _link: string; 26 | }; 27 | } 28 | 29 | export interface ApprovePaymentData { 30 | deliveryId: string; 31 | paymentId: string; 32 | amount: number; 33 | } 34 | 35 | export enum PaymentStatus { 36 | CREATED = 'created', 37 | SUBMITTED = 'submitted', 38 | CANCELLED = 'cancelled', 39 | COMPLETED = 'completed', 40 | } 41 | 42 | export interface IPayout { 43 | id: string; 44 | courierUserId: ICourier; 45 | PaymentId: string; 46 | amount: number; 47 | paymentStatus: PaymentStatus; 48 | transactionId: string; 49 | } 50 | 51 | export interface IEarning { 52 | id: string; 53 | delivery: ITransaction; 54 | paymentId: string; 55 | amount: number; 56 | paymentStatus: PaymentStatus; 57 | transactionId: string; 58 | } 59 | -------------------------------------------------------------------------------- /server/src/interfaces/result.ts: -------------------------------------------------------------------------------- 1 | import { Result } from '../constants/result'; 2 | 3 | export interface SuccessResult { 4 | type: Result.SUCCESS; 5 | data: T; 6 | } 7 | 8 | export interface NotFoundResult { 9 | type: Result.NOT_FOUND; 10 | message: string; 11 | } 12 | 13 | export interface ErrorResult { 14 | type: Result.ERROR; 15 | message: string; 16 | error?: unknown; 17 | } 18 | -------------------------------------------------------------------------------- /server/src/interfaces/transaction.ts: -------------------------------------------------------------------------------- 1 | import { IEarning } from './payment'; 2 | import { ICourier, IUser } from './user'; 3 | 4 | export interface ITransaction { 5 | id: string; 6 | trackingNumber: number | null; 7 | senderUserId: IUser; 8 | courierUserId: ICourier | null; 9 | receiverUserId: IUser; 10 | preferredModeOfDelivery: string; 11 | fromAddress: string; 12 | toAddress: string; 13 | imagePublicId: string; 14 | itemImage: string; 15 | itemName: string; 16 | itemDescription: string; 17 | itemWeight: number; 18 | itemSize: number; 19 | transactionAmount: number; 20 | deliveryStatus: DeliveryStatus; 21 | itemCategory: ItemCategory; 22 | deliveryRange: DeliveryRange; 23 | estimatedDeliveryTime: Date | null; 24 | pickupDate: Date | null; 25 | deliveryDate: Date; 26 | deletedDate: string | null; 27 | deliveryCode: number; 28 | paymentId: IEarning | null; 29 | } 30 | 31 | export interface CreateTransactionDTO { 32 | senderUserId: string; 33 | courierUserId?: string; 34 | receiverUsername: string; 35 | preferredModeOfDelivery?: string; 36 | fromAddress: string; 37 | toAddress: string; 38 | itemImage: string; 39 | itemName: string; 40 | itemDescription: string; 41 | itemWeight: number; 42 | itemSize: number; 43 | transactionAmount?: number; 44 | deliveryStatus: DeliveryStatus; 45 | itemCategory: ItemCategory; 46 | deliveryRange: DeliveryRange; 47 | fromState: string; 48 | toState: string | null; 49 | estimatedDeliveryTime?: Date; 50 | } 51 | 52 | export interface UpdateTransaction { 53 | courierId?: string; 54 | preferredModeOfDelivery?: string; 55 | fromAddress?: string; 56 | toAddress?: string; 57 | itemImage?: string; 58 | itemName?: string; 59 | itemDescription?: string; 60 | itemWeight?: number; 61 | itemSize?: number; 62 | transactionAmount?: number; 63 | itemCategory?: ItemCategory; 64 | deliveryRange?: DeliveryRange; 65 | estimatedDeliveryTime?: Date; 66 | pickupDate?: Date; 67 | deliveryDate?: Date; 68 | deliveryCode?: number; 69 | } 70 | 71 | export enum DeliveryStatus { 72 | CREATED = 'Created', 73 | PENDING = 'Pending', 74 | ACCEPTED = 'Accepted', 75 | REJECTED = 'Rejected', 76 | PICKED_UP = 'Picked up', 77 | IN_TRANSIT = 'In Transit', 78 | DELIVERED = 'Delivered', 79 | } 80 | 81 | export enum DeliveryRange { 82 | LOCAL_DELIVERY = 'Local Delivery', 83 | INTER_STATE = 'Inter State', 84 | } 85 | 86 | export enum ItemCategory { 87 | FOOD_DELIVERY = 'Food delivery', 88 | ELECTRONICS = 'Electronics', 89 | PHONES_AND_COMPUTERS = 'Phone and Computers', 90 | GROCERIES = 'Groceries', 91 | FURNITURES = 'Furnitures', 92 | FASHION = 'Fashion', 93 | BABY_PRODUCTS = 'Baby products', 94 | AUTOMOBILE = 'Automobile', 95 | OTHERS = 'Others', 96 | } 97 | -------------------------------------------------------------------------------- /server/src/interfaces/user.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | userUid: string; 3 | username: string; 4 | walletAddress: string | null; 5 | profileImg: string | null; 6 | accessToken: string; 7 | imagePublicId: string | null; 8 | userRole?: UserRole; 9 | } 10 | 11 | export interface ICourier { 12 | courierUserId: string; 13 | numberOfLikes: number; 14 | rating: number; 15 | modeOfTransportation: string; 16 | regionOfOperation: string; 17 | preferredDeliveryAmount: number; 18 | isActive: boolean; 19 | earnings: number; 20 | startTime?: string; 21 | endTime?: string; 22 | } 23 | 24 | export interface UpdateCourierDTO { 25 | modeOfTransportation?: string; 26 | regionOfOperation?: string; 27 | preferredDeliveryAmount?: number; 28 | numberOfLikes?: number; 29 | rating?: number; 30 | isActive?: boolean; 31 | earnings?: number; 32 | } 33 | 34 | export interface UpdateUserDTO { 35 | walletAddress?: string; 36 | profileImg?: string; 37 | accessToken?: string; 38 | userRole?: UserRole; 39 | } 40 | 41 | export type CreateUserDTO = { 42 | user: { 43 | uid: string; 44 | username: string; 45 | }; 46 | accessToken: string; 47 | }; 48 | 49 | export enum UserRole { 50 | USER = 1, 51 | COURIER = 2, 52 | } 53 | 54 | export interface IUserCourier { 55 | id: string; 56 | user: IUser; 57 | courier: ICourier | null; 58 | } 59 | -------------------------------------------------------------------------------- /server/src/middlewares/auth.ts: -------------------------------------------------------------------------------- 1 | import jwt, { Secret, JwtPayload } from 'jsonwebtoken'; 2 | import { Request, Response, NextFunction } from 'express'; 3 | import env from '../constants/environments'; 4 | 5 | export const SECRET_KEY: Secret = env.SESSION_SECRET; 6 | 7 | interface JWTToken extends JwtPayload { 8 | userUid: string; 9 | username: string; 10 | } 11 | 12 | export interface CustomRequest extends Request { 13 | token: JWTToken; 14 | } 15 | 16 | export const auth = async (req: Request, res: Response, next: NextFunction) => { 17 | try { 18 | const token = req.headers.authorization.split(' ')[1]; 19 | 20 | const decoded = jwt.verify(token, SECRET_KEY) as JWTToken; 21 | (req as CustomRequest).token = decoded; 22 | 23 | console.log('decoded', decoded); 24 | 25 | return next(); 26 | } catch (err) { 27 | console.log(err); 28 | return res.status(401).json({ message: 'Please authenticate' }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /server/src/middlewares/cloudinary.ts: -------------------------------------------------------------------------------- 1 | import cloudinary from 'cloudinary'; 2 | import env from '../constants/environments'; 3 | 4 | cloudinary.v2.config({ 5 | cloud_name: env.CLOUDINARY_NAME, 6 | api_key: env.CLOUDINARY_API_KEY, 7 | api_secret: env.CLOUDINARY_API_SECRET, 8 | secure: true, 9 | }); 10 | 11 | export const uploadeImageToCloudinary = async (path: string, options = {}) => { 12 | const result = await cloudinary.v2.uploader.upload(path, options); 13 | return { publicId: result.public_id, secureURL: result.secure_url }; 14 | }; 15 | -------------------------------------------------------------------------------- /server/src/middlewares/cors.ts: -------------------------------------------------------------------------------- 1 | import cors from 'cors'; 2 | 3 | let middleware = cors(); 4 | 5 | if (process.env.CORS_ALLOWED_ORIGINS) { 6 | const allowedOrigins: string[] = (process.env.CORS_ALLOWED_ORIGINS || '').split(','); 7 | if (allowedOrigins.length) { 8 | const corsOptions = { 9 | origin: (origin, callback) => { 10 | if (allowedOrigins.some((allowedOrigin) => allowedOrigin.startsWith(origin))) { 11 | callback(null, true); 12 | } else { 13 | callback(new Error('The Current Origin is not allowed by CORS')); 14 | } 15 | }, 16 | optionsSuccessStatus: 200, 17 | }; 18 | middleware = cors(corsOptions); 19 | } 20 | } 21 | 22 | export const corsMiddleware = middleware; 23 | -------------------------------------------------------------------------------- /server/src/middlewares/generateCode.ts: -------------------------------------------------------------------------------- 1 | import cryptoRandomString from 'crypto-random-string'; 2 | 3 | export type UniqueCodeType = 4 | | 'hex' 5 | | 'base64' 6 | | 'url-safe' 7 | | 'numeric' 8 | | 'distinguishable' 9 | | 'ascii-printable' 10 | | 'alphanumeric'; 11 | 12 | export const generateUniqueCode = (length: number, type: UniqueCodeType) => { 13 | const code = cryptoRandomString({ length, type }).toUpperCase(); 14 | return code; 15 | }; 16 | -------------------------------------------------------------------------------- /server/src/middlewares/multer.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | import multer from 'multer'; 3 | 4 | const acceptedMimeType = 'image/jpeg' || 'image/png' || 'image/jpg'; 5 | export const upload = multer({ 6 | storage: multer.diskStorage({}), 7 | fileFilter: (req, file, callback) => { 8 | if (file.mimetype !== acceptedMimeType) { 9 | callback(new Error('File type not supported')); 10 | } 11 | callback(null, true); 12 | }, 13 | }); 14 | 15 | export const profileImageUpload = upload.single('image'); 16 | 17 | export const multerUploadImage = (req: Request, res: Response, next: NextFunction) => { 18 | profileImageUpload(req, res, (err) => { 19 | if (err) return res.status(400).json({ error: 'invalid_file' }); 20 | }); 21 | next(); 22 | }; 23 | -------------------------------------------------------------------------------- /server/src/modules/payment/handlers/AccountToUserPayment.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { accountToUserPayment } from '../services/payment.services'; 4 | 5 | export async function withdrawPI(req: Request, res: Response) { 6 | const paymentData = req.body; 7 | const completedPayment = await accountToUserPayment(paymentData); 8 | 9 | if (completedPayment.type === Result.ERROR) { 10 | return res.status(500).json(completedPayment); 11 | } 12 | if (completedPayment.type === Result.NOT_FOUND) { 13 | return res.status(404).json(completedPayment); 14 | } 15 | return res.status(201).json(completedPayment); 16 | } 17 | -------------------------------------------------------------------------------- /server/src/modules/payment/handlers/approveU2APayment.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { approveUserToAppPayment } from '../services/payment.services'; 4 | 5 | export async function approveU2APayment(req: Request, res: Response) { 6 | const paymentData = req.body; 7 | 8 | const result = await approveUserToAppPayment(paymentData); 9 | if (result.type === Result.ERROR) { 10 | return res.status(500).json(result); 11 | } 12 | return res.status(200).json(result); 13 | } 14 | -------------------------------------------------------------------------------- /server/src/modules/payment/handlers/cancelledU2APayment.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { cancelledUserToAppPayment } from '../services/payment.services'; 4 | 5 | export async function cancelledU2APayment(req: Request, res: Response) { 6 | const paymentId = req.body.paymentId; 7 | const result = await cancelledUserToAppPayment(paymentId); 8 | 9 | if (result.type === Result.ERROR) { 10 | return res.status(500).json(result); 11 | } 12 | return res.status(200).json(result); 13 | } 14 | -------------------------------------------------------------------------------- /server/src/modules/payment/handlers/completeU2APayment.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { completeUserToAppPayment } from '../services/payment.services'; 4 | 5 | export async function completeU2APayment(req: Request, res: Response) { 6 | const paymentId = req.body.paymentId; 7 | const txid = req.body.txid; 8 | 9 | const completePaymentResult = await completeUserToAppPayment(paymentId, txid); 10 | if (completePaymentResult.type === Result.ERROR) { 11 | res.status(500).json(completePaymentResult); 12 | } 13 | res.status(200).json(completePaymentResult); 14 | } 15 | -------------------------------------------------------------------------------- /server/src/modules/payment/handlers/incompleteU2APayment.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { incompleteUserToAppPayment } from '../services/payment.services'; 4 | 5 | export async function incompleteU2APayment(req: Request, res: Response) { 6 | const payment = req.body.payment; 7 | const incompletePayment = await incompleteUserToAppPayment(payment); 8 | if (incompletePayment.type === Result.ERROR && incompletePayment.error) { 9 | return res.status(500).json(incompletePayment); 10 | } 11 | if (incompletePayment.type === Result.ERROR) { 12 | return res.status(400).json(incompletePayment); 13 | } 14 | if (incompletePayment.type === Result.NOT_FOUND) { 15 | return res.status(404).json(incompletePayment); 16 | } 17 | return res.status(200).json(incompletePayment); 18 | } 19 | -------------------------------------------------------------------------------- /server/src/modules/payment/payment.controller.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { approveU2APayment } from './handlers/approveU2APayment'; 3 | import { completeU2APayment } from './handlers/completeU2APayment'; 4 | import { incompleteU2APayment } from './handlers/incompleteU2APayment'; 5 | import { withdrawPI } from './handlers/AccountToUserPayment'; 6 | import { cancelledU2APayment } from './handlers/cancelledU2APayment'; 7 | import { auth } from '../../middlewares/auth'; 8 | 9 | const paymentRouter = Router(); 10 | 11 | paymentRouter.post('/approve', auth, approveU2APayment); 12 | paymentRouter.post('/incomplete', incompleteU2APayment); 13 | paymentRouter.post('/complete', auth, completeU2APayment); 14 | paymentRouter.post('/cancel', auth, cancelledU2APayment); 15 | paymentRouter.get('/courier/withdraw', auth, withdrawPI); 16 | 17 | export const PaymentController = { router: paymentRouter }; 18 | -------------------------------------------------------------------------------- /server/src/modules/router.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { PaymentController } from './payment/payment.controller'; 3 | import { TransactionController } from './transaction/transaction.controller'; 4 | import { UserController } from './user/user.controller'; 5 | 6 | const router = Router(); 7 | 8 | router.use('/payment', PaymentController.router); 9 | router.use('/user', UserController.router); 10 | router.use('/transaction', TransactionController.router); 11 | 12 | export const apiRouter = router; 13 | -------------------------------------------------------------------------------- /server/src/modules/transaction/handlers/acceptPendingTransaction.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { acceptPendingTransactionRequest } from '../services/transaction.services'; 4 | 5 | export async function acceptPendingTransaction(req: Request, res: Response) { 6 | const trxUId = req.params.id; 7 | const courierId = req.body.courierId; 8 | const response = await acceptPendingTransactionRequest(trxUId, courierId); 9 | 10 | if (response.type === Result.ERROR) { 11 | return res.status(500).json(response); 12 | } 13 | if (response.type === Result.NOT_FOUND) { 14 | return res.status(404).json(response); 15 | } 16 | 17 | return res.status(200).json(response); 18 | } 19 | -------------------------------------------------------------------------------- /server/src/modules/transaction/handlers/createTransaction.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { createTransactionEntry } from '../services/transaction.services'; 4 | 5 | export async function createTransaction(req: Request, res: Response) { 6 | const transactionData = req.body; 7 | console.log(req.file); 8 | if (req.file) { 9 | transactionData.itemImage = req.file.path; 10 | } 11 | const response = await createTransactionEntry(transactionData); 12 | 13 | if (response.type === Result.ERROR) { 14 | return res.status(500).json(response); 15 | } 16 | if (response.type === Result.NOT_FOUND) { 17 | return res.status(404).json(response); 18 | } 19 | 20 | return res.status(201).json(response); 21 | } 22 | -------------------------------------------------------------------------------- /server/src/modules/transaction/handlers/deleteTransaction.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { deleteTransactionEntry } from '../services/transaction.services'; 4 | 5 | export async function deleteTransaction(req: Request, res: Response) { 6 | const trxUid = req.params.id; 7 | const response = await deleteTransactionEntry(trxUid); 8 | 9 | if (response.type === Result.ERROR) { 10 | return res.status(500).json(response); 11 | } 12 | 13 | return res.status(200).json(response); 14 | } 15 | -------------------------------------------------------------------------------- /server/src/modules/transaction/handlers/getAllPendingTransaction.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { getAllPendingTransaction } from '../services/transaction.services'; 4 | 5 | export async function getAllPendingTransactions(req: Request, res: Response) { 6 | const reponse = await getAllPendingTransaction(); 7 | 8 | if (reponse.type === Result.ERROR) { 9 | return res.status(500).json(reponse); 10 | } 11 | 12 | return res.status(200).json(reponse); 13 | } 14 | -------------------------------------------------------------------------------- /server/src/modules/transaction/handlers/getAllTransactions.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { getTransactionsEntry } from '../services/transaction.services'; 4 | 5 | export async function getAllTransactions(req: Request, res: Response) { 6 | const reponse = await getTransactionsEntry(); 7 | 8 | if (reponse.type === Result.ERROR) { 9 | return res.status(500).json(reponse); 10 | } 11 | 12 | return res.status(200).json(reponse); 13 | } 14 | -------------------------------------------------------------------------------- /server/src/modules/transaction/handlers/getAllTransactionsForCourier.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { getTransactionsEntryForCourierId } from '../services/transaction.services'; 4 | 5 | export async function getAllTransactionsForCourier(req: Request, res: Response) { 6 | const userUid = req.params.id; 7 | const reponse = await getTransactionsEntryForCourierId(userUid); 8 | 9 | if (reponse.type === Result.ERROR) { 10 | return res.status(500).json(reponse); 11 | } 12 | 13 | return res.status(200).json(reponse); 14 | } 15 | -------------------------------------------------------------------------------- /server/src/modules/transaction/handlers/getAllTransactionsForReceiver.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { getTransactionsEntryForReceiverById } from '../services/transaction.services'; 4 | import { getTransactionsEntryForReceiverByUsername } from '../services/transaction.services'; 5 | 6 | export async function getAllTransactionsForReceiver(req: Request, res: Response) { 7 | const userUid = req.params.id; 8 | const reponse = await getTransactionsEntryForReceiverById(userUid); 9 | 10 | if (reponse.type === Result.ERROR) { 11 | return res.status(500).json(reponse); 12 | } 13 | 14 | return res.status(200).json(reponse); 15 | } 16 | 17 | export async function getAllTransactionsForReceiverByUsername(req: Request, res: Response) { 18 | const username = req.params.username; 19 | const reponse = await getTransactionsEntryForReceiverByUsername(username); 20 | 21 | if (reponse.type === Result.ERROR) { 22 | return res.status(500).json(reponse); 23 | } 24 | 25 | return res.status(200).json(reponse); 26 | } 27 | -------------------------------------------------------------------------------- /server/src/modules/transaction/handlers/getAllTransactionsForSender.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { getTransactionsEntryForSenderById } from '../services/transaction.services'; 4 | 5 | export async function getAllTransactionsForSender(req: Request, res: Response) { 6 | const userUid = req.params.id; 7 | const reponse = await getTransactionsEntryForSenderById(userUid); 8 | 9 | if (reponse.type === Result.ERROR) { 10 | return res.status(500).json(reponse); 11 | } 12 | 13 | return res.status(200).json(reponse); 14 | } 15 | -------------------------------------------------------------------------------- /server/src/modules/transaction/handlers/getTransaction.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { getTransactionEntry } from '../services/transaction.services'; 4 | 5 | export async function getTransaction(req: Request, res: Response) { 6 | const trxUId = req.params.id; 7 | const response = await getTransactionEntry(trxUId); 8 | 9 | if (response.type === Result.ERROR) { 10 | return res.status(500).json(response); 11 | } 12 | if (response.type === Result.NOT_FOUND) { 13 | return res.status(404).json(response); 14 | } 15 | 16 | return res.status(200).json(response); 17 | } 18 | -------------------------------------------------------------------------------- /server/src/modules/transaction/handlers/updateTransaction.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { updateTransactionEntry } from '../services/transaction.services'; 4 | 5 | export async function updateTransaction(req: Request, res: Response) { 6 | const trxUId = req.params.id; 7 | const updateData = req.body; 8 | if (req.file) { 9 | updateData.itemImage = req.file.path; 10 | } 11 | const response = await updateTransactionEntry(trxUId, updateData); 12 | 13 | if (response.type === Result.ERROR) { 14 | return res.status(500).json(response); 15 | } 16 | 17 | if (response.type === Result.NOT_FOUND) { 18 | return res.status(404).json(response); 19 | } 20 | 21 | return res.status(200).json(response); 22 | } 23 | -------------------------------------------------------------------------------- /server/src/modules/transaction/handlers/updateTransactionStatus.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { updateDeliverystatus } from '../services/transaction.services'; 4 | 5 | export async function updateTransactionStatus(req: Request, res: Response) { 6 | const trxUId = req.params.id; 7 | const status = req.body; 8 | const response = await updateDeliverystatus(trxUId, status); 9 | 10 | if (response.type === Result.ERROR) { 11 | return res.status(500).json(response); 12 | } 13 | 14 | return res.status(200).json(response); 15 | } 16 | -------------------------------------------------------------------------------- /server/src/modules/transaction/transaction.controller.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { deleteTransaction } from './handlers/deleteTransaction'; 3 | import { getAllTransactions } from './handlers/getAllTransactions'; 4 | import { getTransaction } from './handlers/getTransaction'; 5 | import { profileImageUpload } from '../../middlewares/multer'; 6 | import { createTransaction } from './handlers/createTransaction'; 7 | import { updateTransactionStatus } from './handlers/updateTransactionStatus'; 8 | import { acceptPendingTransaction } from './handlers/acceptPendingTransaction'; 9 | import { updateTransaction } from './handlers/updateTransaction'; 10 | import { getAllPendingTransactions } from './handlers/getAllPendingTransaction'; 11 | import { getAllTransactionsForSender } from './handlers/getAllTransactionsForSender'; 12 | import { getAllTransactionsForCourier } from './handlers/getAllTransactionsForCourier'; 13 | import { getAllTransactionsForReceiver } from './handlers/getAllTransactionsForReceiver'; 14 | import { getAllTransactionsForReceiverByUsername } from './handlers/getAllTransactionsForReceiver'; 15 | import { auth } from '../../middlewares/auth'; 16 | 17 | const transactionRouter = Router(); 18 | 19 | transactionRouter.get('/', auth, getAllTransactions); 20 | transactionRouter.post('/', auth, profileImageUpload, createTransaction); 21 | transactionRouter.get('/:id', auth, getTransaction); 22 | transactionRouter.patch('/:id', auth, profileImageUpload, updateTransaction); 23 | transactionRouter.get('/pending', auth, getAllPendingTransactions); 24 | transactionRouter.delete('/:id', auth, deleteTransaction); 25 | transactionRouter.patch('/status/:id', auth, updateTransactionStatus); 26 | transactionRouter.patch('/requests/accept-pending', auth, acceptPendingTransaction); 27 | transactionRouter.get('/sender/:id', auth, getAllTransactionsForSender); 28 | transactionRouter.get('/courier/:id', auth, getAllTransactionsForCourier); 29 | transactionRouter.get('/receiver/:id', auth, getAllTransactionsForReceiver); 30 | transactionRouter.get('/username', auth, getAllTransactionsForReceiverByUsername); 31 | 32 | export const TransactionController = { router: transactionRouter }; 33 | -------------------------------------------------------------------------------- /server/src/modules/user/handlers/createCourier.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { createCourierEntry } from '../services/user.services'; 4 | 5 | export async function createCourier(req: Request, res: Response) { 6 | const courier = req.body; 7 | const inserted = await createCourierEntry(courier); 8 | 9 | if (inserted.type === Result.ERROR) { 10 | return res.status(500).json(inserted); 11 | } 12 | return res.status(201).json(inserted); 13 | } 14 | -------------------------------------------------------------------------------- /server/src/modules/user/handlers/createUser.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { CreateUserDTO } from '../../../interfaces/user'; 4 | import { createUserEntry } from '../services/user.services'; 5 | 6 | export async function createUser(req: Request, res: Response) { 7 | const auth: CreateUserDTO = req.body; 8 | const inserted = await createUserEntry(auth); 9 | 10 | if (inserted.type === Result.ERROR) { 11 | return res.status(500).json(inserted); 12 | } 13 | return res.status(201).json(inserted); 14 | } 15 | -------------------------------------------------------------------------------- /server/src/modules/user/handlers/deleteCourier.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { deleteCourierEntry } from '../services/user.services'; 4 | 5 | export async function deleteCourier(req: Request, res: Response) { 6 | const courierUid = req.params.id; 7 | const deleteResult = await deleteCourierEntry(courierUid); 8 | 9 | if (deleteResult.type === Result.ERROR) { 10 | return res.status(500).json(deleteResult); 11 | } 12 | 13 | return res.status(200).json(deleteResult); 14 | } 15 | -------------------------------------------------------------------------------- /server/src/modules/user/handlers/deleteUser.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { deleteUserEntry } from '../services/user.services'; 4 | 5 | export async function deleteUser(req: Request, res: Response) { 6 | const userUid = req.params.id; 7 | const deleteResult = await deleteUserEntry(userUid); 8 | 9 | if (deleteResult.type === Result.ERROR) { 10 | return res.status(500).json(deleteResult); 11 | } 12 | 13 | return res.status(200).json(deleteResult); 14 | } 15 | -------------------------------------------------------------------------------- /server/src/modules/user/handlers/getAllCourierUsers.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { getCourierUsersEntry } from '../services/user.services'; 4 | 5 | export async function getAllCourierUsers(req: Request, res: Response) { 6 | const usersResult = await getCourierUsersEntry(); 7 | 8 | if (usersResult.type === Result.ERROR) { 9 | return res.status(500).json(usersResult); 10 | } 11 | 12 | return res.status(200).json(usersResult); 13 | } 14 | -------------------------------------------------------------------------------- /server/src/modules/user/handlers/getAllUser.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { getUsersEntry } from '../services/user.services'; 4 | 5 | export async function getAllUsers(req: Request, res: Response) { 6 | const usersResult = await getUsersEntry(); 7 | 8 | if (usersResult.type === Result.ERROR) { 9 | return res.status(500).json(usersResult); 10 | } 11 | 12 | return res.status(200).json(usersResult); 13 | } 14 | -------------------------------------------------------------------------------- /server/src/modules/user/handlers/getUser.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { CustomRequest } from '../../../middlewares/auth'; 4 | import { findUser } from '../services/user.services'; 5 | 6 | export async function getUser(req: Request, res: Response) { 7 | const userUid = (req as CustomRequest).token.userUid; 8 | const userResult = await findUser(userUid); 9 | 10 | if (userResult.type === Result.ERROR) { 11 | return res.status(500).json(userResult); 12 | } 13 | if (userResult.type === Result.NOT_FOUND) { 14 | return res.status(404).json(userResult); 15 | } 16 | 17 | return res.status(200).json(userResult); 18 | } 19 | -------------------------------------------------------------------------------- /server/src/modules/user/handlers/getUserByUsername.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { findUserByUsername } from '../services/user.services'; 4 | 5 | export async function getUserByUsername(req: Request, res: Response) { 6 | const username = req.params.username; 7 | console.log(username); 8 | const userResult = await findUserByUsername(username); 9 | 10 | if (userResult.type === Result.ERROR) { 11 | return res.status(500).json(userResult); 12 | } 13 | if (userResult.type === Result.NOT_FOUND) { 14 | return res.status(404).json(userResult); 15 | } 16 | 17 | return res.status(200).json(userResult); 18 | } 19 | -------------------------------------------------------------------------------- /server/src/modules/user/handlers/signInUser.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import jwt from 'jsonwebtoken'; 3 | import env from '../../../constants/environments'; 4 | import { Result } from '../../../constants/result'; 5 | import { platformAPIClient } from '../../../utils/platformAPIClient'; 6 | import { createUserEntry } from '../services/user.services'; 7 | 8 | export async function signInUser(req: Request, res: Response) { 9 | const auth = req.body.authResult; 10 | console.log(auth); 11 | try { 12 | await platformAPIClient.get(`/v2/me`, { 13 | headers: { Authorization: `Bearer ${auth.accessToken}` }, 14 | }); 15 | } catch (error) { 16 | return res.status(401).json({ error: 'Invalid access token' }); 17 | } 18 | 19 | const result = await createUserEntry(auth); 20 | 21 | if (result.type === Result.SUCCESS) { 22 | const token = jwt.sign( 23 | { userUid: result.data.user.userUid, username: result.data.user.username }, 24 | env.SESSION_SECRET, 25 | { 26 | expiresIn: '7 days', 27 | } 28 | ); 29 | return res.status(200).json({ message: 'User signed in', data: result.data, token: token }); 30 | } else { 31 | return res.status(500).json({ message: result.message, error: result.error }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /server/src/modules/user/handlers/updateCourier.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { updateCourierInfo } from '../services/user.services'; 4 | 5 | export async function updateCourier(req: Request, res: Response) { 6 | const courierUid = req.params.id; 7 | const courierData = req.body; 8 | const updateResult = await updateCourierInfo(courierUid, courierData); 9 | 10 | if (updateResult.type === Result.ERROR) { 11 | return res.status(500).json(updateResult); 12 | } 13 | 14 | return res.status(200).json(updateResult); 15 | } 16 | -------------------------------------------------------------------------------- /server/src/modules/user/handlers/updateUser.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { Result } from '../../../constants/result'; 3 | import { updateUserEntry } from '../services/user.services'; 4 | 5 | export async function updateUser(req: Request, res: Response) { 6 | const userUid = req.params.id; 7 | const userData = req.body; 8 | if (req.file) { 9 | userData.profileImg = req.file.path; 10 | } 11 | const updateResult = await updateUserEntry(userUid, userData); 12 | 13 | if (updateResult.type === Result.ERROR) { 14 | return res.status(500).json(updateResult); 15 | } 16 | 17 | return res.status(200).json(updateResult); 18 | } 19 | -------------------------------------------------------------------------------- /server/src/modules/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | import { createUser } from './handlers/createUser'; 3 | import { deleteUser } from './handlers/deleteUser'; 4 | import { getAllUsers } from './handlers/getAllUser'; 5 | import { getUser } from './handlers/getUser'; 6 | import { getUserByUsername } from './handlers/getUserByUsername'; 7 | import { updateUser } from './handlers/updateUser'; 8 | import { signInUser } from './handlers/signInUser'; 9 | import { createCourier } from './handlers/createCourier'; 10 | import { updateCourier } from './handlers/updateCourier'; 11 | import { deleteCourier } from './handlers/deleteCourier'; 12 | import { getAllCourierUsers } from './handlers/getAllCourierUsers'; 13 | import { profileImageUpload } from '../../middlewares/multer'; 14 | import { auth } from '../../middlewares/auth'; 15 | 16 | const userRouter = Router(); 17 | 18 | userRouter.get('/', auth, getAllUsers); 19 | userRouter.get('/couriers', auth, getAllCourierUsers); 20 | userRouter.post('/', auth, createUser); 21 | userRouter.get('/profile', auth, getUser); 22 | userRouter.get('/:username', auth, getUserByUsername); 23 | userRouter.delete('/:id', auth, deleteUser); 24 | userRouter.patch('/:id', auth, profileImageUpload, updateUser); 25 | userRouter.post('/courier', auth, createCourier); 26 | userRouter.delete('/courier/:id', auth, deleteCourier); 27 | userRouter.patch('/courier/:id', auth, updateCourier); 28 | userRouter.post('/sign-in', signInUser); 29 | 30 | export const UserController = { router: userRouter }; 31 | -------------------------------------------------------------------------------- /server/src/utils/platformAPIClient.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import env from '../constants/environments'; 3 | 4 | export const platformAPIClient = axios.create({ 5 | baseURL: env.PLATFORM_API_URL, 6 | timeout: 20000, 7 | headers: { Authorization: `Key ${env.PI_API_KEY}` }, 8 | }); 9 | -------------------------------------------------------------------------------- /server/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "test", 6 | "build", 7 | "**/*spec.ts" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true, 10 | "target": "es2017", 11 | "sourceMap": true, 12 | "outDir": "./build", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false, 20 | "typeRoots": [ 21 | "./src/types", // this is where you define your types 22 | "./node_modules/@types" //this where npm packages containing types are located 23 | ] 24 | } 25 | } 26 | --------------------------------------------------------------------------------