├── .all-contributorsrc ├── .commitlintrc.json ├── .dockerignore ├── .env.example ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── docs.yml │ ├── feature_request.yml │ ├── refactor.yml │ ├── security_issue.yml │ └── test.yml └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .husky └── commit-msg ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── package.json ├── public ├── favicon.ico ├── index.html ├── manifest.json └── robots.txt ├── renovate.json ├── src ├── App.test.tsx ├── App.tsx ├── actions │ ├── auth.actions.ts │ └── request.actions.ts ├── assets │ ├── logos │ │ ├── full-transparent.png │ │ ├── full-transparent.svg │ │ ├── full.png │ │ ├── full.svg │ │ ├── icon-transparent.png │ │ ├── icon-transparent.svg │ │ ├── icon.png │ │ └── icon.svg │ └── photos │ │ ├── background.svg │ │ ├── not_found.svg │ │ ├── product-sample.png │ │ ├── vector-login.svg │ │ ├── vector-signup.svg │ │ └── vector-verify.svg ├── components │ ├── nav │ │ ├── Nav.tsx │ │ └── NavbarLayout.tsx │ └── tooltip │ │ └── Tooltip.tsx ├── hooks │ ├── useAppDispatch.ts │ ├── useAppSelector.ts │ └── useUpdateObjectState.ts ├── index.css ├── index.tsx ├── pages │ ├── about │ │ └── about.tsx │ ├── home │ │ └── Home.tsx │ ├── login │ │ └── Login.tsx │ ├── not_found │ │ └── Not_Found.tsx │ ├── password_reset │ │ └── PasswordReset.tsx │ ├── signup │ │ └── Signup.tsx │ ├── support │ │ └── Support.tsx │ └── verification │ │ └── Verification.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── setupTests.ts ├── store.ts └── utils │ └── api.util.ts ├── tailwind.config.js ├── tsconfig.json ├── typedoc.json └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "frontend", 3 | "projectOwner": "MultiEmail", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 70, 10 | "commit": true, 11 | "commitConvention": "eslint", 12 | "contributors": [ 13 | { 14 | "login": "aayushchugh", 15 | "name": "Ayush Chugh", 16 | "avatar_url": "https://avatars.githubusercontent.com/u/69336518?v=4", 17 | "profile": "https://shriproperty.com/", 18 | "contributions": [ 19 | "code", 20 | "review", 21 | "projectManagement" 22 | ] 23 | }, 24 | { 25 | "login": "KanLSK", 26 | "name": "Kan Halder", 27 | "avatar_url": "https://avatars.githubusercontent.com/u/59249490?v=4", 28 | "profile": "https://github.com/KanLSK", 29 | "contributions": [ 30 | "code", 31 | "design", 32 | "review" 33 | ] 34 | }, 35 | { 36 | "login": "Karol-Ari", 37 | "name": "Karol-Ari", 38 | "avatar_url": "https://avatars.githubusercontent.com/u/55296886?v=4", 39 | "profile": "https://github.com/Karol-Ari", 40 | "contributions": [ 41 | "code", 42 | "design", 43 | "review" 44 | ] 45 | }, 46 | { 47 | "login": "is-it-ayush", 48 | "name": "Ayush", 49 | "avatar_url": "https://avatars.githubusercontent.com/u/36449128?v=4", 50 | "profile": "https://github.com/is-it-ayush", 51 | "contributions": [ 52 | "code", 53 | "review", 54 | "mentoring" 55 | ] 56 | }, 57 | { 58 | "login": "CodesWithJames", 59 | "name": "James", 60 | "avatar_url": "https://avatars.githubusercontent.com/u/71551059?v=4", 61 | "profile": "https://www.jamesmesser.xyz/", 62 | "contributions": [ 63 | "financial" 64 | ] 65 | }, 66 | { 67 | "login": "shivamvishwakarm", 68 | "name": "shivam vishwakarma", 69 | "avatar_url": "https://avatars.githubusercontent.com/u/80755217?v=4", 70 | "profile": "https://github.com/shivamvishwakarm", 71 | "contributions": [ 72 | "code" 73 | ] 74 | }, 75 | { 76 | "login": "AndrewFirePvP7", 77 | "name": "AndrewDev", 78 | "avatar_url": "https://avatars.githubusercontent.com/u/29314485?v=4", 79 | "profile": "https://github.com/AndrewFirePvP7", 80 | "contributions": [ 81 | "ideas" 82 | ] 83 | }, 84 | { 85 | "login": "samiulbasirfahim", 86 | "name": "Samiul Basir Fahim", 87 | "avatar_url": "https://avatars.githubusercontent.com/u/93071892?v=4", 88 | "profile": "https://github.com/samiulbasirfahim", 89 | "contributions": [ 90 | "doc" 91 | ] 92 | }, 93 | { 94 | "login": "Kunalpal215", 95 | "name": "Kunal Pal", 96 | "avatar_url": "https://avatars.githubusercontent.com/u/83537208?v=4", 97 | "profile": "https://kunalpal215.github.io/", 98 | "contributions": [ 99 | "code" 100 | ] 101 | }, 102 | { 103 | "login": "KOQ-UTx10101", 104 | "name": "Utkrisht Singh Chauhan", 105 | "avatar_url": "https://avatars.githubusercontent.com/u/83289528?v=4", 106 | "profile": "https://github.com/KOQ-UTx10101", 107 | "contributions": [ 108 | "code" 109 | ] 110 | }, 111 | { 112 | "login": "Gauravpawar3102", 113 | "name": "Gaurav Pawar", 114 | "avatar_url": "https://avatars.githubusercontent.com/u/73964378?v=4", 115 | "profile": "https://github.com/Gauravpawar3102", 116 | "contributions": [ 117 | "code" 118 | ] 119 | } 120 | ], 121 | "contributorsPerLine": 7, 122 | "linkToUsage": true 123 | } 124 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .dockerignore 4 | Dockerfile 5 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_API_URL="http://localhost:3001/api" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "🐛 Bug report" 2 | description: "File a bug/issue" 3 | title: "[BUG] :bug: " 4 | labels: [":bug: bug"] 5 | body: 6 | - type: textarea 7 | id: current_behaviour 8 | attributes: 9 | label: Current Behaviour 10 | description: A concise description of what you are currently experiencing 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: expected_behaviour 15 | attributes: 16 | label: Expected Behaviour 17 | description: A concise description of what is expected to happen 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: steps_reproduce 22 | attributes: 23 | label: Steps To Reproduce 24 | description: Steps to reproduce the behavior. 25 | placeholder: | 26 | 1. Go to... 27 | 2. Click on... 28 | 3. See error... 29 | validations: 30 | required: false 31 | - type: textarea 32 | id: anything_else 33 | attributes: 34 | label: Anything else? 35 | description: | 36 | Links? References? Anything that will give us more context about the issue you are encountering! 37 | 38 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 39 | validations: 40 | required: false 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs.yml: -------------------------------------------------------------------------------- 1 | name: "📝 Docs" 2 | description: "Found a security issue report it here" 3 | title: "[DOCS] :memo: <title>" 4 | labels: [":memo: documentation"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: A concise description if you want us to update or create new documentation 11 | validations: 12 | required: false 13 | - type: textarea 14 | id: anything_else 15 | attributes: 16 | label: Anything else? 17 | description: | 18 | Links? References? Anything that will give us more context about the issue you are encountering! 19 | 20 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 21 | validations: 22 | required: false 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "✨ Feature Request" 2 | description: "Suggest and idea for this project" 3 | title: "[FEAT] :sparkles: <title>" 4 | labels: [":sparkles: enhancement"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: A concise description of what you want in this project 11 | validations: 12 | required: false 13 | - type: textarea 14 | id: anything_else 15 | attributes: 16 | label: Anything else? 17 | description: | 18 | Links? References? Anything that will give us more context about the issue you are encountering! 19 | 20 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 21 | validations: 22 | required: false 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/refactor.yml: -------------------------------------------------------------------------------- 1 | name: "♻ Refactor Code" 2 | description: "Refactor your code" 3 | title: "[REFACTOR] :recycle: <title>" 4 | labels: [":recycle: refactor"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: A concise description of how we can refactor our code 11 | validations: 12 | required: false 13 | - type: textarea 14 | id: anything_else 15 | attributes: 16 | label: Anything else? 17 | description: | 18 | Links? References? Anything that will give us more context about the issue you are encountering! 19 | 20 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 21 | validations: 22 | required: false 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/security_issue.yml: -------------------------------------------------------------------------------- 1 | name: "🔒 Security Issue" 2 | description: "Found a security issue report it here" 3 | title: "[SECURITY] :lock: <title>" 4 | labels: [":lock: security"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: A concise description of the security issue 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: how_to_solve 15 | attributes: 16 | label: How can we solve this 17 | description: Please tell us how can we solve this issue 18 | validations: 19 | required: false 20 | - type: textarea 21 | id: anything_else 22 | attributes: 23 | label: Anything else? 24 | description: | 25 | Links? References? Anything that will give us more context about the issue you are encountering! 26 | 27 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 28 | validations: 29 | required: false 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/test.yml: -------------------------------------------------------------------------------- 1 | name: "🧪 Test" 2 | description: "Issue related to tests" 3 | title: "[TEST] :test_tube: <title>" 4 | labels: [":test_tube: tests"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: A concise description if you want us to write new tests or check on failing tests 11 | validations: 12 | required: false 13 | - type: textarea 14 | id: anything_else 15 | attributes: 16 | label: Anything else? 17 | description: | 18 | Links? References? Anything that will give us more context about the issue you are encountering! 19 | 20 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 21 | validations: 22 | required: false 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | <!-- -------------------------- Required section --------------------------- --> 2 | 3 | ### Description 4 | 5 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 6 | 7 | ### Checklist: 8 | 9 | <!-- pull request can be still created if all tasks are not completed --> 10 | 11 | - [ ] This pull request follow our contribution guidelines 12 | - [ ] I have performed a self-review of my own code 13 | - [ ] I have commented my code, particularly in hard-to-understand areas 14 | - [ ] I have made corresponding changes to the documentation (postman and swagger) 15 | - [ ] I have written tests for the code 16 | - [ ] I have updated .env.example file for new .env variables 17 | 18 | <!-- -------------------------- Optional section --------------------------- --> 19 | 20 | ### What is current behavior? 21 | 22 | <!-- You can add issue number --> 23 | 24 | Closes #(issue_number) 25 | 26 | ### What is new behavior? 27 | 28 | <!-- You can attach gif, screenshots, etc --> 29 | 30 | ### Breaking Changes? 31 | 32 | <!-- If this PR introduce any breaking changes then please list them here --> 33 | 34 | ### Additional information 35 | 36 | <!-- Links? References? Anything that will give us more context about the pull request --> 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # documentation 4 | /docs 5 | 6 | # dependencies 7 | /node_modules 8 | /.pnp 9 | .pnp.js 10 | 11 | # testing 12 | /coverage 13 | 14 | # production 15 | /build 16 | 17 | # misc 18 | .DS_Store 19 | .env 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | 30 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["IAPI"], 3 | "docwriter.style": "Auto-detect", 4 | "editor.tabSize": 4, 5 | "editor.comments.insertSpace": true, 6 | "editor.autoIndent": "full", 7 | "prettier.tabWidth": 4, 8 | "prettier.arrowParens": "always", 9 | "prettier.bracketSpacing": true, 10 | "editor.insertSpaces": false, 11 | "prettier.useTabs": true, 12 | "dotenv.enableAutocloaking": false 13 | } 14 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | mutiiemail@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | [fork]: /fork 4 | [pr]: /compare 5 | [code-of-conduct]: CODE_OF_CONDUCT.md 6 | 7 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 8 | 9 | Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. 10 | 11 | ## Issues and PRs 12 | 13 | If you have suggestions for how this project could be improved, or want to report a bug, open an issue! We'd love all and any contributions. If you have questions, too, we'd love to hear them. 14 | 15 | We'd also love PRs. If you're thinking of a large PR, we advise opening up an issue first to talk about it, though! Look at the links below if you're not sure how to open a PR. 16 | 17 | 1. [Fork][fork] and clone the repository. 18 | 2. Configure and install the dependencies: `yarn install`. 19 | 3. Create a new branch: `git checkout -b my-branch-name`. 20 | 4. Work on your issue/feature 21 | 5. Before committing the changes run tests using `yarn test` 22 | 6. Follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for writing commit messages 23 | 7. Push to your fork and [submit a pull request][pr] in `main` branch 24 | 8. Pat your self on the back and wait for your pull request to be approved and merged. 25 | 26 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 27 | 28 | - Keep your changes as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 29 | - Follow [Conventional commits](https://www.conventionalcommits.org/en/v1.0.0/). 30 | 31 | Work in Progress pull requests are also welcome to get feedback early on, or if there is something blocked you. (just mark them as draft) 32 | 33 | ## Resources 34 | 35 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 36 | - [About Pull Requests](https://help.github.com/articles/about-pull-requests/) 37 | - [GitHub Help](https://help.github.com) 38 | - [Conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) 39 | - [Conventional commits vscode extension](https://marketplace.visualstudio.com/items?itemName=vivaxy.vscode-conventional-commits) 40 | - [Github issues and pull requests vscode extension](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github) 41 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package.json . 6 | COPY yarn.lock . 7 | 8 | RUN yarn install 9 | 10 | COPY . . 11 | 12 | CMD ["yarn", "run", "start"] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Multi Email 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> 2 | [![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors-) 3 | <!-- ALL-CONTRIBUTORS-BADGE:END --> 4 | 5 | # Technologies Used 6 | 7 | - React 8 | - Redux 9 | - Tailwind CSS 10 | 11 | # Features 12 | 13 | - Admin dashboard 14 | - User settings and or user dashboard 15 | - send emails 16 | - recive emails 17 | - Connections through other parties ie discord, twitter, facebook etc.. 18 | 19 | # Setup 20 | 21 | ## Windows 22 | 23 | ``` 24 | git clone https://github.com/MultiEmail/MultiEmail-frontend.git 25 | cd MultiEmail-frontend 26 | yarn install 27 | yarn dev 28 | ``` 29 | 30 | # Linux 31 | 32 | ``` 33 | git clone https://github.com/MultiEmail/MultiEmail-frontend.git && cd MultiEmail-frontend && yarn install && yarn dev 34 | ``` 35 | 36 | # Docker 37 | 38 | For development server (http://localhost:3000) 39 | ```bash 40 | git clone https://github.com/MultiEmail/MultiEmail-frontend.git 41 | cd MultiEmail-frontend 42 | docker compose --env-file ./.env 43 | ``` 44 | 45 | 46 | # Wanna join the team? 47 | 48 | - [Discord server](https://discord.gg/8kTdfWmuQa) 49 | - [Postman team](https://www.postman.com/multiemail/workspace/muti-email-rest-api/overview) 50 | 51 | 52 | ## Contributing 53 | 54 | - Contributions make the open source community such an amazing place to learn, inspire, and create. 55 | - Any contributions you make are **truly appreciated**. 56 | - Check out our [contribution guidelines](/CONTRIBUTING.md) for more information. 57 | 58 | <h2> 59 | License 60 | </h2> 61 | 62 | <br> 63 | <p> 64 | This project is Licensed under the <a href="./LICENSE">MIT License</a>. Please go through the License atleast once before making your contribution. </p> 65 | <br> 66 | 67 | ## Contributors ✨ 68 | 69 | Thanks goes to these wonderful people ❤: 70 | 71 | <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> 72 | <!-- prettier-ignore-start --> 73 | <!-- markdownlint-disable --> 74 | <table> 75 | <tbody> 76 | <tr> 77 | <td align="center"><a href="https://shriproperty.com/"><img src="https://avatars.githubusercontent.com/u/69336518?v=4?s=70" width="70px;" alt="Ayush Chugh"/><br /><sub><b>Ayush Chugh</b></sub></a><br /><a href="https://github.com/MultiEmail/frontend/commits?author=aayushchugh" title="Code">💻</a> <a href="https://github.com/MultiEmail/frontend/pulls?q=is%3Apr+reviewed-by%3Aaayushchugh" title="Reviewed Pull Requests">👀</a> <a href="#projectManagement-aayushchugh" title="Project Management">📆</a></td> 78 | <td align="center"><a href="https://github.com/KanLSK"><img src="https://avatars.githubusercontent.com/u/59249490?v=4?s=70" width="70px;" alt="Kan Halder"/><br /><sub><b>Kan Halder</b></sub></a><br /><a href="https://github.com/MultiEmail/frontend/commits?author=KanLSK" title="Code">💻</a> <a href="#design-KanLSK" title="Design">🎨</a> <a href="https://github.com/MultiEmail/frontend/pulls?q=is%3Apr+reviewed-by%3AKanLSK" title="Reviewed Pull Requests">👀</a></td> 79 | <td align="center"><a href="https://github.com/Karol-Ari"><img src="https://avatars.githubusercontent.com/u/55296886?v=4?s=70" width="70px;" alt="Karol-Ari"/><br /><sub><b>Karol-Ari</b></sub></a><br /><a href="https://github.com/MultiEmail/frontend/commits?author=Karol-Ari" title="Code">💻</a> <a href="#design-Karol-Ari" title="Design">🎨</a> <a href="https://github.com/MultiEmail/frontend/pulls?q=is%3Apr+reviewed-by%3AKarol-Ari" title="Reviewed Pull Requests">👀</a></td> 80 | <td align="center"><a href="https://github.com/is-it-ayush"><img src="https://avatars.githubusercontent.com/u/36449128?v=4?s=70" width="70px;" alt="Ayush"/><br /><sub><b>Ayush</b></sub></a><br /><a href="https://github.com/MultiEmail/frontend/commits?author=is-it-ayush" title="Code">💻</a> <a href="https://github.com/MultiEmail/frontend/pulls?q=is%3Apr+reviewed-by%3Ais-it-ayush" title="Reviewed Pull Requests">👀</a> <a href="#mentoring-is-it-ayush" title="Mentoring">🧑‍🏫</a></td> 81 | <td align="center"><a href="https://www.jamesmesser.xyz/"><img src="https://avatars.githubusercontent.com/u/71551059?v=4?s=70" width="70px;" alt="James"/><br /><sub><b>James</b></sub></a><br /><a href="#financial-CodesWithJames" title="Financial">💵</a></td> 82 | <td align="center"><a href="https://github.com/shivamvishwakarm"><img src="https://avatars.githubusercontent.com/u/80755217?v=4?s=70" width="70px;" alt="shivam vishwakarma"/><br /><sub><b>shivam vishwakarma</b></sub></a><br /><a href="https://github.com/MultiEmail/frontend/commits?author=shivamvishwakarm" title="Code">💻</a></td> 83 | <td align="center"><a href="https://github.com/AndrewFirePvP7"><img src="https://avatars.githubusercontent.com/u/29314485?v=4?s=70" width="70px;" alt="AndrewDev"/><br /><sub><b>AndrewDev</b></sub></a><br /><a href="#ideas-AndrewFirePvP7" title="Ideas, Planning, & Feedback">🤔</a></td> 84 | </tr> 85 | <tr> 86 | <td align="center"><a href="https://github.com/samiulbasirfahim"><img src="https://avatars.githubusercontent.com/u/93071892?v=4?s=70" width="70px;" alt="Samiul Basir Fahim"/><br /><sub><b>Samiul Basir Fahim</b></sub></a><br /><a href="https://github.com/MultiEmail/frontend/commits?author=samiulbasirfahim" title="Documentation">📖</a></td> 87 | <td align="center"><a href="https://kunalpal215.github.io/"><img src="https://avatars.githubusercontent.com/u/83537208?v=4?s=70" width="70px;" alt="Kunal Pal"/><br /><sub><b>Kunal Pal</b></sub></a><br /><a href="https://github.com/MultiEmail/frontend/commits?author=Kunalpal215" title="Code">💻</a></td> 88 | <td align="center"><a href="https://github.com/KOQ-UTx10101"><img src="https://avatars.githubusercontent.com/u/83289528?v=4?s=70" width="70px;" alt="Utkrisht Singh Chauhan"/><br /><sub><b>Utkrisht Singh Chauhan</b></sub></a><br /><a href="https://github.com/MultiEmail/frontend/commits?author=KOQ-UTx10101" title="Code">💻</a></td> 89 | <td align="center"><a href="https://github.com/Gauravpawar3102"><img src="https://avatars.githubusercontent.com/u/73964378?v=4?s=70" width="70px;" alt="Gaurav Pawar"/><br /><sub><b>Gaurav Pawar</b></sub></a><br /><a href="https://github.com/MultiEmail/frontend/commits?author=Gauravpawar3102" title="Code">💻</a></td> 90 | </tr> 91 | </tbody> 92 | <tfoot> 93 | <tr> 94 | <td align="center" size="13px" colspan="7"> 95 | <img src="https://raw.githubusercontent.com/all-contributors/all-contributors-cli/1b8533af435da9854653492b1327a23a4dbd0a10/assets/logo-small.svg"> 96 | <a href="https://all-contributors.js.org/docs/en/bot/usage">Add your contributions</a> 97 | </img> 98 | </td> 99 | </tr> 100 | </tfoot> 101 | </table> 102 | 103 | <!-- markdownlint-restore --> 104 | <!-- prettier-ignore-end --> 105 | 106 | <!-- ALL-CONTRIBUTORS-LIST:END --> 107 | 108 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 109 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | frontend: 5 | container_name: MultiEmail-frontend-dev 6 | stdin_open: true 7 | build: . 8 | ports: 9 | - "3000:3000" 10 | volumes: 11 | - "/usr/src/app/node_modules" 12 | - "./:/usr/src/app" 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@formkit/auto-animate": "1.0.0-pre-alpha.3", 7 | "@heroicons/react": "2.0.17", 8 | "@reduxjs/toolkit": "1.9.6", 9 | "axios": "1.5.1", 10 | "formik": "2.2.9", 11 | "framer-motion": "10.16.4", 12 | "query-string": "8.1.0", 13 | "react": "18.2.0", 14 | "react-dom": "18.2.0", 15 | "react-icons": "4.8.0", 16 | "react-redux": "8.0.5", 17 | "react-router-dom": "6.10.0", 18 | "web-vitals": "3.3.1", 19 | "yup": "1.3.2" 20 | }, 21 | "scripts": { 22 | "start": "yarn && react-scripts start", 23 | "build": "yarn && react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject", 26 | "prepare": "husky install", 27 | "docs": "typedoc" 28 | }, 29 | "eslintConfig": { 30 | "extends": [ 31 | "react-app", 32 | "react-app/jest" 33 | ] 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.2%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | }, 47 | "devDependencies": { 48 | "@testing-library/jest-dom": "5.16.5", 49 | "@testing-library/react": "14.0.0", 50 | "@testing-library/user-event": "14.4.3", 51 | "typedoc": "0.25.1", 52 | "typescript": "5.2.2", 53 | "@types/jest": "29.5.5", 54 | "@types/node": "18.18.1", 55 | "@types/react": "18.2.23", 56 | "@types/react-dom": "18.2.8", 57 | "@commitlint/cli": "17.7.2", 58 | "@commitlint/config-conventional": "17.7.0", 59 | "@types/axios": "0.14.0", 60 | "all-contributors-cli": "6.24.0", 61 | "commitlint": "17.7.2", 62 | "husky": "8.0.3", 63 | "react-scripts": "5.0.1", 64 | "tailwindcss": "3.3.3" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiEmail/frontend/676109e017877da6da71a6407d3fd5641d458c31/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="utf-8" /> 5 | <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> 6 | <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 | <meta name="theme-color" content="#000000" /> 8 | <meta 9 | name="description" 10 | content="MutliMail is a web application that allows you to easily manage multiple email accounts from one place." 11 | /> 12 | <!--Google Font Prefetch--> 13 | <link rel="preconnect" href="https://fonts.googleapis.com"> 14 | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 15 | <link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet"> 16 | 17 | <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> 18 | <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> 19 | <title>MultiMail 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /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": "../logos/icon-transparent.png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "../logos/full-transparent.png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from "@testing-library/react"; 2 | import { Provider } from "react-redux"; 3 | import store from "./store"; 4 | import App from "./App"; 5 | 6 | test("renders learn react link", () => { 7 | const { getByText } = render( 8 | 9 | 10 | 11 | ); 12 | 13 | /** 14 | * @author @is-it-ayush 15 | * This is a basic test example. This will pass as it use's regex to find the Multimail text in the App.tsx which exists. 16 | * More test's can be added to test the functionality of the app later. 17 | */ 18 | expect(getByText(/Multimail/i)).toBeInTheDocument(); 19 | }); 20 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Routes, Route } from "react-router-dom"; 2 | 3 | //import pages & components 4 | 5 | import Home from "./pages/home/Home"; 6 | import Login from "./pages/login/Login"; 7 | import Signup from "./pages/signup/Signup"; 8 | import Verification from "./pages/verification/Verification"; 9 | import NavLayout from "./components/nav/NavbarLayout"; 10 | import NotFound from "./pages/not_found/Not_Found"; 11 | 12 | import { FC } from "react"; 13 | 14 | import About from './pages/about/about'; 15 | import Support from './pages/support/Support'; 16 | import PasswordReset from './pages/password_reset/PasswordReset'; 17 | 18 | const App: FC = () => { 19 | return ( 20 |
21 | 22 | 23 | }> 24 | } /> 25 | } /> 26 | } /> 27 | } /> 28 | } /> 29 | } /> 30 | } /> 31 | } /> 32 | 33 | 34 | 35 |
36 | ); 37 | }; 38 | 39 | export default App; 40 | -------------------------------------------------------------------------------- /src/actions/auth.actions.ts: -------------------------------------------------------------------------------- 1 | import { AxiosError } from "axios"; 2 | import API, { IAPIResponseError, IAPIResponseSuccess } from "../utils/api.util"; 3 | 4 | export interface ISignupPayload { 5 | username: string; 6 | email: string; 7 | password: string; 8 | cpassword: string; 9 | acceptedTermsAndConditions: boolean; 10 | receiveMarketingEmails: boolean; 11 | } 12 | 13 | /** 14 | * This function will send POST request to `/auth/signup` route 15 | * @param payload for post request 16 | * 17 | * @author aayushchugh 18 | */ 19 | export const signupHandler = (payload: ISignupPayload) => { 20 | return async () => { 21 | interface APIResponse extends IAPIResponseSuccess { 22 | message: string; 23 | } 24 | 25 | try { 26 | console.log("payload", payload); 27 | const mapToApiFields = { 28 | username: payload.username, 29 | email: payload.email, 30 | password: payload.password, 31 | cpassword: payload.cpassword, 32 | acceptedTermsAndConditions: payload.acceptedTermsAndConditions, 33 | receiveMarketingEmails: payload.receiveMarketingEmails, 34 | }; 35 | const response = await API.post("/auth/signup", mapToApiFields); 36 | console.log(response); 37 | return Promise.resolve(response); 38 | } catch (error) { 39 | return Promise.reject(error as AxiosError); 40 | } 41 | }; 42 | }; 43 | 44 | export interface ILoginPayload { 45 | emailOrUsername: string; 46 | password: string; 47 | } 48 | 49 | /** 50 | * This function will send request to `/auth/login` route and than 51 | * set `access_token` and `refresh_token` from response in `localStorage` 52 | * @param payload for post request 53 | * 54 | * @author aayushchugh 55 | */ 56 | export const loginHandler = (payload: ILoginPayload) => { 57 | return async () => { 58 | interface APIResponse extends IAPIResponseSuccess { 59 | access_token: string; 60 | refresh_token: string; 61 | } 62 | 63 | try { 64 | // make API request to `/auth/login` route 65 | interface MapToApiFields { 66 | username?: string; 67 | email?: string; 68 | password: string; 69 | } 70 | 71 | const mapToApiFields: MapToApiFields = { 72 | password: payload.password, 73 | }; 74 | if (payload.emailOrUsername.includes("@")) { 75 | mapToApiFields.email = payload.emailOrUsername; 76 | } else { 77 | mapToApiFields.username = payload.emailOrUsername; 78 | } 79 | const res = await API.post("/auth/login", mapToApiFields); 80 | 81 | // save access and refresh token to local storage 82 | localStorage.setItem("access_token", res.data.access_token); 83 | localStorage.setItem("refresh_token", res.data.refresh_token); 84 | 85 | return Promise.resolve(res); 86 | } catch (err) { 87 | return Promise.reject(err as AxiosError); 88 | } 89 | }; 90 | }; 91 | 92 | export interface IVerificationPayload { 93 | verificationCode: Number; 94 | } 95 | 96 | /** 97 | * This function will send request to `/auth/verify` route 98 | * @param payload for post request 99 | * @author is-it-ayush 100 | */ 101 | 102 | export const verifyHandler = (payload: IVerificationPayload, token: String) => { 103 | return async () => { 104 | interface APIResponse extends IAPIResponseSuccess { 105 | message: string; 106 | } 107 | 108 | try { 109 | const res = await API.get(`/auth/verify/${payload.verificationCode}`, { 110 | headers: { 111 | "Authorization": `Bearer ${token}`, 112 | } 113 | }); 114 | return Promise.resolve(res); 115 | } catch (err) { 116 | return Promise.reject(err as AxiosError); 117 | } 118 | }; 119 | } 120 | 121 | 122 | /** 123 | * This function will send request to `/auth/forgotpassword` route 124 | * @param payload for post request 125 | * @author is-it-ayush 126 | */ 127 | 128 | export interface IResetRequestPayload { 129 | email: string; 130 | } 131 | 132 | export const resetRequestHandler = (payload: IResetRequestPayload) => { 133 | return async () => { 134 | interface APIResponse extends IAPIResponseSuccess { 135 | message: string; 136 | } 137 | 138 | try { 139 | const res = await API.post("/auth/forgotpassword", payload); 140 | return Promise.resolve(res); 141 | } catch (err) { 142 | return Promise.reject(err as AxiosError); 143 | } 144 | }; 145 | } 146 | 147 | 148 | /** 149 | * This function will send request to `/auth/resetpassword/:email/:passwordResetCode` route 150 | * @param payload for patch request 151 | * @author is-it-ayush 152 | * @todo Reconfigure the route to use a JWT token instead of email and passwordResetCode 153 | */ 154 | 155 | 156 | export interface IResetPasswordParams { 157 | payload: { 158 | password: string; 159 | cpassword: string; 160 | }; 161 | passwordResetCode: number; 162 | } 163 | 164 | export const resetPasswordHandler = (data: IResetPasswordParams, request: IResetRequestPayload) => { 165 | return async () => { 166 | interface APIResponse extends IAPIResponseSuccess { 167 | message: string; 168 | } 169 | 170 | try { 171 | const res = await API.patch(`/auth/resetpassword/${request.email}/${data.passwordResetCode}`, data.payload); 172 | return Promise.resolve(res); 173 | } catch (err) { 174 | return Promise.reject(err as AxiosError); 175 | } 176 | }; 177 | } -------------------------------------------------------------------------------- /src/actions/request.actions.ts: -------------------------------------------------------------------------------- 1 | // // @todo: Retrieve the public key of the user from the database and encrypt the request before sending it to the server. 2 | 3 | // export const encryptPayload = (payload: any) => { 4 | // return CryptoJS.AES.encrypt(JSON.stringify(payload), process.env.REACT_APP_CRYPTO_KEY).toString(); 5 | // } 6 | 7 | export default null; -------------------------------------------------------------------------------- /src/assets/logos/full-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiEmail/frontend/676109e017877da6da71a6407d3fd5641d458c31/src/assets/logos/full-transparent.png -------------------------------------------------------------------------------- /src/assets/logos/full-transparent.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/logos/full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiEmail/frontend/676109e017877da6da71a6407d3fd5641d458c31/src/assets/logos/full.png -------------------------------------------------------------------------------- /src/assets/logos/icon-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiEmail/frontend/676109e017877da6da71a6407d3fd5641d458c31/src/assets/logos/icon-transparent.png -------------------------------------------------------------------------------- /src/assets/logos/icon-transparent.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/logos/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiEmail/frontend/676109e017877da6da71a6407d3fd5641d458c31/src/assets/logos/icon.png -------------------------------------------------------------------------------- /src/assets/logos/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/photos/background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/assets/photos/product-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MultiEmail/frontend/676109e017877da6da71a6407d3fd5641d458c31/src/assets/photos/product-sample.png -------------------------------------------------------------------------------- /src/assets/photos/vector-login.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/assets/photos/vector-signup.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/assets/photos/vector-verify.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/components/nav/Nav.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import logo from "../../assets/logos/icon-transparent.svg"; 4 | import { useLocation } from "react-router-dom"; 5 | 6 | import { HiMenuAlt3 } from "react-icons/hi"; 7 | import { IoClose } from "react-icons/io5"; 8 | 9 | /** 10 | * Nav component 11 | * @author Kan Halder 12 | */ 13 | const Nav: FC = () => { 14 | 15 | const location = useLocation(); 16 | const [isOpen, setIsOpen] = useState(false); 17 | const toggle = () => setIsOpen(!isOpen) 18 | 19 | 20 | // This is a hacky way to get the current route. 21 | // I'm not sure if there's a better way to do this. 22 | const currentRoute = location.pathname.split("/")[1]; 23 | 24 | 25 | return ( 26 |
27 |
28 |
29 | logo 30 | Multi Email 31 |
32 |
    33 |
  • About
  • 34 |
  • Github
  • 35 |
  • Team
  • 36 |
  • Support
  • 37 |
38 |
39 | Sign Up 40 | Log In 41 |
42 |
43 | {isOpen ? : } 44 |
45 |
46 | 47 | {/* Mobile Navbar, Side Menu is hidden so no need for update */} 48 |
49 |
    50 |
  • About
  • 51 |
  • Github
  • 52 |
  • Team
  • 53 |
  • Support
  • 54 |
55 |
56 | Sign Up 57 | Log In 58 |
59 |
60 |
61 | ); 62 | }; 63 | 64 | export default Nav; 65 | -------------------------------------------------------------------------------- /src/components/nav/NavbarLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router-dom'; 2 | import { FC } from 'react'; 3 | import Navbar from './Nav'; 4 | 5 | const NavbarLayout: FC = () => { 6 | return ( 7 |
8 | 9 | 10 |
11 | ); 12 | }; 13 | 14 | export default NavbarLayout; -------------------------------------------------------------------------------- /src/components/tooltip/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import { FC, useEffect, useState } from "react"; 3 | import { AiOutlineCheckCircle, AiOutlineCloseCircle, AiOutlineInfoCircle, AiOutlineWarning } from "react-icons/ai"; 4 | 5 | const ToolTip: FC = ({ message, type }) => { 6 | const [tooltipMessage, setTooltipMessage] = useState("You're not supposed to see this."); 7 | const [tooltipType, setTooltipType] = useState<"error" | "success" | "warning" | "info">("info"); 8 | 9 | // Icon Map with corresponding colors 10 | const iconMapWithColors = { 11 | error: , 12 | success: , 13 | warning: , 14 | info: 15 | }; 16 | 17 | useEffect(() => { 18 | setTooltipMessage(message); 19 | setTooltipType(type); 20 | }, [message, type]); 21 | 22 | return ( 23 | 30 |
31 |
32 | {iconMapWithColors[tooltipType]} 33 |
34 |

{tooltipMessage}

35 |
36 |
37 | ); 38 | } 39 | 40 | interface ITooltipProps { 41 | message: string; 42 | type: "error" | "success" | "warning" | "info"; 43 | } 44 | 45 | export default ToolTip; -------------------------------------------------------------------------------- /src/hooks/useAppDispatch.ts: -------------------------------------------------------------------------------- 1 | import { useDispatch } from "react-redux"; 2 | import { TAppDispatch } from "../store"; 3 | 4 | /** 5 | * use `useAppDispatch` throughout the code instead of `useDispatch` 6 | * 7 | * @author aayushchugh 8 | * @constant 9 | */ 10 | export const useAppDispatch = () => useDispatch(); 11 | -------------------------------------------------------------------------------- /src/hooks/useAppSelector.ts: -------------------------------------------------------------------------------- 1 | import { TypedUseSelectorHook, useSelector } from "react-redux"; 2 | import { TRootState } from "../store"; 3 | 4 | /** 5 | * use `useAppSelector` throughout the code instead of `useSelector` 6 | * 7 | * @author aayushchugh 8 | * @constant 9 | */ 10 | export const useAppSelector: TypedUseSelectorHook = useSelector; 11 | -------------------------------------------------------------------------------- /src/hooks/useUpdateObjectState.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction } from "react"; 2 | 3 | /** 4 | * This hook is used to update a single property of object state 5 | * 6 | * It takes a React state setter function and returns a function that takes a key and a value and 7 | * updates the state with the new value 8 | * 9 | * @typeParam T - Type of state 10 | * @param setState setState function that will update the state you want 11 | * 12 | * @returns A function that takes a key and a value and updates the state with the new value. 13 | * 14 | * @example 15 | * ```tsx 16 | * import { useState } from "react"; 17 | * import useUpdateObjectState from '../hooks/useUpdateObjectState'; 18 | * 19 | * interface FormData { 20 | * name: string; 21 | * email: string 22 | * } 23 | * 24 | * const [formData, setFormData] = useState({}) 25 | * const updateForm = useUpdateObjectState(setFormData) 26 | * 27 | * return ( 28 | *
29 | * updateForm("name", e.target.value)} /> 30 | * updateForm("email", e.target.value)} /> 31 | *
32 | * ) 33 | * ``` 34 | * @author aayushchugh 35 | */ 36 | const useUpdateObjectState = (setState: Dispatch>) => { 37 | /** 38 | * This function will update the key of state object that 39 | * is passed 40 | * 41 | * @param key which key should be updated 42 | * @param value what value should be set for the key 43 | * 44 | * @example 45 | * 46 | * ```tsx 47 | * import { useState } from "react"; 48 | * import useUpdateObjectState from '../hooks/useUpdateObjectState'; 49 | * 50 | * interface FormData { 51 | * name: string; 52 | * email: string 53 | * } 54 | * 55 | * const [formData, setFormData] = useState({}) 56 | * const updateForm = useUpdateObjectState(setFormData) 57 | * 58 | * return ( 59 | *
60 | * updateForm("name", e.target.value)} /> 61 | * updateForm("email", e.target.value)} /> 62 | *
63 | * ) 64 | * ``` 65 | * 66 | * @author aayushchugh 67 | */ 68 | return (key: keyof T, value: any) => { 69 | setState(prevState => { 70 | return { ...prevState, [key]: value }; 71 | }); 72 | }; 73 | }; 74 | 75 | export default useUpdateObjectState; 76 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | @layer base { 7 | input[type="number"]::-webkit-inner-spin-button, 8 | input[type="number"]::-webkit-outer-spin-button { 9 | -webkit-appearance: none; 10 | appearance: none; 11 | margin: 0; 12 | } 13 | } 14 | 15 | .no-select { 16 | -webkit-touch-callout: none; /* iOS Safari */ 17 | -webkit-user-select: none; /* Safari */ 18 | -khtml-user-select: none; /* Konqueror HTML */ 19 | -moz-user-select: none; /* Old versions of Firefox */ 20 | -ms-user-select: none; /* Internet Explorer/Edge */ 21 | user-select: none; /* Non-prefixed version, currently supported by Chrome, Edge, Opera and Firefox */ 22 | } 23 | 24 | .no-scroll-x { 25 | overflow-x: hidden; 26 | /* Hide Scrollbar */ 27 | -ms-overflow-style: none; /* IE and Edge */ 28 | scrollbar-width: none; /* Firefox */ 29 | } 30 | 31 | .dashed-border { 32 | background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='5' ry='5' stroke='black' stroke-width='4' stroke-dasharray='2%2c8' stroke-dashoffset='0' stroke-linecap='butt'/%3e%3c/svg%3e"); 33 | border-radius: 5px; 34 | } 35 | 36 | .background { 37 | background-image: url('./assets/photos/background.svg'); 38 | width: 100%; 39 | height: 100%; 40 | position: absolute; 41 | top: 0; 42 | z-index: -10; 43 | background-size: cover; 44 | } 45 | 46 | .back-arrow { 47 | background-color: #DBE2EF; 48 | } 49 | 50 | .back-arrow:hover { 51 | background-color: white; 52 | } -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import { Provider } from "react-redux"; 4 | import store from "./store"; 5 | import App from "./App"; 6 | import reportWebVitals from "./reportWebVitals"; 7 | import "./index.css"; 8 | 9 | const container = document.getElementById("root")!; 10 | const root = createRoot(container); 11 | 12 | root.render( 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | 20 | // If you want to start measuring performance in your app, pass a function 21 | // to log results (for example: reportWebVitals(console.log)) 22 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 23 | reportWebVitals(); 24 | -------------------------------------------------------------------------------- /src/pages/about/about.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from 'framer-motion'; 2 | import { FC, useState } from 'react'; 3 | 4 | //Icons 5 | import { FiMail, FiGithub } from 'react-icons/fi'; 6 | import { AiOutlineArrowDown, AiOutlineArrowUp } from 'react-icons/ai'; 7 | import { CgSortAz } from 'react-icons/cg'; 8 | import { BiTimeFive } from 'react-icons/bi'; 9 | import { MdDesignServices } from 'react-icons/md'; 10 | 11 | const About: FC = () => { 12 | 13 | const [allowRot, setAllowRot] = useState(false); 14 | 15 | const mail = [ 16 | 'work@company.com', 17 | 'school@school.edu', 18 | 'personal@me.com', 19 | ]; 20 | 21 | window.addEventListener('scroll', () => { 22 | if (window.scrollY / window.innerHeight > 0.5) { 23 | setAllowRot(true); 24 | } 25 | else { 26 | setAllowRot(false); 27 | } 28 | 29 | }); 30 | 31 | return ( 32 | 33 |
34 |
35 |
36 | { 37 | Array.from({length: 3}, (_, i) => ( 38 |

Let's began with the problem.

39 | )) 40 | } 41 |

In our live's, we often have different mail account's on different services such as our personal email, our work, our school etc.

42 |
43 |
44 |
45 |
46 | { 47 | mail.map((x, i) => ( 48 |
49 |

{x}

50 | 51 |
52 | )) 53 | } 54 |
55 |
56 |
{ 57 | // Check the current position of the page and scroll to the next section if not at the bottom 58 | if (window.scrollY < window.innerHeight) { 59 | window.scrollTo({ 60 | top: window.innerHeight, 61 | behavior: 'smooth', 62 | }); 63 | } 64 | else { 65 | window.scrollTo({ 66 | top: 0, 67 | behavior: 'smooth', 68 | }); 69 | } 70 | }}> 71 | 72 |
73 |
74 |
75 |
76 |
77 |
78 | 79 |

Organized

80 |
81 |
82 | 83 |

Save's Time

84 |
85 |
86 |
87 |
88 | 89 |

Customize

90 |
91 |
92 | 93 |

Open Source

94 |
95 |
96 |
97 |
98 |

So, Multi Email?

99 |

Multi Email is a web app that allows you to manage all your email accounts in one place. It organizes your emails and offers ton of customization's.

100 |
101 |
102 |
103 | //
104 | //
105 | //
106 | //
107 | // image 114 | //
115 | //
116 | //

117 | // Multi Email development team is carried out by 118 | // passionate developers 119 | //

120 | //

121 | // Project to manage multiple emails at once with lots 122 | // of customization. You can send and receive emails. 123 | // Desktop notifications can be modified. I also want 124 | // to eventually make a mobile version of this app so 125 | // that the desktop and mobile versions can 126 | // communicate. 127 | //

128 | //

129 | // {' '} 130 | // Later on we will also make it a vscode extention, so 131 | // if you spent a lot of time in vscode that's the 132 | // extention you want. 133 | //

134 | //
135 | //
136 | //
137 | //
138 | ); 139 | }; 140 | 141 | export default About; -------------------------------------------------------------------------------- /src/pages/home/Home.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import sample from "../../assets/photos/product-sample.png"; 3 | import logo from "../../assets/logos/icon-transparent.svg"; 4 | import { motion } from "framer-motion"; 5 | import { Link } from "react-router-dom"; 6 | 7 | const Home: FC = () => { 8 | return ( 9 | 10 |
11 |
12 |
13 | sample 14 |
15 |
16 |
17 |
18 |

Hey! This is

19 |

Multi Email

20 |

A simple and easy to use multiple

21 |

email client.

22 |
23 | Get Started 24 | Learn More 25 |
26 |
27 |
28 |
29 |
30 | sample 31 |
32 |
33 |
34 |
35 | {/* An Open Source Projet */} 36 |
37 |

[ An Open Source Project

38 | Github ] 39 |
40 |
41 |
42 | ); 43 | }; 44 | 45 | export default Home; 46 | -------------------------------------------------------------------------------- /src/pages/login/Login.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | 4 | //import photos 5 | import vector from "../../assets/photos/vector-login.svg"; 6 | 7 | //import icons 8 | import { 9 | AiOutlineArrowLeft, 10 | AiFillEye, 11 | AiFillEyeInvisible 12 | } from "react-icons/ai"; 13 | import { IoIosHelpBuoy } from "react-icons/io"; 14 | import { ILoginPayload, loginHandler } from "../../actions/auth.actions"; 15 | import { useAppDispatch } from "../../hooks/useAppDispatch"; 16 | import { AnimatePresence, motion } from "framer-motion"; 17 | import Tooltip from "../../components/tooltip/Tooltip"; 18 | import { IAPIResponseError } from "../../utils/api.util"; 19 | import { AxiosError } from "axios"; 20 | import * as Yup from "yup"; 21 | import { useFormik } from "formik"; 22 | 23 | const Login: FC = () => { 24 | const dispatch = useAppDispatch(); 25 | const navigate = useNavigate(); 26 | const [canShowTooltip, setCanShowTooltip] = useState(false); 27 | const [tooltipMessage, setTooltipMessage] = useState(""); 28 | const [tooltipType, setTooltipType] = useState<"error" | "success">("success"); 29 | 30 | 31 | const onSubmit = async (values: ILoginPayload, actions: any) => { 32 | try { 33 | await dispatch(loginHandler(values)); 34 | setTooltipMessage("You have been logged in successfully!"); 35 | setTooltipType("success"); 36 | setCanShowTooltip(true); 37 | resetForm(); 38 | setTimeout(() => { 39 | setCanShowTooltip(false); 40 | 41 | // Navigate to the dashboard. 42 | } 43 | , 5000); 44 | } 45 | catch (e) { 46 | const er = e as AxiosError; 47 | const msg = er.response?.data.error ? er.response?.data.error : "Something went wrong! Please try again later."; 48 | setTooltipMessage(msg); 49 | setTooltipType("error"); 50 | setCanShowTooltip(true); 51 | setTimeout(() => { 52 | setCanShowTooltip(false); 53 | }, 5000); 54 | } 55 | }; 56 | 57 | /** 58 | * This state will contain formData which will be posted to backend 59 | * when user clicks `login` button 60 | * @constant 61 | * @author aayushchugh 62 | */ 63 | 64 | // const [formData, setFormData] = useState({ 65 | // email: "", 66 | // password: "", 67 | // }); 68 | 69 | // /** 70 | // * Weather password should be visible or not 71 | // * @constant 72 | // * @author KanLSK 73 | // */ 74 | const [showPassword, setShowPassword] = useState(false); 75 | 76 | // const updateFormData = useUpdateObjectState(setFormData); 77 | 78 | /** 79 | * This function will change the password visibility 80 | * @author KanLSK 81 | */ 82 | const passwordVisibility = () => { 83 | setShowPassword(prev => !prev); 84 | }; 85 | 86 | // /** 87 | // * This function will run on form submit and will dispatch a action for login 88 | // * @param e Form Event 89 | // * 90 | // * @author aayushchugh 91 | // */ 92 | // const submitHandler = async (e: FormEvent) => { 93 | // e.preventDefault(); 94 | 95 | // try { 96 | // await dispatch(loginHandler(formData)); 97 | // navigate("/"); 98 | // // TODO: show feedback on UI 99 | // } catch (err) { 100 | // // TODO: show feedback on UI 101 | // } 102 | // }; 103 | 104 | 105 | const loginSchema = Yup.object().shape({ 106 | emailOrUsername: Yup.string().test({ 107 | name: "emailOrUsername", 108 | message: function({value}) { 109 | return value.includes("@") ? "Invalid email" : "Invalid username"; 110 | }, 111 | test: function(value) { 112 | if (value?.includes("@")) { 113 | return Yup.string().email("Invalid email").required("Email is required").isValidSync(value); 114 | } 115 | return Yup.string().required("Username is required").isValidSync(value); 116 | }, 117 | }), 118 | password: Yup.string().required("Password is required"), 119 | }); 120 | 121 | const { values, errors, handleBlur, isSubmitting, handleSubmit, handleChange, resetForm } = useFormik({ 122 | initialValues: { 123 | emailOrUsername: "", 124 | password: "", 125 | }, 126 | validationSchema: loginSchema, 127 | onSubmit, 128 | }) 129 | 130 | 131 | 132 | return ( 133 | 134 | 135 |
136 |
137 |
138 | {/* vector */} 139 | vector 140 |
141 |
142 |
143 | {/* logo */} 144 |
145 |
146 |
147 | {/* email */} 148 |
149 | 150 | 159 |
160 | {/* password */} 161 |
162 | 163 |
164 | 173 |
passwordVisibility()} 176 | > 177 | {showPassword ? ( 178 | 179 | ) : ( 180 | 181 | )} 182 |
183 |
184 |
185 | {/* submit button */} 186 |
187 | 197 |
198 | {/* Already have an account */} 199 |
200 |

Need help?

201 | {/* Sign Up */} 202 | { 205 | e.preventDefault(); 206 | navigate("/support"); 207 | } 208 | }/> 209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 | {/*icon for redirect to home */} 217 |
{ 218 | navigate(-1); 219 | }}> 220 | 221 |
222 | {/* ToolTip */} 223 | 224 | { 225 | canShowTooltip ? : null 226 | } 227 | 228 | 229 | 230 | ); 231 | }; 232 | 233 | export default Login; 234 | 235 | -------------------------------------------------------------------------------- /src/pages/not_found/Not_Found.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router-dom' 2 | import not_found from '../../assets/photos/not_found.svg' 3 | import { motion } from 'framer-motion' 4 | 5 | const NotFound = () => { 6 | 7 | const navigate = useNavigate(); 8 | 9 | return ( 10 | 11 |
12 |
13 |
14 | sample 15 |
16 |
17 |
18 |
19 |

Something is missing...

20 |

We could'nt really find what you're looking for. Maybe try again?

21 |
22 | 27 |
28 |
29 |
30 |
31 |
32 | sample 33 |
34 |
35 |
36 |
37 | ) 38 | } 39 | 40 | export default NotFound -------------------------------------------------------------------------------- /src/pages/password_reset/PasswordReset.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState } from "react"; 2 | import { AnimatePresence, motion } from "framer-motion"; 3 | import Tooltip from "../../components/tooltip/Tooltip"; 4 | import { useFormik } from "formik"; 5 | import * as Yup from "yup"; 6 | import { AxiosError } from "axios"; 7 | import { useNavigate } from "react-router-dom"; 8 | import { IResetRequestPayload, IResetPasswordParams, resetRequestHandler, resetPasswordHandler } from "../../actions/auth.actions"; 9 | import { useAppDispatch } from "../../hooks/useAppDispatch"; 10 | import { IAPIResponseError } from "../../utils/api.util"; 11 | 12 | // Icons 13 | import { MdPassword } from "react-icons/md"; 14 | import { IoIosHelpBuoy } from "react-icons/io"; 15 | import { AiFillEye, AiFillEyeInvisible } from "react-icons/ai"; 16 | 17 | 18 | 19 | const PasswordReset: FC = () => { 20 | 21 | const dispatch = useAppDispatch(); 22 | const navigate = useNavigate(); 23 | 24 | const [showInitalForm, setShowInitialForm] = useState(true); 25 | const [showPassword, setShowPassword] = useState(false); 26 | const [email, setEmail] = useState(""); 27 | const [showConfirmPassword, setShowConfirmPassword] = useState(false); 28 | 29 | const [canShowTooltip, setCanShowTooltip] = useState(false); 30 | const [tooltipMessage, setTooltipMessage] = useState(""); 31 | const [tooltipType, setTooltipType] = useState<"error" | "success">("success"); 32 | 33 | 34 | const onRequestSubmit = async (values: IResetRequestPayload, actions: any) => { 35 | try { 36 | await dispatch(resetRequestHandler(values)); 37 | setTooltipMessage("A password reset link has been sent to your email!"); 38 | setTooltipType("success"); 39 | setCanShowTooltip(true); 40 | setEmail(values.email); 41 | setShowInitialForm(false); 42 | setTimeout(() => { 43 | setCanShowTooltip(false); 44 | } 45 | , 3000); 46 | } 47 | catch (e) { 48 | const er = e as AxiosError; 49 | const msg = er.response?.data.error ? er.response?.data.error : "Something went wrong! Please try again later."; 50 | setTooltipMessage(msg); 51 | setTooltipType("error"); 52 | setCanShowTooltip(true); 53 | setTimeout(() => { 54 | setCanShowTooltip(false); 55 | }, 5000); 56 | } 57 | }; 58 | 59 | const onResetSubmit = async (values: IResetPasswordParams, actions: any) => { 60 | try { 61 | await dispatch(resetPasswordHandler(values, requestForm.values)); 62 | setTooltipMessage("Success! Your password was reset. Redirecting to login page..."); 63 | setTooltipType("success"); 64 | setCanShowTooltip(true); 65 | setTimeout(() => { 66 | setCanShowTooltip(false); 67 | navigate("/login"); 68 | } 69 | , 3000); 70 | } 71 | catch (e) { 72 | const er = e as AxiosError; 73 | const msg = er.response?.data.error ? er.response?.data.error : "Something went wrong! Please try again later."; 74 | setTooltipMessage(msg); 75 | setTooltipType("error"); 76 | setCanShowTooltip(true); 77 | setTimeout(() => { 78 | setCanShowTooltip(false); 79 | }, 5000); 80 | } 81 | } 82 | 83 | const resetRequestSchema = Yup.object().shape({ 84 | email: Yup.string().email("Invalid email").required("Email is required"), 85 | }); 86 | 87 | const resetPasswordSchema = Yup.object().shape({ 88 | payload: Yup.object().shape({ 89 | password: Yup.string().required("Password is required").min(8, "Password must be at least 8 characters long"), 90 | cpassword: Yup.string().required("Confirm password is required").oneOf([Yup.ref("password"), null], "Passwords must match"), 91 | }), 92 | passwordResetCode: Yup.number().min(1000, "Invalid code").max(9999, "Invalid code").required("Code is required"), 93 | }); 94 | 95 | 96 | const requestForm = useFormik({ 97 | initialValues: { 98 | email: email, 99 | }, 100 | validationSchema: resetRequestSchema, 101 | onSubmit: onRequestSubmit, 102 | }) 103 | 104 | const resetForm = useFormik({ 105 | initialValues: { 106 | payload: { 107 | password: "", 108 | cpassword: "", 109 | }, 110 | passwordResetCode: 0, 111 | }, 112 | validationSchema: resetPasswordSchema, 113 | onSubmit: onResetSubmit, 114 | }); 115 | 116 | 117 | return ( 118 | 119 | 120 | 121 | 122 |
123 |
124 | 125 |
126 |
127 |
128 |
129 | 130 | { 131 | showInitalForm ? ( 132 | 133 |

Reset Password

134 | 135 |
136 |

Enter your email address and we'll send you a link to reset your password.

137 |
138 | 139 | 148 |
149 |
150 | {/* submit button */} 151 |
152 | 162 |
163 | {/* Already have an account */} 164 |
165 |

Need help?

166 | {/* Sign Up */} 167 | { 170 | e.preventDefault(); 171 | navigate("/support"); 172 | } 173 | }/> 174 |
175 |
176 | ) : ( 177 | 178 |

Reset Password

179 |
180 |

We have sent you an email with a link to reset your password.

181 |
182 | {/* new password */} 183 |
184 | 185 |
186 | 195 |
{ 198 | setShowPassword(prev => !prev); 199 | }} 200 | > 201 | {showPassword ? ( 202 | 203 | ) : ( 204 | 205 | )} 206 |
207 |
208 |
209 | {/* confirm new password */} 210 |
211 | 212 |
213 | 222 |
{ 225 | setShowConfirmPassword(prev => !prev); 226 | }} 227 | > 228 | {showConfirmPassword ? ( 229 | 230 | ) : ( 231 | 232 | )} 233 |
234 |
235 |
236 | {/* code */} 237 |
238 | 239 |
240 | 249 |
250 |
251 | {/* submit button */} 252 |
253 | 263 |
264 |
265 |
266 |
267 | ) 268 | } 269 |
270 |
271 |
272 |
273 | 274 | 275 | { 276 | canShowTooltip ? : null 277 | } 278 | 279 | 280 | 281 | ); 282 | } 283 | 284 | export default PasswordReset; -------------------------------------------------------------------------------- /src/pages/signup/Signup.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { ISignupPayload, signupHandler } from "../../actions/auth.actions"; 4 | import { useAppDispatch } from "../../hooks/useAppDispatch"; 5 | import Tooltip from "../../components/tooltip/Tooltip"; 6 | import * as yup from 'yup'; 7 | import { useFormik } from "formik"; 8 | //import photos 9 | import vector from "../../assets/photos/vector-signup.svg"; 10 | //import icons 11 | import { 12 | AiFillEye, 13 | AiFillEyeInvisible 14 | } from "react-icons/ai"; 15 | import { AnimatePresence, motion } from "framer-motion"; 16 | import { AxiosError } from "axios"; 17 | import { IAPIResponseError } from "../../utils/api.util"; 18 | /** 19 | * Signup page 20 | * @returns JSX.Element 21 | */ 22 | const Signup: FC = () => { 23 | const dispatch = useAppDispatch(); 24 | 25 | const [showPassword, setShowPassword] = useState(false); 26 | const [showConfirmPassword, setShowConfirmPassword] = 27 | useState(false); 28 | const [canShowTooltip, setCanShowTooltip] = useState(false); 29 | const [tooltipMessage, setTooltipMessage] = useState(""); 30 | const [tooltipType, setTooltipType] = useState<"error" | "success">("success"); 31 | 32 | 33 | const { values, errors, handleBlur, isSubmitting, handleSubmit, handleChange, resetForm } = useFormik({ 34 | initialValues: { 35 | username: "", 36 | email: "", 37 | password: "", 38 | cpassword: "", 39 | acceptedTermsAndConditions: false, 40 | receiveMarketingEmails: false 41 | }, 42 | validationSchema: yup.object().shape({ 43 | username: yup.string().required("Username is required").min(3, "Username must be at least 3 characters long").max(20, "Username must be at most 20 characters long").lowercase("Username must be lowercase"), 44 | email: yup.string().email().required(), 45 | password: yup.string().min(8).required(), 46 | cpassword: yup.string().oneOf([yup.ref("password")], "Passwords must match").required(), 47 | acceptedTermsAndConditions: yup.boolean().oneOf([true], "You must accept the acceptedTermsAndConditions and conditions").required(), 48 | receiveMarketingEmails: yup.boolean().oneOf([true, false]).required() 49 | }), 50 | onSubmit: async (values: ISignupPayload) => { 51 | try { 52 | console.log(values); 53 | await dispatch(signupHandler(values)); 54 | setTooltipMessage("Your account has been created successfully. Check your email for verification link."); 55 | setTooltipType("success"); 56 | setCanShowTooltip(true); 57 | resetForm(); 58 | setTimeout(() => { 59 | setCanShowTooltip(false); 60 | } 61 | , 5000); 62 | } 63 | catch (e) { 64 | const error = e as AxiosError; 65 | console.log(error) 66 | setTooltipMessage("An error occured while trying to signup. Please try again later."); 67 | setTooltipType("error"); 68 | setCanShowTooltip(true); 69 | setTimeout(() => { 70 | setCanShowTooltip(false); 71 | }, 5000); 72 | } 73 | }, 74 | }); 75 | 76 | // /** 77 | // * This state will contain formData which will be posted to backend 78 | // * when user clicks `signup` button 79 | // * @constant 80 | // * @author aayushchugh 81 | // */ 82 | // const [formData, setFormData] = useState({ 83 | // username: "", 84 | // email: "", 85 | // password: "", 86 | // cpassword: "", 87 | // acceptedTermsAndConditions: false, 88 | // receiveMarketingEmails: false, 89 | // }); 90 | 91 | // const [error, setError] = useState(""); 92 | 93 | 94 | // const updateFormData = useUpdateObjectState(setFormData); 95 | 96 | // /** 97 | // * This function will be called when form is submitted 98 | // * @param e form event 99 | // * 100 | // * @author aayushchugh 101 | // */ 102 | // const submitHandler = async (e: FormEvent) => { 103 | // e.preventDefault(); 104 | // try { 105 | // await dispatch(signupHandler(formData)); 106 | // navigate(`/verification?email=${formData.email}`); 107 | 108 | // // TODO: show success message on UI 109 | // } catch (err) { 110 | // // TODO: show error on UI 111 | // } 112 | // }; 113 | 114 | //show password in input field 115 | // const passwordVisibility = () => { 116 | // setShowPassword((prev) => !prev); 117 | // }; 118 | 119 | // //show confirm password in input field 120 | // const confirmPasswordVisibility = () => { 121 | // setShowConfirmPassword((prev) => !prev); 122 | // }; 123 | 124 | /* 125 | TODO: Add validation for following fields 126 | - username 127 | - [x]required 128 | - [x]should not be shorter than 3 characters 129 | - [x]should not be longer than 50 characters 130 | 131 | - email 132 | - [x]required 133 | - [x]should be a valid email 134 | 135 | - password 136 | - [x]required 137 | - [x]minimum length of 6 characters 138 | 139 | - cpassword 140 | - [x]required 141 | - [x]minimum length of 6 characters 142 | - [x]password and cpassword should be same 143 | -features 144 | - [x]enter key for submit 145 | - [x]responsive design 146 | */ 147 | 148 | return ( 149 | 150 | 151 |
152 | 153 |
154 |
155 | {/* vector */} 156 | vector 157 |
158 |
159 |
160 | {/* logo */} 161 |
162 |
163 |
164 | {/* username */} 165 |
166 | 167 | 176 |
177 | {/* email */} 178 |
179 | 180 | 189 |
190 | {/* password */} 191 |
192 | 193 |
194 | 203 |
setShowPassword(!showPassword)} 206 | > 207 | {showPassword ? ( 208 | 209 | ) : ( 210 | 211 | )} 212 |
213 |
214 |
215 |
216 | 217 |
218 | 227 |
setShowConfirmPassword(!showConfirmPassword)} 230 | > 231 | {showConfirmPassword ? ( 232 | 233 | ) : ( 234 | 235 | )} 236 |
237 |
238 |
239 | {/* acceptedTermsAndConditions Conditions and Markettings Opt In */} 240 |
241 |
242 | 250 | 251 |
252 |
253 | 261 | 262 |
263 |
264 | {/* submit button */} 265 |
266 | 276 |
277 | {/* Already have an account */} 278 |
279 |

Already have an account? Login

280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 | {/* // Note: Cannot include this due to the sheer size of the page. 288 |
{ 289 | navigate(-1); 290 | }}> 291 | 292 |
*/} 293 | {/* ToolTip */} 294 | 295 | { 296 | canShowTooltip ? : null 297 | } 298 | 299 | 300 | 301 | ); 302 | }; 303 | 304 | export default Signup; 305 | -------------------------------------------------------------------------------- /src/pages/support/Support.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { FC, useEffect, useState } from "react"; 3 | import { AnimatePresence, motion } from "framer-motion"; 4 | import { useNavigate } from "react-router-dom"; 5 | 6 | // Icons 7 | import { MdPassword } from "react-icons/md"; 8 | import { HiOutlineDocumentDuplicate } from "react-icons/hi"; 9 | import { TbBrandDiscord } from "react-icons/tb"; 10 | 11 | const Support: FC = () => { 12 | 13 | const navigate = useNavigate(); 14 | const [pageLoad, setPageLoad] = useState(true); 15 | const [externals, setExternals] = useState({}); 16 | 17 | useEffect(() => { 18 | if(pageLoad) { 19 | 20 | // Run Once; Fetch Discord Link from .env (TOOD: Write a handler later) [TODO: Provide them in the config] 21 | const discordLink = process.env.REACT_APP_DISCORD_LINK || "https://discord.gg/GtSftczQX5"; 22 | const documentationLink = process.env.REACT_APP_MULTIEMAIL_DOCS_LINK || "https://docs.multiemail.us"; 23 | 24 | setExternals({ 25 | ...externals, 26 | discord: discordLink, 27 | documentation: documentationLink 28 | }); 29 | setPageLoad(false); 30 | } 31 | }, [pageLoad]) 32 | 33 | 34 | 35 | return ( 36 | 37 | 38 |
39 | 40 | 41 |
{ 43 | navigate("/support/password/reset"); 44 | } 45 | }> 46 |
47 | 48 |
49 |
50 |

Password?

51 |

Did you forget your password? Request a reset link.

52 |
53 |
54 |
{ 56 | if(externals.documentation) { 57 | window.open(externals.documentation, "_blank"); 58 | } else { 59 | console.log("[Config] The configuration is not setup poperly. Viist 'placehoder link' for more information."); 60 | } 61 | } 62 | }> 63 |
64 | 65 |
66 |
67 |

Documentation?

68 |

We wrote a simple guide for you!

69 |
70 |
71 |
{ 73 | if(externals.discord) { 74 | window.open(externals.discord, "_blank"); 75 | } else { 76 | console.log("[Config] The configuration is not setup poperly. Viist 'placehoder link' for more information."); 77 | } 78 | } 79 | }> 80 |
81 | 82 |
83 |
84 |

Discord?

85 |

Need extra help? Join our discord.

86 |
87 |
88 |
89 |
90 |
91 |
92 | ); 93 | } 94 | 95 | export default Support; -------------------------------------------------------------------------------- /src/pages/verification/Verification.tsx: -------------------------------------------------------------------------------- 1 | import * as yup from "yup"; 2 | import { FC, useState, useEffect } from "react"; 3 | import { useFormik } from "formik"; 4 | import { useLocation } from "react-router-dom"; 5 | import queryString from "query-string"; 6 | import mmIcon from "../../assets/logos/icon-transparent.svg"; 7 | import verificationVector from "../../assets/photos/vector-verify.svg"; 8 | import { AnimatePresence, motion } from "framer-motion"; 9 | import Tooltip from "../../components/tooltip/Tooltip"; 10 | import NotFound from "../not_found/Not_Found"; 11 | 12 | // Icons 13 | import { 14 | IVerificationPayload, 15 | verifyHandler, 16 | } from "../../actions/auth.actions"; 17 | import { AxiosError } from "axios"; 18 | import { IAPIResponseError } from "../../utils/api.util"; 19 | import { useAppDispatch } from "../../hooks/useAppDispatch"; 20 | 21 | /** NOTE: 22 | * This page will only be accessible through a link in the signed up email, which will have two parameters: 23 | * 1. jwt token - (key param) - to get the user's email address and verification status. (via JWT _id payload) 24 | * 2. verification code - (code param) - to verify the user's email address. ex. 1234 25 | * 26 | * Example URL: /verify?c=<1234>&k= 27 | * @author is-it-ayush 28 | */ 29 | const Verification: FC = () => { 30 | // Convert them all into redux state. 31 | const dispatch = useAppDispatch(); 32 | const location = useLocation(); 33 | 34 | const parsedParams = queryString.parse(location.search); 35 | const [pageLoaded, setPageLoaded] = useState(false); 36 | 37 | const [token, setToken] = useState(""); 38 | const [canRequestVerification, setCanRequestVerification] = 39 | useState(false); 40 | 41 | const [canShowTooltip, setCanShowTooltip] = useState(false); 42 | const [tooltipMessage, setTooltipMessage] = useState(""); 43 | const [tooltipType, setTooltipType] = useState<"error" | "success">( 44 | "success" 45 | ); 46 | 47 | const onSubmit = async (values: IVerificationPayload, actions: any) => { 48 | try { 49 | await dispatch(verifyHandler(values, token)); 50 | setTooltipMessage( 51 | "Your email has been verified successfully! You can login now." 52 | ); 53 | setTooltipType("success"); 54 | setCanShowTooltip(true); 55 | resetForm(); 56 | setTimeout(() => { 57 | setCanShowTooltip(false); 58 | }, 5000); 59 | } catch (e) { 60 | const er = e as AxiosError; 61 | setTooltipMessage("Something went wrong. Please try again later."); 62 | setTooltipType("error"); 63 | setCanShowTooltip(true); 64 | setTimeout(() => { 65 | setCanShowTooltip(false); 66 | }, 5000); 67 | } 68 | }; 69 | 70 | useEffect(() => { 71 | // This will run only once when the page is loaded. 72 | if (!pageLoaded) { 73 | if (parsedParams.v && parsedParams.t) { 74 | if (parsedParams.t.length > 0) { 75 | // k (jwt) present --> Make Request --> Validate JWT (on backend) ---> Retrieve status & email --> if verification status is false --> setCanRequestVerification(true); 76 | // For now, we'll just set it to true. 77 | setCanRequestVerification(true); 78 | setToken(String(parsedParams.t)); 79 | if (parsedParams.v.length > 0) { 80 | values.verificationCode = Number(parsedParams.v); 81 | } 82 | } 83 | } 84 | setPageLoaded(true); 85 | } 86 | }, [pageLoaded]); 87 | 88 | const verificationSchema = yup.object().shape({ 89 | verificationCode: yup 90 | .number() 91 | .required("Verification code is required") 92 | .min(1000, "Verification code must be 4 digits") 93 | .max(9999, "Verification code must be 4 digits"), 94 | }); 95 | 96 | // We can use formik for form validation and form control. 97 | const { 98 | values, 99 | errors, 100 | touched, 101 | handleBlur, 102 | isSubmitting, 103 | handleSubmit, 104 | handleChange, 105 | resetForm, 106 | } = useFormik({ 107 | initialValues: { 108 | verificationCode: 0, 109 | }, 110 | validationSchema: verificationSchema, 111 | onSubmit, 112 | }); 113 | 114 | return ( 115 | 116 | 117 | {canRequestVerification ? ( 118 | 125 |
126 |
127 | {/* This will hold the vector */} 128 | verification 133 |
134 |
135 |
136 | {/* This will hold the information */} 137 |
138 |
139 | checked 144 |

145 | Multi Email 146 |

147 |
148 |
149 |

150 | Authenticate Your Account 151 |

152 |

153 | Please confirm your account by 154 | entering the verification code sent 155 | to your email address. 156 |

157 |
158 |

159 | {"thisisaniceemail@nice.mail"} 160 |

161 |
162 |
163 |
164 | {/* This will hold the input field */} 165 |
166 | 172 | 190 |
191 |
192 | {/* This will hold the button */} 193 | 207 |
208 |
209 |
210 |
211 | {/* This will hold the buttons */} 212 |
213 |

214 | It may take a minute to receive your 215 | code. 216 |

217 |
218 |
219 |
220 |
221 | 222 | ) : ( 223 | 224 | )} 225 | 226 | {canShowTooltip ? ( 227 | 228 | ) : null} 229 | 230 | 231 | 232 | ); 233 | }; 234 | 235 | export default Verification; 236 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | 3 | const store = configureStore({ 4 | reducer: {}, 5 | }); 6 | 7 | /** 8 | * Use TAppDispatch type for the constant that you will create using useAppDispatch hook 9 | * 10 | * @author aayushchugh 11 | * 12 | * @example 13 | * ```tsx 14 | * cont dispatch: TAppDispatch = useAppDispatch() 15 | * ``` 16 | */ 17 | export type TAppDispatch = typeof store.dispatch; 18 | 19 | /** 20 | * Use `TRootState` as type for `state` parameter in useAppSelector 21 | * 22 | * @author aayushchugh 23 | * 24 | * @example 25 | * ```tsx 26 | * const user = useAppSelector((state: TRootState) => state.user) 27 | * ``` 28 | */ 29 | export type TRootState = ReturnType; 30 | 31 | export default store; 32 | -------------------------------------------------------------------------------- /src/utils/api.util.ts: -------------------------------------------------------------------------------- 1 | import axios, { 2 | AxiosError, 3 | AxiosInstance, 4 | AxiosRequestConfig, 5 | AxiosResponse, 6 | } from "axios"; 7 | 8 | /** 9 | * Use this `API` instead of `axios` for api requests 10 | * throughout the code 11 | * 12 | * This is instance of `axios` with custom config 13 | * and interceptors 14 | * 15 | * @constant 16 | * @author aayushchugh 17 | */ 18 | const API: AxiosInstance = axios.create({ 19 | baseURL: process.env.REACT_APP_API_URL, 20 | timeout: 10000, 21 | }); 22 | 23 | // TODO: Add request interceptor to add authorization headers in every request 24 | 25 | /** 26 | * This interceptor will format the response and error from API 27 | * 28 | * You will get formatted response and error when you use `API` for sending 29 | * requests to API 30 | * @author aayushchugh 31 | */ 32 | API.interceptors.response.use( 33 | (res: AxiosResponse) => { 34 | return Promise.resolve(res); 35 | }, 36 | async (err: AxiosError) => { 37 | interface APIResponse extends IAPIResponseSuccess { 38 | access_token: string; 39 | } 40 | 41 | const accessToken = localStorage.getItem("access_token"); 42 | const refreshToken = localStorage.getItem("refresh_token"); 43 | 44 | // auto refresh access token if it is expired 45 | if ( 46 | err.response?.status === 403 && 47 | err.response.data.error === 48 | "User is not logged in or access_token is expired" && 49 | accessToken && 50 | refreshToken 51 | ) { 52 | const res = await API.get("/auth/refresh", { 53 | headers: { 54 | "x-refresh": refreshToken, 55 | }, 56 | }); 57 | 58 | if (res.data.access_token) { 59 | localStorage.setItem("access_token", res.data.access_token); 60 | // resend the request with new access token 61 | const response = await API(err.config as AxiosRequestConfig); 62 | return Promise.resolve(response); 63 | } 64 | } 65 | 66 | const formattedErr = { 67 | status: err.response?.status, 68 | statusText: err.response?.statusText, 69 | code: err.code, 70 | data: err.response?.data, 71 | }; 72 | 73 | return Promise.reject(formattedErr); 74 | } 75 | ); 76 | 77 | /** 78 | * This type contains data which will be sent to API on success 79 | * 80 | * extend this interface when you want to add more properties to APIResponse 81 | * 82 | * @example 83 | * ```tsx 84 | * interface IGetAllUsersResponse extends IAPIResponseSuccess { 85 | * records: User[] 86 | * } 87 | * ``` 88 | * @author aayushchugh 89 | */ 90 | export interface IAPIResponseSuccess { 91 | message: string; 92 | } 93 | 94 | /** 95 | * This type contains data which will be sent by API on error 96 | * 97 | * @author aayushchugh 98 | */ 99 | export interface IAPIResponseError { 100 | error: string; 101 | } 102 | 103 | export default API; 104 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{html,tsx, js, ts}"], 4 | theme: { 5 | screens: { 6 | sm: "640px", 7 | md: "768px", 8 | lg: "1024px", 9 | xl: "1280px", 10 | }, 11 | fontFamily: { 12 | sans: ["Inter var", "sans-serif"], 13 | serif: ["Georgia", "serif"], 14 | mono: ["Menlo", "Monaco", "Consolas", "monospace"], 15 | poppins: ["Poppins", "sans-serif"], 16 | roboto: ["Roboto", "sans-serif"], 17 | }, 18 | fontWeights: { 19 | light: 200, 20 | normal: 400, 21 | medium: 500, 22 | bold: 700, 23 | black: 900, 24 | }, 25 | extend: { 26 | colors: { 27 | primary: "#5271FF", 28 | "light": { 29 | "white": { 30 | 100: "#F9F7F7", 31 | 200: "#DBE2EF" 32 | }, 33 | "blue": { 34 | 100: "#3F72AF", 35 | 200: "#112D4E", 36 | }, 37 | }, 38 | "dark": { 39 | "blue": { 40 | 100: "#BBE1FA", 41 | 200: "#3282B8", 42 | 300: "#0F4C75", 43 | 400: "#1B262C", 44 | }, 45 | }, 46 | // <--- New Themes Color Palettes ---> 47 | "patriot": { 48 | darkblue: "#16213E", 49 | blue: "#0F3460", 50 | purple: "#533483", 51 | red: "#E94560" 52 | }, 53 | "aqua": { 54 | "teal": { 55 | 100: "#E7F6F2", 56 | 200: "#A5C9CA", 57 | 300: "#395B64", 58 | 400: "#2C3333" 59 | } 60 | }, 61 | // Saints Row Reference 62 | "saints": { 63 | "purple": { 64 | 100: "#372948", 65 | 200: "#251B37", 66 | }, 67 | pink: "#FFCACA", 68 | white: "#FFECEF" 69 | } 70 | }, 71 | }, 72 | }, 73 | plugins: [], 74 | }; 75 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | // Comments are supported, like tsconfig.json 3 | "entryPoints": [ 4 | "src/*/**/*", 5 | "src/componenets/**/*", 6 | "src/hooks/**/*", 7 | "src/pages/home/**/*", 8 | "src/pages/login/**/*", 9 | "src/pages/not_found/**/*", 10 | "src/utils/**/*", 11 | "src/App.tsx", 12 | "src/index.tsx" 13 | ], 14 | "out": "docs" 15 | } --------------------------------------------------------------------------------