├── .env.sample
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── feature_request.yml
│ └── general_issue.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── welcome-contributors.yml
├── .gitignore
├── .husky
└── pre-commit
├── .prettierignore
├── CODE_OF_CONDUCT.md
├── README.md
├── package.json
├── public
├── _redirects
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── recievedToken.html
└── robots.txt
├── src
├── App.css
├── App.test.tsx
├── App.tsx
├── assets
│ ├── certificate_assets
│ │ ├── cert-body.png
│ │ ├── certificate.css
│ │ └── projectpiker.css
│ ├── darkwave1.svg
│ ├── darkwave2.svg
│ ├── darkwave3.svg
│ ├── home.json
│ ├── lightwave1.svg
│ ├── lightwave2.svg
│ ├── lightwave3.svg
│ └── loginlightwave1.svg
├── components
│ ├── Button.tsx
│ ├── CertificatePdf.tsx
│ ├── DarkModeButton.tsx
│ ├── Footer.tsx
│ ├── Fork.tsx
│ ├── GenerateCertificate.tsx
│ ├── Header.tsx
│ ├── MicroLogin.tsx
│ ├── OpenPopup.ts
│ ├── ProjectDataTab.tsx
│ ├── ProjectInput
│ │ ├── BitbucketProjectInput.tsx
│ │ ├── GithubProjectInput.tsx
│ │ ├── GitlabProjectInput.tsx
│ │ └── index.tsx
│ ├── ProjectPicker.tsx
│ ├── ProjectProgressBar
│ │ ├── ProjectProgressBar.module.css
│ │ └── ProjectProgressBar.tsx
│ ├── ProjectStat.tsx
│ ├── Spinner.tsx
│ └── Toast.ts
├── config.ts
├── context
│ ├── AuthContext
│ │ ├── AuthProvider.tsx
│ │ └── AuthReducer.ts
│ ├── CertificateContext
│ │ ├── CertificateProvider.tsx
│ │ ├── CertificateReducer.ts
│ │ └── CertificateRouter.tsx
│ ├── ProjectContext
│ │ ├── ProjectRouter.tsx
│ │ └── index.tsx
│ └── ThemeContext
│ │ ├── ThemeProvider.tsx
│ │ └── ThemeReducer.ts
├── index.css
├── index.tsx
├── logo.svg
├── pages
│ ├── Certificate.tsx
│ ├── Login.tsx
│ ├── Main.tsx
│ └── VerifyCertificate.tsx
├── react-app-env.d.ts
├── reportWebVitals.ts
├── router
│ ├── config.ts
│ └── route.tsx
├── setupTests.ts
├── sitemap
│ └── .gitkeep
└── tailwind.css
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock
/.env.sample:
--------------------------------------------------------------------------------
1 | REACT_APP_API_BASE_URL=XXXXX
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: Report a bug
3 | title: "[Bug]: "
4 | labels:
5 | - "bug"
6 | body:
7 | - type: checkboxes
8 | attributes:
9 | label: Preflight Checklist
10 | description: Please ensure you've completed all of the following.
11 | options:
12 | - label: I have read the Contribution.md for this project.
13 | required: true
14 | - label: I agree to follow the Code of Conduct that this project adheres to.
15 | required: true
16 | - label: I have searched the issue for a feature request that matches the one I want to file, without success.
17 | required: true
18 | - type: input
19 | attributes:
20 | label: Version
21 | description: What version of are you using?
22 | placeholder: 0.0.0
23 | validations:
24 | required: true
25 | - type: dropdown
26 | attributes:
27 | label: What arch are you using?
28 | options:
29 | - x64
30 | - ia32
31 | - arm64 (including Apple Silicon)
32 | - Other (specify below)
33 | validations:
34 | required: true
35 | - type: textarea
36 | attributes:
37 | label: Current Behavior
38 | description: A clear description of what actually happens.
39 | validations:
40 | required: true
41 | - type: textarea
42 | attributes:
43 | label: Expected Behavior
44 | description: A clear and concise description of what you expected to happen.
45 | validations:
46 | required: true
47 | - type: textarea
48 | attributes:
49 | label: Additional Information
50 | description: If your problem needs further explanation, or if the issue you're seeing cannot be reproduced in a gist, please add more information here.
51 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Suggest an idea
3 | title: "[Feature Request]: "
4 | labels:
5 | - "enhancement"
6 | body:
7 | - type: checkboxes
8 | attributes:
9 | label: Preflight Checklist
10 | description: Please ensure you've completed all of the following.
11 | options:
12 | - label: I have read the Contribution.md for this project.
13 | required: true
14 | - label: I agree to follow the Code of Conduct that this project adheres to.
15 | required: true
16 | - label: I have searched the issue for a feature request that matches the one I want to file, without success.
17 | required: true
18 | - type: textarea
19 | attributes:
20 | label: Problem Description
21 | description: Please add a clear and concise description of the problem you are seeking to solve with this feature request.
22 | validations:
23 | required: true
24 | - type: textarea
25 | attributes:
26 | label: Proposed Solution
27 | description: Describe the solution you'd like in a clear and concise manner.
28 | validations:
29 | required: true
30 | - type: textarea
31 | attributes:
32 | label: Alternatives Considered
33 | description: A clear and concise description of any alternative solutions or features you've considered.
34 | validations:
35 | required: true
36 | - type: textarea
37 | attributes:
38 | label: Additional Information
39 | description: Add any other context about the problem here.
40 | validations:
41 | required: false
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/general_issue.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: General issue
3 | about: Suggest an issue for this project
4 | title: ""
5 | labels: ""
6 | assignees: ""
7 | ---
8 |
9 | # Prerequisites:
10 |
11 | Please answer the following questions for yourself before submitting an issue. **YOU MAY DELETE THE PREREQUISITES SECTION.**
12 |
13 |
14 |
15 | - [ ] I checked to make sure that this issue has not already been filed.
16 |
17 | # Expected Behavior:
18 |
19 | Please describe the behavior you are expecting.
20 |
21 | # Current Behavior:
22 |
23 | What is the current behavior?
24 |
25 | # Solution:
26 |
27 | Please describe what will you do to solve this issue or your approach to solve this issue.
28 |
29 | # Screenshots (optional):
30 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Description:
2 |
3 | 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.
4 |
5 | Fixes # (issue no.)
6 |
7 |
8 |
9 | ## Type of change:
10 |
11 |
12 |
13 | - [ ] Bug fix (non-breaking change which fixes an issue)
14 | - [ ] New feature (non-breaking change which adds functionality)
15 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
16 | - [ ] This change requires a documentation update
17 |
18 | # Checklist:
19 |
20 |
21 |
22 | - [ ] My code follows the style guidelines of this project.
23 | - [ ] I have performed a self-review of my own code.
24 | - [ ] I have commented my code, particularly in hard-to-understand areas.
25 | - [ ] I have made corresponding changes to the documentation.
26 | - [ ] My changes generate no new warnings.
27 | - [ ] I have added tests that prove my fix is effective or that my feature works.
28 | - [ ] New and existing unit tests pass locally with my changes.
29 | - [ ] Any dependent changes have been merged and published in downstream modules.
30 |
31 | # Screenshots / Video:
32 |
--------------------------------------------------------------------------------
/.github/workflows/welcome-contributors.yml:
--------------------------------------------------------------------------------
1 | name: Welcome contributors
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - opened
7 | issues:
8 | types:
9 | - opened
10 |
11 | jobs:
12 | welcome:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/first-interaction@v1
16 | with:
17 | repo-token: ${{ secrets.GITHUB_TOKEN }}
18 | issue-message: |
19 | Hello there ${{ github.actor }} 👋
20 |
21 | Welcome to Open-Certs !!💖🥳
22 |
23 | Thank you 🎉 for opening an issue in this project. oc-frontend fosters an open and welcoming environment for all our contributors.🌸
24 |
25 | Please take care of few points : Clearly specify the title and description of the issue . You can even add supporting material like Screenshots etc.
26 |
27 | Incase you want to claim this issue, please tag the maintainers by commenting down below! We will try to get back to you as soon as we can.👀
28 |
29 | 👩💻 If you have any interesting ideas, just open an issue. We would love to hear you and engage in discussions.
30 |
31 | pr-message: |
32 | Hello there ${{ github.actor }} 👋
33 |
34 | Thank you and congrats 🎉 for opening a PR in this project.✨
35 |
36 | oc-frontend fosters an open and welcoming environment for all our contributors.🌸
37 |
38 | Please take care of few points : Clearly specify the title and description of the Pull Request . Specify the issue no using " #[issue_no] "
39 |
40 | Please tag the maintainers by commenting down below! We will review it as soon as we can.👀
41 |
42 |
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | .firebaserc
27 | firebase.json
28 | .firebase
29 | .babelrc
30 | GenerateSiteMap.js
31 | generator.ts
32 | index.ts
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 |
5 | npm run format && git add .
6 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | build
2 | node_modules
3 | tailwind.generated.css
4 | .github
5 |
--------------------------------------------------------------------------------
/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 | shukhu10@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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OC-Frontend
2 |
3 |
4 |
5 | > This includes the frontend for Open-Certs. 📜
6 |
7 | After seeing so many open-source projects being monetized :dollar: without giving any recognition to contributors, Open-Certs comes with the vision to certify every open-source contribution :tickets:.
8 |
9 | It really is that easy! 💚
10 |
11 | And the best part of it? **Open-Certs is open source!** 🤩
12 |
13 | You can also contribute to it 🏆.
14 |
15 | **The website is live and can be visited on https://open-certs.dev/** 😃
16 |
17 |
18 |
19 |
20 |
21 |
22 | Live-Demo
23 |
24 |
25 | ## Table of Contents 🔖
26 |
27 | - [Major Objective](#major-objective-pushpin)
28 | - [Tech Stack](#tech-stack-)
29 | - [Set Up](#set-up-)
30 | - [Running the application locally](#running-the-application-locally-%EF%B8%8F)
31 | - [Contributing](#contributing-)
32 | - [Contribution Guidelines](#contribution-guidelines-)
33 | - [Contributors](#contributors)
34 | - [Visitor's Count](#visitors-count-)
35 |
36 |
37 | ## Major Objective :pushpin:
38 |
39 | - To let beginners venture into the world of Open Source.
40 | - To provide validation to every open-source contribution.
41 |
42 |
43 | ## Tech Stack 💻
44 |
45 | Open-Certs frontend is based on component-based architecture. It takes advantage of `Reactjs` as UI library, `Typescript` as programming language, `TailwindCSS`, `Material-UI`, etc and consumes [Open-Certs-Backend](https://github.com/open-certs/oc-backend) to delivery required services to the users.
46 |
47 |
48 | ## Set Up 🔨
49 |
50 | - To get started, install the required node modules:
51 |
52 | ```
53 | yarn install
54 | ```
55 |
56 | - Then copy the `.env.sample` to `.env` and configure it.
57 |
58 |
59 | ## Running the application locally ⚙️
60 |
61 | Then issue the following command to run the server:
62 |
63 | ```
64 | yarn start
65 | ```
66 |
67 |
68 |
69 | ## Contributing 🏗
70 |
71 | Any contributions you make are **greatly appreciated**.
72 |
73 | 1. Create / Choose an issue [here](https://github.com/open-certs/oc-frontend/issues).
74 | 2. Get the issue assigned to yourself by commenting.
75 | 3. Fork the Project
76 | 4. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
77 | 5. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
78 | 6. Push to the Branch (`git push origin feature/AmazingFeature`)
79 | 7. Open a Pull Request on `dev` branch
80 |
81 | **Voila :exclamation: You have made a PR to the Open-Certs :boom: . Wait for your submission to be accepted ✔️ and your PR to be merged.**
82 |
83 | **Congratulations! 🎉 You've made your first contribution! 🙌🏼**
84 |
85 |
86 | ## Contribution Guidelines 📋
87 |
88 | - Please read our [Code of Conduct](https://github.com/open-certs/oc-frontend/blob/main/CODE_OF_CONDUCT.md) file for contributing towards the project.
89 | - If you are creating an issue, please make sure that it is a valid and significant issue that will actually contribute towards the project.
90 | - Write clear meaningful git commit messages 📧.
91 | - Make sure your PR's description is clear and it mentions the issue number that your PR fixes.
92 | - When you're submitting a PR, it would be really awesome if you add a screenshot 📸 or video 📽️ of your change or a link 🔗 to a deployment where it can be tested out along with your PR. It makes it very easy for the reviewers and you'll also get reviews quicker.
93 | - When you make very minor changes to a PR of yours (like for example fixing a text in button, minor changes requested by reviewers) make sure you squash your commits afterwards so that you don't have an absurd number of commits for a very small fix. (Learn how to squash at [here](https://davidwalsh.name/squash-commits-git))
94 |
95 |
96 | ## Contributors
97 |
98 | ### Credits goes to these people: ✨
99 |
100 |
109 |
110 | ## Visitor's Count 📍
111 |
112 |
113 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "oc-frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "^11.7.1",
7 | "@emotion/styled": "^11.6.0",
8 | "@headlessui/react": "^1.4.3",
9 | "@heroicons/react": "^1.0.5",
10 | "@mui/material": "^5.4.1",
11 | "@tailwindcss/line-clamp": "^0.3.1",
12 | "@testing-library/jest-dom": "^5.16.2",
13 | "@testing-library/react": "^12.1.2",
14 | "@testing-library/user-event": "^13.5.0",
15 | "@types/jest": "^27.4.0",
16 | "@types/node": "^16.11.24",
17 | "@types/react": "^17.0.39",
18 | "@types/react-dom": "^17.0.11",
19 | "react": "^17.0.2",
20 | "react-dom": "^17.0.2",
21 | "react-icons": "^4.3.1",
22 | "react-lottie": "^1.2.3",
23 | "react-router-dom": "^6.2.1",
24 | "react-scripts": "^5.0.0",
25 | "react-toastify": "^8.2.0",
26 | "sitemap": "^7.1.1",
27 | "sweetalert2": "^11.4.0",
28 | "typescript": "^4.5.5",
29 | "universal-cookie": "^4.0.4",
30 | "web-vitals": "^2.1.4"
31 | },
32 | "scripts": {
33 | "start": "react-scripts start",
34 | "build": "react-scripts build",
35 | "test": "react-scripts test",
36 | "eject": "react-scripts eject",
37 | "prepare": "husky install",
38 | "format": "prettier --write ."
39 | },
40 | "eslintConfig": {
41 | "extends": [
42 | "react-app",
43 | "react-app/jest"
44 | ]
45 | },
46 | "browserslist": {
47 | "production": [
48 | ">0.2%",
49 | "not dead",
50 | "not op_mini all"
51 | ],
52 | "development": [
53 | "last 1 chrome version",
54 | "last 1 firefox version",
55 | "last 1 safari version"
56 | ]
57 | },
58 | "devDependencies": {
59 | "@babel/core": "^7.17.5",
60 | "@babel/node": "^7.16.8",
61 | "@babel/polyfill": "^7.12.1",
62 | "@babel/preset-env": "^7.16.11",
63 | "@babel/preset-typescript": "^7.16.7",
64 | "@babel/register": "^7.17.0",
65 | "@types/react-lottie": "^1.2.6",
66 | "husky": "^7.0.0",
67 | "postcss": "^8.4.6",
68 | "postcss-import": "^14.0.2",
69 | "postcss-loader": "^6.2.1",
70 | "prettier": "^2.5.1",
71 | "tailwind-scrollbar": "^1.3.1",
72 | "tailwindcss": "^3.0.21",
73 | "ts-node": "^10.5.0"
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-certs/oc-frontend/316d859a92f9489eeb8257f3ae5d642a7b5b8f2d/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
15 |
19 |
23 |
24 |
33 | Open-Certs
34 |
35 |
36 |
37 | You need to enable JavaScript to run this app.
38 |
39 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-certs/oc-frontend/316d859a92f9489eeb8257f3ae5d642a7b5b8f2d/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-certs/oc-frontend/316d859a92f9489eeb8257f3ae5d642a7b5b8f2d/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "https://user-images.githubusercontent.com/41825906/153617213-e956c616-4f4a-4d62-95af-2d4ac4d57942.png",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "https://user-images.githubusercontent.com/41825906/153617213-e956c616-4f4a-4d62-95af-2d4ac4d57942.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "https://user-images.githubusercontent.com/41825906/153617213-e956c616-4f4a-4d62-95af-2d4ac4d57942.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/recievedToken.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Redirecting...
4 |
5 |
6 |
7 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | /* @import url("https://cdnjs.cloudflare.com/ajax/libs/inter-ui/3.18.0/inter.css"); */
2 | @import "tailwindcss/base";
3 | @import "tailwindcss/utilities";
4 |
5 | h1,
6 | h2,
7 | h3,
8 | h4,
9 | p {
10 | line-height: 1.6;
11 | }
12 |
13 | h1,
14 | h2,
15 | h3,
16 | h4,
17 | p.bold {
18 | font-weight: 700;
19 | }
20 |
21 | :root {
22 | font-size: 14px;
23 |
24 | --color-button-text: #fff;
25 | --color-primary-100: #dee3ea;
26 | --color-primary-200: #b2bdcd;
27 | --color-primary-300: #5d7290;
28 | --color-primary-600: #323d4d;
29 | --color-primary-700: #242c37;
30 | --color-primary-800: #151a21;
31 | --color-primary-900: #0b0e11;
32 | --color-secondary-washed-out: #879eed;
33 | --color-secondary: #4a6ffd;
34 | --color-secondary-dark: #3158e6;
35 | --color-accent-glow: rgba(253, 77, 77, 0.3);
36 | --color-accent-gradient: linear-gradient(
37 | 90deg,
38 | rgba(255, 114, 110, 1) 0%,
39 | rgba(224, 68, 44, 1) 40%,
40 | rgba(74, 111, 253, 1) 69%,
41 | rgba(74, 111, 253, 1) 79%
42 | );
43 | --color-accent-gradient-light: linear-gradient(
44 | 90deg,
45 | rgb(255, 255, 255) 0%,
46 | rgb(255, 255, 255) 40%,
47 | rgb(255, 255, 255) 69%,
48 | rgba(255, 255, 255, 0.959) 79%
49 | );
50 | --color-accent: #e0442c;
51 | --color-accent-hover: #fd6868;
52 | --color-accent-disabled: #f5bfbf;
53 | --screen-height-reduction: 0px;
54 | --color-primary-100-translucent: rgba(222, 227, 234, 0.15);
55 | --color-dark-background: #0a0f14;
56 | --color-darkwave1-background: #273036;
57 | --color-white: #ffffff;
58 | }
59 |
60 | input:focus {
61 | text-decoration: none;
62 | border: none;
63 | outline: var(--color-secondary-washed-out) solid 2px !important;
64 | }
65 | h1 {
66 | font-size: 4rem;
67 | }
68 |
69 | h2 {
70 | font-size: 2.8rem;
71 | }
72 |
73 | h3 {
74 | font-size: 2rem;
75 | }
76 |
77 | h4 {
78 | font-size: 1.4rem;
79 | }
80 |
81 | p {
82 | font-size: 1rem;
83 | font-weight: 500;
84 | }
85 |
86 | p.small {
87 | font-size: 0.85rem;
88 | }
89 |
90 | /* for firefox */
91 | * {
92 | scrollbar-width: thin;
93 | scrollbar-color: #888;
94 | }
95 |
96 | /* for non-firefox */
97 | ::-webkit-scrollbar {
98 | overflow: overlay;
99 | width: 5px;
100 | /* shouldnt hardcode width, pls find way to make it dynamic, thin wasn't working for me...*/
101 | }
102 |
103 | ::-webkit-scrollbar-track {
104 | width: 5px;
105 | display: initial;
106 | }
107 |
108 | ::-webkit-scrollbar-thumb {
109 | /* background-color: var(--color-primary-700); */
110 | background-color: #888;
111 | border-radius: 5px;
112 | }
113 |
114 | html,
115 | body,
116 | .__next {
117 | height: 100%;
118 | width: 100%;
119 | display: flex;
120 | font-family: "Roboto Mono", monospace;
121 | /* color: white; */
122 | }
123 |
124 | audio {
125 | width: 0;
126 | display: none !important;
127 | }
128 |
129 | html {
130 | position: fixed;
131 | }
132 |
133 | .__next {
134 | overflow-y: auto;
135 | }
136 |
137 | ::-webkit-resizer {
138 | background: var(--color-primary-700);
139 | }
140 |
141 | /* #nprogress {
142 | position: relative;
143 | z-index: 9999999;
144 | }
145 |
146 | #nprogress .bar {
147 | background: var(--color-accent-hover) !important;
148 | }
149 |
150 | #nprogress .peg {
151 | box-shadow: 0 0 10px var(--color-accent-hover),
152 | 0 0 5px var(--color-accent-hover) !important;
153 | }
154 |
155 | #nprogress .spinner-icon {
156 | border-top-color: var(--color-accent-hover) !important;
157 | border-left-color: var(--color-accent-hover) !important;
158 | } */
159 |
160 | img.emoji {
161 | height: 1.2rem;
162 | width: 1.2rem;
163 | margin: 0 0.05em 0 0.1em;
164 | top: -0.1em;
165 | vertical-align: middle;
166 | display: inline-block;
167 | position: relative;
168 | -webkit-user-drag: none;
169 | -moz-user-drag: none;
170 | -o-user-drag: none;
171 | user-drag: none;
172 | cursor: text;
173 | }
174 |
175 | .h-screen {
176 | height: calc(100vh - var(--screen-height-reduction));
177 | }
178 |
179 | button:focus {
180 | outline: none;
181 | }
182 |
183 | .text-bg {
184 | background: var(--color-accent-gradient);
185 | -webkit-text-fill-color: transparent;
186 | -webkit-background-clip: text;
187 | }
188 | .text-bg-light {
189 | background: var(--color-accent-gradient-light);
190 | -webkit-text-fill-color: transparent;
191 | -webkit-background-clip: text;
192 | }
193 |
194 | .background-oregon-grapes-light {
195 | background-image: url("./assets/lightwave1.svg"),
196 | url("./assets/lightwave2.svg");
197 | width: 100% !important;
198 | min-height: 90vh;
199 | background-repeat: no-repeat;
200 | background-position: center;
201 | background-size: cover;
202 | /* background-attachment: fixed; */
203 | /* background-blend-mode: screen; */
204 | }
205 | .background-oregon-grapes {
206 | background-image: url("./assets/darkwave1.svg"), url("./assets/darkwave2.svg");
207 | width: 100% !important;
208 | min-height: 90vh;
209 | background-repeat: no-repeat;
210 | background-position: center;
211 | background-size: cover;
212 | /* background-attachment: fixed; */
213 | /* background-blend-mode: screen; */
214 | }
215 | .background-oregon-grapes-login {
216 | background-image: url("./assets/darkwave2.svg"), url("./assets/darkwave3.svg");
217 | width: 100% !important;
218 | background-repeat: no-repeat;
219 | background-position: center;
220 | background-size: cover;
221 | background-attachment: fixed;
222 | background-blend-mode: screen;
223 | }
224 | .background-oregon-grapes-light-login {
225 | background-image: url("./assets/lightwave2.svg"),
226 | url("./assets/loginlightwave1.svg");
227 | width: 100% !important;
228 | background-repeat: no-repeat;
229 | background-position: center;
230 | background-size: cover;
231 | background-attachment: fixed;
232 | background-blend-mode: screen;
233 | }
234 | .ribbon {
235 | background: var(--color-accent-gradient);
236 | overflow: hidden;
237 | white-space: nowrap;
238 | z-index: 1;
239 | position: absolute;
240 | right: -55px;
241 | bottom: 30px;
242 | transform: rotate(-45deg);
243 | box-shadow: 0 0 10px rgb(49, 49, 49);
244 | }
245 |
246 | .ribbon a {
247 | border: 1px solid rgb(88, 88, 88);
248 | color: #fff;
249 | display: block;
250 | margin: 1px 0;
251 | text-align: center;
252 | padding: 10px 50px;
253 | text-decoration: none;
254 | text-shadow: 0 0 5px #444;
255 | font-size: 1.2em;
256 | display: flex;
257 | align-items: center;
258 | justify-content: center;
259 | }
260 |
261 | #lottie-Landing-animation > * > * {
262 | transform: none !important;
263 | }
264 | .mainTitle {
265 | --padding: 0rem;
266 | position: relative;
267 | width: max-content;
268 | }
269 |
270 | .mainTitle::after {
271 | content: "";
272 | position: absolute;
273 | height: 2px;
274 | border-radius: 5px;
275 | left: 0;
276 | top: 20px;
277 | width: 100%;
278 | background: rgb(253, 251, 251);
279 | transform-origin: center;
280 | transform: scaleX(0);
281 | transition: all 400ms ease;
282 | }
283 |
284 | .mainTitle.active::after {
285 | transform: scaleX(1);
286 | font-weight: bolder;
287 | }
288 | .navBtn:hover.mainTitle::after {
289 | transform: scaleX(1);
290 | }
291 |
--------------------------------------------------------------------------------
/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render, screen } from "@testing-library/react";
3 | import App from "./App";
4 |
5 | test("renders learn react link", () => {
6 | render( );
7 | const linkElement = screen.getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./App.css";
3 | import { BrowserRouter } from "react-router-dom";
4 | import { Routing } from "./router/route";
5 | import { AuthProvider } from "./context/AuthContext/AuthProvider";
6 | import { Fork } from "./components/Fork";
7 | import { ThemeProvider } from "./context/ThemeContext/ThemeProvider";
8 | import { ToastContainer } from "react-toastify";
9 | import "react-toastify/dist/ReactToastify.css";
10 | import { Header } from "./components/Header";
11 | import Footer from "./components/Footer";
12 | import CertificateRouter from "./context/CertificateContext/CertificateRouter";
13 |
14 | function App() {
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | export default App;
33 |
--------------------------------------------------------------------------------
/src/assets/certificate_assets/cert-body.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-certs/oc-frontend/316d859a92f9489eeb8257f3ae5d642a7b5b8f2d/src/assets/certificate_assets/cert-body.png
--------------------------------------------------------------------------------
/src/assets/certificate_assets/certificate.css:
--------------------------------------------------------------------------------
1 | .text-bg {
2 | background: linear-gradient(
3 | 90deg,
4 | rgba(255, 114, 110, 1) 0%,
5 | rgba(224, 68, 44, 1) 40%,
6 | rgba(74, 111, 253, 1) 69%,
7 | rgba(74, 111, 253, 1) 79%
8 | );
9 | -webkit-text-fill-color: transparent !important;
10 | background-clip: text;
11 | -webkit-background-clip: text !important;
12 | }
13 | .body {
14 | width: 1053px !important;
15 | height: 813.5px !important;
16 | }
17 | #cert-container {
18 | position: relative;
19 | }
20 |
21 | .cert-body {
22 | position: absolute;
23 | max-width: none !;
24 | width: 1053px !important;
25 | height: 813.5px !important;
26 | }
27 | .cert-text {
28 | position: absolute;
29 | top: 0;
30 | width: 1053px !important;
31 | height: 813.5px !important;
32 | }
33 | .cert-on {
34 | position: absolute;
35 | top: 0;
36 | font-size: 11.2px;
37 | right: 40px;
38 | margin: 10px;
39 | color: rgb(224 68 44);
40 | font-family: monospace;
41 | }
42 | .center {
43 | position: absolute;
44 | }
45 | .cert-head {
46 | position: absolute;
47 | top: 40px;
48 | text-align: center;
49 | width: 100%;
50 | }
51 | .cert-head-text {
52 | margin: 0px;
53 | font-size: 4.2rem;
54 | }
55 | .cert-name {
56 | font-size: 1.68rem;
57 | position: absolute;
58 | top: 300px;
59 | width: 100%;
60 | text-align: center;
61 | }
62 | .last-contrib {
63 | font-size: 1.05rem;
64 | width: 100%;
65 | text-align: center;
66 | position: absolute;
67 | top: 460px;
68 | }
69 |
70 | .cert-flex-container {
71 | justify-content: space-between;
72 | align-items: center;
73 | position: relative;
74 | top: 460px;
75 | display: flex;
76 | flex-direction: row;
77 | justify-content: space-between;
78 | align-items: center;
79 | margin: 140px 70px 70px 70px;
80 | }
81 |
82 | .cert-id {
83 | position: absolute;
84 | bottom: 0;
85 | padding: 2px;
86 | margin-left: 40px;
87 | color: rgb(224 68 44);
88 | font-family: monospace;
89 | font-weight: 600;
90 | font-size: 1rem;
91 | padding: 10px;
92 | }
93 |
94 | .icon {
95 | height: 70px;
96 | width: auto;
97 | }
98 | .icon-qr {
99 | height: 124px;
100 | width: auto;
101 | }
102 | .flex-container {
103 | display: flex;
104 | flex-direction: row;
105 | justify-content: space-between;
106 | align-items: center;
107 | margin: 5px 100px 100px 100px;
108 | }
109 |
110 | .veryfy {
111 | color: #4a6ffd;
112 | text-align: center;
113 | font-size: 1rem;
114 | }
115 |
116 | .print {
117 | border: solid;
118 | border-width: 1px;
119 | border-color: black;
120 | color: blanchedalmond;
121 | background-color: brown;
122 | padding: 10px;
123 | cursor: pointer;
124 | }
125 |
126 | #print-btn {
127 | position: relative;
128 | top: -400px;
129 | left: 1100px;
130 | width: 50px;
131 | }
132 |
133 | @page {
134 | size: letter landscape;
135 | margin: 0px !important;
136 | }
137 |
138 | @media print {
139 | #print-btn {
140 | display: none !important;
141 | }
142 | #cert-container {
143 | width: 1053px !important;
144 | height: 813.5px !important;
145 | }
146 | .body {
147 | width: 1053px !important;
148 | height: 813.5px !important;
149 | margin: 1px;
150 | padding: 1px;
151 | }
152 | .text-bg {
153 | -webkit-print-color-adjust: exact !important;
154 | /* color-adjust: exact !important; */
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/assets/certificate_assets/projectpiker.css:
--------------------------------------------------------------------------------
1 | .svg,
2 | .provider-logo {
3 | width: 114px;
4 | height: 114px;
5 | margin: 1em;
6 | }
7 |
8 | .circle-bg {
9 | fill: none;
10 | stroke-width: 10px;
11 | }
12 |
13 | .progress {
14 | fill: none;
15 | stroke-width: 10px;
16 | stroke-linecap: round;
17 | transform: rotate(-90deg);
18 | transform-origin: 50% 50%;
19 | stroke-dasharray: 360;
20 | stroke-dashoffset: 50;
21 | animation: progress-1 1s ease-out;
22 | }
23 |
--------------------------------------------------------------------------------
/src/assets/darkwave1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/assets/darkwave2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/assets/darkwave3.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/lightwave1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/assets/lightwave2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/assets/lightwave3.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/loginlightwave1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Button.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | ButtonHTMLAttributes,
3 | DetailedHTMLProps,
4 | ReactNode,
5 | } from "react";
6 | import { Spinner } from "./Spinner";
7 |
8 | const sizeClassnames = {
9 | big: "py-2 px-6 text-sm rounded-lg",
10 | small: "px-2 py-1 text-sm rounded-md",
11 | tiny: "px-1 text-sm rounded-5",
12 | };
13 |
14 | const colorClassnames = {
15 | primary:
16 | "text-button bg-accent transition duration-200 ease-in-out hover:bg-accent-hover disabled:text-accent-disabled disabled:bg-accent-hover",
17 | secondary:
18 | "text-button bg-primary-700 hover:bg-primary-600 disabled:text-primary-300",
19 | "secondary-800":
20 | "text-button bg-primary-800 hover:bg-primary-600 disabled:text-primary-300",
21 | "primary-300":
22 | "text-button bg-primary-700 hover:bg-primary-600 disabled:text-primary-300",
23 | transparent: "text-button bg-transparent",
24 | "accent-secondary":
25 | "text-button bg-secondary hover:bg-secondary-washed-out disabled:text-secondary-washed-out",
26 | };
27 |
28 | export type ButtonProps = DetailedHTMLProps<
29 | ButtonHTMLAttributes,
30 | HTMLButtonElement
31 | > & {
32 | size?: keyof typeof sizeClassnames;
33 | color?: keyof typeof colorClassnames;
34 | loading?: boolean;
35 | icon?: ReactNode;
36 | transition?: boolean;
37 | };
38 |
39 | export const Button: React.FC = ({
40 | children,
41 | size = "big",
42 | color = "primary",
43 | disabled,
44 | loading,
45 | icon,
46 | className = "",
47 | transition,
48 | ...props
49 | }) => {
50 | return (
51 |
61 |
62 | {icon ? {icon} : null}
63 | {children}
64 |
65 | {loading ? (
66 |
67 |
68 |
69 | ) : null}
70 |
71 | );
72 | };
73 |
--------------------------------------------------------------------------------
/src/components/CertificatePdf.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from "react";
2 | import "../assets/certificate_assets/certificate.css";
3 | import certbody from "../assets/certificate_assets/cert-body.png";
4 | import CertificateContext from "../context/CertificateContext/CertificateProvider";
5 |
6 | interface CertificateProps {}
7 | export const CertificatePdf: React.FC = () => {
8 | const { certificate, setCertificate } = useContext(CertificateContext);
9 | const [loadedImgs, setLoadedImgs] = useState(0);
10 | useEffect(() => {
11 | if (!certificate) return;
12 | if (loadedImgs >= 2 + certificate.images.length) {
13 | window.print();
14 | setCertificate(null);
15 | }
16 | }, [setCertificate, certificate, loadedImgs]);
17 |
18 | return (
19 |
20 |
21 |
22 |
23 |
setLoadedImgs((prev) => prev + 1)}
28 | />
29 |
30 |
31 | Certified On:{" "}
32 | {certificate.createdAt.split("T").join(" ").slice(0, 19)}
33 |
34 |
35 |
Certificate
36 | of Contribution
37 |
38 |
39 | This is to certify that
40 |
43 | {certificate.userName}
44 |
45 |
46 | has actively contibuted to open source project{" "}
47 | {certificate.projectRepo} of
48 | {certificate.projectOwner}.
49 |
50 |
51 | Last contributed at : {" "}
52 | {certificate.lastContributionDate
53 | .split("T")
54 | .join(" ")
55 | .slice(0, 19)}
56 |
57 |
58 |
70 |
71 | {certificate.images.map((img: any) => (
72 |
83 | ))}
84 |
85 |
86 |
87 | Certificate Id:{" "}
88 | {certificate._id.match(new RegExp(".{1,5}", "g")).join("-")}
89 |
90 |
91 |
92 |
93 |
94 | );
95 | };
96 |
--------------------------------------------------------------------------------
/src/components/DarkModeButton.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import ThemeContext from "../context/ThemeContext/ThemeProvider";
3 | import { BsFillMoonStarsFill } from "react-icons/bs";
4 | import { IoSunny } from "react-icons/io5";
5 |
6 | const DarkModeButton = () => {
7 | const { theme, toggle } = useContext(ThemeContext);
8 | //console.log({ theme });
9 | return (
10 |
15 | {theme !== "dark" ? (
16 |
17 | ) : (
18 |
19 | )}
20 |
21 | );
22 | };
23 |
24 | export default DarkModeButton;
25 |
--------------------------------------------------------------------------------
/src/components/Footer.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useLocation } from "react-router-dom";
3 |
4 | import { FaDiscord, FaGithubAlt } from "react-icons/fa";
5 |
6 | export default function Footer() {
7 | const location = useLocation();
8 | return location.pathname !== "/certificate" ? (
9 |
58 | ) : null;
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/Fork.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { FaGithubAlt } from "react-icons/fa";
3 | import { useLocation } from "react-router-dom";
4 |
5 | interface ForkProps {}
6 |
7 | export const Fork: React.FC = () => {
8 | const location = useLocation();
9 | return location.pathname !== "/certificate" ? (
10 |
20 | ) : null;
21 | };
22 |
--------------------------------------------------------------------------------
/src/components/GenerateCertificate.tsx:
--------------------------------------------------------------------------------
1 | import { FormControlLabel, FormGroup, Switch } from "@mui/material";
2 | import React, { useState } from "react";
3 | import { useNavigate } from "react-router-dom";
4 | import { apiBaseUrl } from "../config";
5 | import { cookies } from "../context/AuthContext/AuthReducer";
6 | import { Button } from "./Button";
7 | import displayToast from "./Toast";
8 |
9 | type Props = {
10 | token: string;
11 | data: any;
12 | value: number;
13 | };
14 |
15 | export default function GenerateCertificate({ token, data, value }: Props) {
16 | const navigate = useNavigate();
17 | const [includeUserImage, setIncludeUserImage] = useState(true);
18 | const [includeRepositoryImage, setIncludeRepositoryImage] =
19 | useState(true);
20 | const generate = async (projectToken: string) => {
21 | const url = `${apiBaseUrl}/certificate`;
22 | await fetch(url, {
23 | method: "POST",
24 | headers: {
25 | "Content-Type": "application/json",
26 | Authorization: `${cookies.get("token")}`,
27 | "project-token": projectToken,
28 | },
29 | body: JSON.stringify({
30 | includeUserImage,
31 | includeRepositoryImage,
32 | }),
33 | })
34 | .then((res) => res.json())
35 | .then((data) => {
36 | if (data.error) {
37 | displayToast("Oops! Something went wrong", "failure");
38 | } else {
39 | displayToast("Certificate generated Successfully", "success"); //github 503 error
40 | navigate("/certificate/" + data.certificate._id);
41 | }
42 | })
43 | .catch((err) => {
44 | displayToast("Oops! Something went wrong", "failure");
45 | });
46 | };
47 | if (Math.min(...Object.values(data)) > value) return null;
48 | console.log({ includeRepositoryImage, includeUserImage });
49 | return (
50 |
51 |
52 | setIncludeUserImage((prev) => !prev)}
58 | />
59 | }
60 | label="Include user Image"
61 | className="dark:text-primary inline-block"
62 | />
63 | setIncludeRepositoryImage((prev) => !prev)}
69 | />
70 | }
71 | label="Include Repo Image"
72 | className="dark:text-primary inline-block"
73 | />
74 |
75 | {/*
76 |
77 |
78 | Default switch checkbox input
79 |
80 |
*/}
81 |
{
83 | generate(token);
84 | }}
85 | >
86 | Generate Certificate
87 |
88 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { FaSignOutAlt } from "react-icons/fa";
3 | import { IoMdPower } from "react-icons/io";
4 | import { useNavigate, NavLink as Link, useLocation } from "react-router-dom";
5 | import { logoDark } from "../config";
6 | import AuthContext from "../context/AuthContext/AuthProvider";
7 | import ThemeContext from "../context/ThemeContext/ThemeProvider";
8 | import { Button } from "./Button";
9 | import DarkModeButton from "./DarkModeButton";
10 |
11 | interface HeaderProps {}
12 |
13 | export const Header: React.FC = () => {
14 | const navigate = useNavigate();
15 | const { isAuthenticated, logout, user } = useContext(AuthContext);
16 | const { theme } = useContext(ThemeContext);
17 | const location = useLocation();
18 | return location.pathname !== "/certificate" ? (
19 |
20 |
21 |
22 |
23 | {/* open-certs logo */}
24 |
navigate("/")}
27 | src={logoDark}
28 | alt="logo"
29 | className="h-8 w-8 cursor-pointer"
30 | />
31 |
32 | {/* open-certs app name */}
33 |
38 | Open-Certs
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | Verify
47 |
48 |
49 | {isAuthenticated === 1 && (
50 | <>
51 | {/* user-name */}
52 |
{user.username}
53 | {/* user profile picture */}
54 |
59 | >
60 | )}
61 | {isAuthenticated ? (
62 | // logout button
63 |
logout()}
66 | className="dark:bg-primary-800 dark:hover:bg-primary-900 bg-transparent hover:bg-secondary-washed-out flex items-center justify-center text-primary-100 text-base p-2 rounded-8 cursor-pointer"
67 | >
68 |
69 |
70 | ) : (
71 | // login button
72 |
73 |
77 |
78 |
79 |
80 | Login
81 |
82 |
83 | )}
84 |
85 |
86 | {/* darkmode toggle button */}
87 |
88 |
89 | ) : null;
90 | };
91 |
--------------------------------------------------------------------------------
/src/components/MicroLogin.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect } from "react";
2 | import { FaBitbucket, FaGithubAlt, FaGitlab } from "react-icons/fa";
3 | import { apiBaseUrl } from "../config";
4 | import AuthContext from "../context/AuthContext/AuthProvider";
5 | import { OpenPopUp } from "./OpenPopup";
6 | import { cookies } from "../context/AuthContext/AuthReducer";
7 |
8 | let browser: any = null;
9 | let popup: any = null;
10 |
11 | export default function MicroLogin() {
12 | const { loginConfirm } = useContext(AuthContext);
13 | useEffect(() => {
14 | browser = window.self;
15 | browser.loggedIn = (token: any) => {
16 | if (loginConfirm) {
17 | loginConfirm(token);
18 | cookies.set("token", token);
19 | }
20 | if (popup && !popup.closed) {
21 | popup.close();
22 | }
23 | };
24 | }, [loginConfirm]);
25 | const onClick = (url: string) => {
26 | popup = OpenPopUp(url, browser, popup);
27 | };
28 | return (
29 |
30 |
31 | Get started with
32 |
33 |
34 |
onClick(`${apiBaseUrl}/auth/github`)}
37 | className="flex flex-1 items-center dark:bg-primary-800 justify-center text-base p-2 w-1/6 rounded-8 dark:hover:bg-primary-900 cursor-pointer bg-primary-200 hover:bg-primary-100 "
38 | >
39 |
40 |
41 |
onClick(`${apiBaseUrl}/auth/bitbucket`)}
44 | className="flex flex-1 items-center dark:bg-primary-800 justify-center text-base p-2 w-1/6 mx-3 rounded-8 dark:hover:bg-primary-900 cursor-pointer bg-primary-200 hover:bg-primary-100"
45 | >
46 |
47 |
48 |
onClick(`${apiBaseUrl}/auth/gitlab`)}
51 | className="flex flex-1 items-center dark:bg-primary-800 justify-center text-base p-2 w-1/6 rounded-8 dark:hover:bg-primary-900 cursor-pointer bg-primary-200 hover:bg-primary-100"
52 | >
53 |
54 |
55 |
56 |
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/src/components/OpenPopup.ts:
--------------------------------------------------------------------------------
1 | export const OpenPopUp = (url: string, browser: any, popup: any): any => {
2 | if (!browser) return;
3 | if (popup && !popup.closed) {
4 | popup.focus();
5 |
6 | return;
7 | }
8 | const w = 700,
9 | h = 700;
10 | const dualScreenLeft =
11 | window.screenLeft !== undefined ? window.screenLeft : window.screenX;
12 | const dualScreenTop =
13 | window.screenTop !== undefined ? window.screenTop : window.screenY;
14 |
15 | const width = window.innerWidth
16 | ? window.innerWidth
17 | : document.documentElement.clientWidth
18 | ? document.documentElement.clientWidth
19 | : window.screen.width;
20 | const height = window.innerHeight
21 | ? window.innerHeight
22 | : document.documentElement.clientHeight
23 | ? document.documentElement.clientHeight
24 | : window.screen.height;
25 |
26 | const systemZoom = width / window.screen.availWidth;
27 | const left = (width - w) / 2 / systemZoom + dualScreenLeft;
28 | const top = (height - h) / 2 / systemZoom + dualScreenTop;
29 | popup = browser.open(
30 | url,
31 | "Login",
32 | `dependent=${1},
33 | alwaysRaised=${1},
34 | width=${w / systemZoom},
35 | height=${h / systemZoom},
36 | top=${top},
37 | left=${left}
38 | `
39 | );
40 |
41 | return popup;
42 | };
43 |
--------------------------------------------------------------------------------
/src/components/ProjectDataTab.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | type Props = {
4 | children?: React.ReactNode;
5 | title: string;
6 | };
7 |
8 | export default function ProjectDataTab({ children, title }: Props) {
9 | return (
10 |
11 |
12 | {title}
13 |
14 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/ProjectInput/BitbucketProjectInput.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from "react";
2 | import { ProjectContext } from "../../context/ProjectContext";
3 | import ThemeContext from "../../context/ThemeContext/ThemeProvider";
4 | import { Button } from "../Button";
5 |
6 | export default function BitbucketProjectInput() {
7 | const { theme } = useContext(ThemeContext);
8 | const { setProject } = useContext(ProjectContext);
9 | const [repoData, setRepoData] = useState<{
10 | workspace: string;
11 | repoName: string;
12 | }>({
13 | workspace: "",
14 | repoName: "",
15 | });
16 |
17 | const { workspace, repoName } = repoData;
18 |
19 | const onChange = (e: any) => {
20 | const { name, value } = e.target;
21 | setRepoData({ ...repoData, [name]: value });
22 | };
23 |
24 | const fetchproject = async () => {
25 | const { workspace, repoName } = repoData;
26 | setProject("Bitbucket", { workspace, repoName });
27 | };
28 | return (
29 |
30 |
31 |
32 |
33 | Workspace
34 |
35 |
44 |
45 |
46 | /
47 |
48 |
49 |
50 | Repository Name
51 |
52 |
61 |
62 |
63 |
64 | fetchproject()}
66 | className="justify-center text-base px-3 sm:px-6"
67 | color={theme === "dark" ? "secondary" : "accent-secondary"}
68 | >
69 | Fetch Project
70 |
71 |
72 |
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/src/components/ProjectInput/GithubProjectInput.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from "react";
2 | import { ProjectContext } from "../../context/ProjectContext";
3 | import ThemeContext from "../../context/ThemeContext/ThemeProvider";
4 | import { Button } from "../Button";
5 |
6 | export default function GithubProjectInput() {
7 | const { theme } = useContext(ThemeContext);
8 | const { setProject } = useContext(ProjectContext);
9 | const [repoData, setRepoData] = useState<{
10 | ownerName: string;
11 | repoName: string;
12 | }>({
13 | ownerName: "",
14 | repoName: "",
15 | });
16 |
17 | const { ownerName, repoName } = repoData;
18 |
19 | const onChange = (e: any) => {
20 | const { name, value } = e.target;
21 | setRepoData({ ...repoData, [name]: value });
22 | };
23 |
24 | const fetchproject = async () => {
25 | const { ownerName, repoName } = repoData;
26 | setProject("Github", { ownerName, repoName });
27 | };
28 | return (
29 |
30 |
31 |
32 |
33 | Owner Name
34 |
35 |
44 |
45 |
46 | /
47 |
48 |
49 |
50 | Repository Name
51 |
52 |
61 |
62 |
63 |
64 | fetchproject()}
66 | className="justify-center text-base px-3 sm:px-6"
67 | color={theme === "dark" ? "secondary" : "accent-secondary"}
68 | >
69 | Fetch Project
70 |
71 |
72 |
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/src/components/ProjectInput/GitlabProjectInput.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from "react";
2 | import { ProjectContext } from "../../context/ProjectContext";
3 | import ThemeContext from "../../context/ThemeContext/ThemeProvider";
4 | import { Button } from "../Button";
5 |
6 | export default function GitlabProjectInput() {
7 | const { theme } = useContext(ThemeContext);
8 | const { setProject } = useContext(ProjectContext);
9 | const [repoData, setRepoData] = useState<{
10 | projectId: string;
11 | }>({
12 | projectId: "",
13 | });
14 |
15 | const { projectId } = repoData;
16 |
17 | const onChange = (e: any) => {
18 | const { name, value } = e.target;
19 | setRepoData({ ...repoData, [name]: value });
20 | };
21 |
22 | const fetchproject = async () => {
23 | const { projectId } = repoData;
24 | setProject("Gitlab", { projectId });
25 | };
26 | return (
27 |
28 |
29 |
30 |
31 | Project Id
32 |
33 |
42 |
43 |
44 |
45 | fetchproject()}
47 | className="justify-center text-base px-3 sm:px-6"
48 | color={theme === "dark" ? "secondary" : "accent-secondary"}
49 | >
50 | Fetch Project
51 |
52 |
53 |
54 | );
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/ProjectInput/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import AuthContext from "../../context/AuthContext/AuthProvider";
3 | import BitbucketProjectInput from "./BitbucketProjectInput";
4 | import GithubProjectInput from "./GithubProjectInput";
5 | import GitlabProjectInput from "./GitlabProjectInput";
6 |
7 | export default function ProjectInput() {
8 | const { isAuthenticated, user } = useContext(AuthContext);
9 | if (!isAuthenticated) {
10 | return null;
11 | }
12 | if (user.kind === "github") {
13 | return ;
14 | }
15 | if (user.kind === "gitlab") {
16 | return ;
17 | }
18 | if (user.kind === "bitbucket") {
19 | return ;
20 | }
21 | return null;
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/ProjectPicker.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import "../../src/assets/certificate_assets/projectpiker.css";
3 | import { ProjectContext } from "../context/ProjectContext";
4 | import ProjectStat from "./ProjectStat";
5 | import ProjectDataTab from "./ProjectDataTab";
6 | import { FaGitAlt } from "react-icons/fa";
7 | import ProjectProgressBar from "./ProjectProgressBar/ProjectProgressBar";
8 | import GenerateCertificate from "./GenerateCertificate";
9 |
10 | interface ProjectPickerProps {}
11 | export const ProjectPicker: React.FC = () => {
12 | const { projectData } = useContext(ProjectContext);
13 |
14 | return (
15 | <>
16 |
17 |
18 |
22 |
23 | {projectData.accumulatedData.name}
24 | {"\u00A0"}
25 |
26 |
by,{"\u00A0"}
27 |
28 |
34 |
35 |
36 | {"\u00A0"}@{projectData.accumulatedData.owner}
37 |
38 |
39 |
43 |
44 |
49 |
54 |
59 |
64 |
69 |
74 |
75 |
76 |
77 |
78 |
83 |
88 |
89 |
93 |
94 |
95 |
107 |
120 |
133 |
134 |
135 |
136 |
137 |
142 |
143 |
144 |
145 | >
146 | );
147 | };
148 |
149 | export default ProjectPicker;
150 |
--------------------------------------------------------------------------------
/src/components/ProjectProgressBar/ProjectProgressBar.module.css:
--------------------------------------------------------------------------------
1 | .progress {
2 | background-color: #f5f5f5;
3 | border-radius: 3px;
4 | box-shadow: none;
5 | text-align: left;
6 | height: 25px;
7 | }
8 | .progress.progress-xs {
9 | height: 5px;
10 | margin-top: 5px;
11 | }
12 | .progress.progress-sm {
13 | height: 10px;
14 | margin-top: 5px;
15 | }
16 | .progress.progress-lg {
17 | height: 25px;
18 | }
19 | .progress.vertical {
20 | position: relative;
21 | width: 20px;
22 | height: 200px;
23 | display: inline-block;
24 | margin-right: 10px;
25 | }
26 | .progress.vertical > .progress-bar {
27 | width: 100% !important;
28 | position: absolute;
29 | bottom: 0;
30 | }
31 | .progress.vertical.progress-xs {
32 | width: 5px;
33 | margin-top: 5px;
34 | }
35 | .progress.vertical.progress-sm {
36 | width: 10px;
37 | margin-top: 5px;
38 | }
39 | .progress.vertical.progress-lg {
40 | width: 30px;
41 | }
42 | .progress-bar {
43 | background-color: #2196f3;
44 | box-shadow: none;
45 | }
46 | .progress-bar.text-left {
47 | text-align: left;
48 | }
49 | .progress-bar.text-left span {
50 | margin-left: 10px;
51 | }
52 | .progress-bar.text-right {
53 | text-align: right;
54 | }
55 | .progress-bar.text-right span {
56 | margin-right: 10px;
57 | }
58 | @-webkit-keyframes progress-bar-stripes {
59 | from {
60 | background-position: 40px 0;
61 | }
62 | to {
63 | background-position: 0 0;
64 | }
65 | }
66 | @keyframes progress-bar-stripes {
67 | from {
68 | background-position: 40px 0;
69 | }
70 | to {
71 | background-position: 0 0;
72 | }
73 | }
74 | .progress.active .progress-bar,
75 | .progress-bar.active {
76 | -webkit-animation: progress-bar-stripes 2s linear infinite;
77 | -o-animation: progress-bar-stripes 2s linear infinite;
78 | animation: progress-bar-stripes 2s linear infinite;
79 | }
80 | .progress-striped .progress-bar,
81 | .progress-bar-striped {
82 | background-image: -webkit-linear-gradient(
83 | 45deg,
84 | rgba(255, 255, 255, 0.15) 25%,
85 | transparent 25%,
86 | transparent 50%,
87 | rgba(255, 255, 255, 0.15) 50%,
88 | rgba(255, 255, 255, 0.15) 75%,
89 | transparent 75%,
90 | transparent
91 | );
92 | background-image: -o-linear-gradient(
93 | 45deg,
94 | rgba(255, 255, 255, 0.15) 25%,
95 | transparent 25%,
96 | transparent 50%,
97 | rgba(255, 255, 255, 0.15) 50%,
98 | rgba(255, 255, 255, 0.15) 75%,
99 | transparent 75%,
100 | transparent
101 | );
102 | background-image: linear-gradient(
103 | 45deg,
104 | rgba(255, 255, 255, 0.15) 25%,
105 | transparent 25%,
106 | transparent 50%,
107 | rgba(255, 255, 255, 0.15) 50%,
108 | rgba(255, 255, 255, 0.15) 75%,
109 | transparent 75%,
110 | transparent
111 | );
112 | background-size: 40px 40px;
113 | }
114 | .progress-bar-secondary {
115 | background-color: #323a45;
116 | }
117 | .progress-bar-default {
118 | background-color: #b0bec5;
119 | }
120 | .progress-bar-success {
121 | background-color: #64dd17;
122 | }
123 | .progress-bar-info {
124 | background-color: #29b6f6;
125 | }
126 | .progress-bar-warning {
127 | background-color: #ffd600;
128 | }
129 | .progress-bar-danger {
130 | background-color: #ef1c1c;
131 | }
132 | .progress-bar {
133 | display: inline-block !important;
134 | height: 25px;
135 | overflow-x: visible;
136 | }
137 | .progress-bar span {
138 | padding: 5%;
139 | height: 100%;
140 | overflow-y: hidden;
141 | }
142 |
--------------------------------------------------------------------------------
/src/components/ProjectProgressBar/ProjectProgressBar.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import styles from "./ProjectProgressBar.module.css";
3 |
4 | type Props = {
5 | value: number;
6 | dataPoints: any;
7 | };
8 |
9 | export default function ProjectProgressBar({ value, dataPoints }: Props) {
10 | const [graph, setGraph] = useState([]);
11 | const graphColours = ["default", "primary", "warning", "danger"];
12 | const graphLabels = [
13 | "Not yet qualified",
14 | "Usefull",
15 | "Famous",
16 | "Unparalleled",
17 | ];
18 | useEffect(() => {
19 | const levels = Object.values(dataPoints).sort((a: any, b: any) => a - b);
20 | let level: any = levels[0];
21 | levels.forEach((l: any) => {
22 | if (value > l || value > level) {
23 | level = l;
24 | }
25 | });
26 | const graph: any = [];
27 | let currValue: number = value;
28 | levels.forEach((l: any) => {
29 | if (l > level) return;
30 | if (value > l) {
31 | graph.push((l / level) * 100);
32 | currValue -= l;
33 | } else {
34 | graph.push((currValue / level) * 100);
35 | }
36 | });
37 | setGraph(graph);
38 | }, [dataPoints, value]);
39 | return (
40 |
41 |
44 | {graph.map((g: any, i: any) => (
45 |
52 | {i === graph.length - 1 ? graphLabels[i % 4] : ""}
53 |
54 | ))}
55 |
56 |
57 | );
58 |
59 | // return (
60 | //
61 | //
68 | // {/*
32% */}
69 | //
)
70 | }
71 |
--------------------------------------------------------------------------------
/src/components/ProjectStat.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | type Props = {
4 | stat: any;
5 | title: string;
6 | className?: string;
7 | };
8 |
9 | export default function ProjectStat({ stat, title, className }: Props) {
10 | return (
11 |
14 |
15 |
{stat}
16 |
{title}
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Spinner.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const sizes = {
4 | "2": "h-2 w-2",
5 | "4": "h-4 w-4",
6 | };
7 |
8 | export const Spinner: React.FC<{ size?: keyof typeof sizes }> = ({
9 | size = "4",
10 | }) => {
11 | return (
12 |
18 |
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/components/Toast.ts:
--------------------------------------------------------------------------------
1 | import { toast } from "react-toastify";
2 |
3 | export default function displayToast(
4 | message: string,
5 | type: "success" | "failure" | "info"
6 | ) {
7 | switch (type) {
8 | case "success":
9 | toast.success(message, {
10 | position: "bottom-left",
11 | autoClose: 5000,
12 | hideProgressBar: false,
13 | closeOnClick: true,
14 | pauseOnHover: true,
15 | draggable: true,
16 | progress: undefined,
17 | });
18 | break;
19 | case "failure":
20 | toast.error(message, {
21 | position: "bottom-left",
22 | autoClose: 5000,
23 | hideProgressBar: false,
24 | closeOnClick: true,
25 | pauseOnHover: true,
26 | draggable: true,
27 | progress: undefined,
28 | });
29 | break;
30 | default:
31 | toast(message, {
32 | position: "bottom-left",
33 | autoClose: 5000,
34 | hideProgressBar: false,
35 | closeOnClick: true,
36 | pauseOnHover: true,
37 | draggable: true,
38 | progress: undefined,
39 | });
40 | break;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | export const __prod__ = process.env.NODE_ENV === "production";
2 | export const logoDark =
3 | "https://user-images.githubusercontent.com/41825906/153617213-e956c616-4f4a-4d62-95af-2d4ac4d57942.png";
4 | export const logoLight =
5 | "https://user-images.githubusercontent.com/55032900/156938066-090de099-4ec7-47eb-91fd-458b30586651.png";
6 | export const clientId = process.env.REACT_APP_CLIENT_ID;
7 | export const clientSecret = process.env.REACT_APP_CLIENT_SECRET;
8 | export const redirectUri = process.env.REACT_APP_REDIRECT_URI;
9 | export const apiBaseUrl = __prod__
10 | ? process.env.REACT_APP_API_BASE_URL
11 | : process.env.REACT_APP_API_BASE_URL_LOCAL;
12 |
--------------------------------------------------------------------------------
/src/context/AuthContext/AuthProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useEffect, useReducer } from "react";
2 | import { useNavigate } from "react-router-dom";
3 | import displayToast from "../../components/Toast";
4 | import { apiBaseUrl } from "../../config";
5 | import { AuthReducer, cookies } from "./AuthReducer";
6 |
7 | export interface defaultState {
8 | isAuthenticated: number;
9 | user: any;
10 | loading: boolean;
11 | }
12 |
13 | const initialState: defaultState = {
14 | isAuthenticated: 0,
15 | user: {},
16 | loading: false,
17 | };
18 |
19 | const AuthContext = createContext(initialState);
20 | export default AuthContext;
21 |
22 | interface AuthProviderProps {
23 | children?: React.ReactNode;
24 | }
25 |
26 | export const AuthProvider: React.FC = ({ children }) => {
27 | const [state, dispatch] = useReducer(AuthReducer, initialState);
28 | const navigate = useNavigate();
29 |
30 | const loginConfirm = (token: string): any => {
31 | // console.log("loginConfirm");
32 | const url: string = `${apiBaseUrl}/users/me`;
33 | fetch(url, {
34 | method: "GET",
35 | headers: new Headers({
36 | Authorization: token,
37 | }),
38 | })
39 | .then((res) => {
40 | return res.json();
41 | })
42 | .then((res) => {
43 | // console.log({ res });
44 | if (res.error) {
45 | displayToast("Invalid Token", "failure");
46 | throw new Error("Invalid token");
47 | } else {
48 | dispatch({
49 | type: "LOGIN",
50 | payload: {
51 | user: res.user,
52 | token,
53 | },
54 | });
55 | navigate("/");
56 | displayToast("Login Successful!", "success");
57 | }
58 | })
59 | .catch((err) => {
60 | // console.log({ err });
61 | displayToast("Something went wrong!", "failure");
62 | });
63 | };
64 |
65 | const logout = (): any => {
66 | dispatch({
67 | type: "LOGOUT",
68 | });
69 | // navigate("/login");
70 | };
71 |
72 | useEffect(() => {
73 | const token = cookies.get("token");
74 | const user = cookies.get("user");
75 | if (token && user) {
76 | // console.log({ token, user });
77 | dispatch({
78 | type: "LOGIN",
79 | payload: {
80 | user,
81 | token,
82 | },
83 | });
84 | }
85 | }, []);
86 |
87 | return (
88 |
97 | {children}
98 |
99 | );
100 | };
101 |
--------------------------------------------------------------------------------
/src/context/AuthContext/AuthReducer.ts:
--------------------------------------------------------------------------------
1 | import { defaultState } from "./AuthProvider";
2 | import Cookies from "universal-cookie";
3 |
4 | export const cookies = new Cookies();
5 |
6 | export const AuthReducer = (state: defaultState, action: any) => {
7 | switch (action.type) {
8 | case "LOGIN":
9 | // console.log(action.payload.token, action.payload.user);
10 | cookies.set("isAuthenticated", JSON.stringify(1));
11 | cookies.set("user", action.payload.user);
12 | cookies.set("token", action.payload.token);
13 | return {
14 | ...state,
15 | isAuthenticated: 1,
16 | user: action.payload.user,
17 | };
18 |
19 | case "LOGOUT":
20 | cookies.remove("isAuthenticated");
21 | cookies.remove("user");
22 | cookies.remove("token");
23 | return {
24 | ...state,
25 | isAuthenticated: 0,
26 | user: null,
27 | };
28 |
29 | case "RESET":
30 | return {
31 | ...state,
32 | loading: true,
33 | };
34 |
35 | default:
36 | return state;
37 | }
38 | };
39 |
40 | // {
41 | // expires: new Date(Date.now() + 3000),
42 | // path: "/",
43 | // }
44 |
--------------------------------------------------------------------------------
/src/context/CertificateContext/CertificateProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useState } from "react";
2 |
3 | export interface defaultState {}
4 |
5 | const initialState: any = {};
6 |
7 | const CertificateContext = createContext(initialState);
8 | export default CertificateContext;
9 |
10 | interface CertificateProviderProps {
11 | children?: React.ReactNode;
12 | }
13 |
14 | export const CertificateProvider: React.FC = ({
15 | children,
16 | }) => {
17 | const [certificate, setCertificate] = useState(null);
18 |
19 | return (
20 |
28 | {children}
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/src/context/CertificateContext/CertificateReducer.ts:
--------------------------------------------------------------------------------
1 | export const CertificateReducer = (state: any, action: any) => {
2 | switch (action.type) {
3 | case "GETPROJECTTOKEN":
4 | return {
5 | ...state,
6 | data: action.payload,
7 | };
8 | case "GENERATECERTIFICATE":
9 | return {
10 | ...state,
11 | certificateData: action.payload,
12 | };
13 |
14 | default:
15 | return { state };
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/src/context/CertificateContext/CertificateRouter.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { CertificatePdf } from "../../components/CertificatePdf";
3 | import CertificateContext, { CertificateProvider } from "./CertificateProvider";
4 |
5 | type Props = {
6 | children?: React.ReactNode;
7 | };
8 | const CertificateRouterUtil = ({ children }: Props) => {
9 | const { certificate } = useContext(CertificateContext);
10 | return certificate ? : <>{children}>;
11 | };
12 |
13 | export default function CertificateRouter(props: Props) {
14 | return (
15 |
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/context/ProjectContext/ProjectRouter.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import ProjectContextProvider, { ProjectContext } from ".";
3 | import ProjectPicker from "../../components/ProjectPicker";
4 |
5 | type Props = {
6 | children?: React.ReactNode;
7 | };
8 |
9 | const ProjectRouterUtil = ({ children }: Props) => {
10 | const { projectData } = useContext(ProjectContext);
11 | return projectData ? : <>{children}>;
12 | };
13 |
14 | export default function ProjectRouter({ children }: Props) {
15 | return (
16 |
17 | {children}
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/context/ProjectContext/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useMemo, useState } from "react";
2 | import displayToast from "../../components/Toast";
3 | import { apiBaseUrl } from "../../config";
4 | import { cookies } from "../AuthContext/AuthReducer";
5 |
6 | export interface defaultState {}
7 |
8 | const initialState: any = {};
9 | export const ProjectContext = createContext(initialState);
10 | type Props = {
11 | children?: React.ReactNode;
12 | };
13 |
14 | export default function ProjectContextProvider({ children }: Props) {
15 | const [projectData, setProjectData] = useState(null);
16 | const [loading, setLoading] = useState(false);
17 |
18 | const GetprojectData = async (url: string, retry: number = 0) => {
19 | await fetch(url, {
20 | method: "POST",
21 | headers: {
22 | "Content-Type": "application/json",
23 | Authorization: `${cookies.get("token")}`,
24 | },
25 | })
26 | .then((res) => res.json())
27 | .then((data) => {
28 | if (data.error) {
29 | if (data.error?.type === "GithubAPITimeoutError") {
30 | if (retry > 2) {
31 | displayToast(
32 | "Its taking longer than usual. Please bear with us.",
33 | "info"
34 | ); //github 503 error
35 | }
36 | return setTimeout(
37 | () => GetprojectData(url, retry + 1),
38 | 1000 * Math.pow(2, retry)
39 | );
40 | }
41 | displayToast(data.error.message, "failure");
42 | } else {
43 | setProjectData(data);
44 | }
45 | setLoading(false);
46 | })
47 | .catch((err) => {
48 | displayToast("Oops! Something went wrong", "failure");
49 | setLoading(false);
50 | });
51 | };
52 |
53 | const setProject = (type: string, data: any) => {
54 | if (loading) return;
55 | setLoading(true);
56 | if (type === "Github") {
57 | GetprojectData(
58 | `${apiBaseUrl}/project/github/${data.ownerName}/${data.repoName}`
59 | );
60 | } else if (type === "Gitlab") {
61 | GetprojectData(`${apiBaseUrl}/project/gitlab/${data.projectId}`);
62 | } else if (type === "Bitbucket") {
63 | GetprojectData(
64 | `${apiBaseUrl}/project/bitbucket/${data.workspace}/${data.repoName}`
65 | );
66 | } else {
67 | setLoading(false);
68 | }
69 | };
70 |
71 | const removeProject = () => {
72 | if (loading) return;
73 | setProjectData(null);
74 | };
75 |
76 | return (
77 | // eslint-disable-next-line react-hooks/exhaustive-deps
78 | ({ setProject, removeProject, projectData, loading }),
81 | [projectData, loading]
82 | )}
83 | >
84 | {children}
85 |
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/src/context/ThemeContext/ThemeProvider.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useEffect, useReducer } from "react";
2 | import { ThemeReducer } from "./ThemeReducer";
3 |
4 | export interface defaultThemeType {
5 | theme: string;
6 | }
7 |
8 | const initialTheme: defaultThemeType = {
9 | theme: "",
10 | };
11 |
12 | const ThemeContext = createContext(initialTheme);
13 |
14 | export default ThemeContext;
15 | interface ThemeProviderProps {
16 | children?: React.ReactNode;
17 | }
18 |
19 | export const ThemeProvider: React.FC = ({ children }) => {
20 | const [state, dispatch] = useReducer(ThemeReducer, initialTheme);
21 | const toggle = () => {
22 | const theme = localStorage.getItem("theme");
23 | if (theme) {
24 | const changedTheme = theme === "dark" ? "light" : "dark";
25 | const root = window.document.documentElement;
26 | root.classList.remove(theme);
27 | root.classList.add(changedTheme!);
28 | dispatch({
29 | type: "CHANGE",
30 | payload: changedTheme,
31 | });
32 | } else {
33 | dispatch({
34 | type: "CHANGE",
35 | payload: "dark",
36 | });
37 | }
38 | };
39 | useEffect(() => {
40 | const theme = localStorage.getItem("theme"); //dark
41 | if (!theme) {
42 | dispatch({
43 | type: "CHANGE",
44 | payload: "dark",
45 | });
46 | const root = window.document.documentElement;
47 | root.classList.remove("light");
48 | root.classList.add("dark");
49 | return;
50 | }
51 |
52 | const changedTheme = theme === "dark" ? "light" : "dark"; //light
53 | const root = window.document.documentElement;
54 |
55 | root.classList.remove(changedTheme);
56 | root.classList.add(theme!);
57 | dispatch({
58 | type: "CHANGE",
59 | payload: theme,
60 | });
61 | }, []);
62 | return (
63 |
64 | {children}
65 |
66 | );
67 | };
68 |
--------------------------------------------------------------------------------
/src/context/ThemeContext/ThemeReducer.ts:
--------------------------------------------------------------------------------
1 | import { defaultThemeType } from "./ThemeProvider";
2 |
3 | export const ThemeReducer = (state: defaultThemeType, action: any) => {
4 | switch (action.type) {
5 | case "CHANGE": {
6 | localStorage.setItem("theme", action.payload);
7 | //console.log(action.payload);
8 | return {
9 | ...state,
10 | theme: action.payload,
11 | };
12 | }
13 |
14 | default:
15 | return state;
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100;0,200;0,300;0,400;1,100;1,200;1,300;1,400&display=swap");
2 |
3 | * {
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | /* color: #fff; */
8 | }
9 |
10 | #root {
11 | /* display: flex; */
12 | height: 100%;
13 | width: 100%;
14 | }
15 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 | import reportWebVitals from "./reportWebVitals";
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById("root")
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/Certificate.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from "react";
2 | import { useNavigate, useParams } from "react-router-dom";
3 | import displayToast from "../components/Toast";
4 | import { apiBaseUrl } from "../config";
5 | import ThemeContext from "../context/ThemeContext/ThemeProvider";
6 | import { AiOutlineSafetyCertificate } from "react-icons/ai";
7 | import CertificateContext from "../context/CertificateContext/CertificateProvider";
8 |
9 | const Certificate: React.FC = () => {
10 | const { setCertificate: setCert } = useContext(CertificateContext);
11 | const { id } = useParams<{ id: string }>();
12 | const [loading, setLoading] = useState(true);
13 | const [certificate, setcertificate] = useState(null);
14 | const { theme } = useContext(ThemeContext);
15 | const navigate = useNavigate();
16 |
17 | useEffect(() => {
18 | setLoading(true);
19 | const url = `${apiBaseUrl}/certificate/certDetails/${id}`;
20 | fetch(url, {
21 | method: "GET",
22 | headers: {
23 | "Content-Type": "application/json",
24 | },
25 | })
26 | .then((res) => res.json())
27 | .then((data) => {
28 | if (!data.error) {
29 | console.log(data);
30 | setcertificate(data.certificate);
31 | // displayToast("Certificate Found", "success");
32 | } else {
33 | displayToast(data.error.message, "failure");
34 | navigate(-1);
35 | }
36 | })
37 | .catch((err) => {
38 | displayToast("Oops some thing went wrong!!", "failure");
39 | navigate(-1);
40 | })
41 | .finally(() => {
42 | setLoading(false);
43 | });
44 | }, [id, navigate]);
45 | return (
46 |
47 |
48 |
55 | {loading ? (
56 |
59 | ) : null}
60 | {certificate && (
61 |
62 |
63 |
64 |
65 |
66 | Certificate Found
67 |
68 |
69 | Certificate of Contribution
70 |
71 |
72 | Issued to {certificate.userName} for his/her/their
73 | active contribution in {certificate.projectRepo} {" "}
74 | project.
75 |
76 |
77 |
78 | Issued on :{" "}
79 | {new Date(certificate.createdAt).toLocaleString()}
80 |
81 |
82 |
83 |
88 |
89 |
90 | Last Contribution on{" "}
91 |
92 |
93 |
94 | {new Date(
95 | certificate.lastContributionDate
96 | ).toLocaleString()}
97 |
98 |
99 |
100 |
101 |
102 | {
105 | setCert(certificate);
106 | }}
107 | >
108 | {" "}
109 | Download
110 |
111 |
112 |
113 |
114 |
115 | )}
116 |
117 |
118 |
119 | );
120 | };
121 | export default Certificate;
122 |
--------------------------------------------------------------------------------
/src/pages/Login.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect } from "react";
2 | import { apiBaseUrl, logoDark, logoLight } from "../config";
3 | import { FaGithubAlt, FaGitlab, FaBitbucket } from "react-icons/fa";
4 | import { Button } from "../components/Button";
5 | import { useNavigate } from "react-router-dom";
6 | import AuthContext from "../context/AuthContext/AuthProvider";
7 | import { cookies } from "../context/AuthContext/AuthReducer";
8 | import { OpenPopUp } from "../components/OpenPopup";
9 | import ThemeContext from "../context/ThemeContext/ThemeProvider";
10 | import "react-toastify/dist/ReactToastify.css";
11 |
12 | interface LoginButtonProps {
13 | children: [React.ReactNode, React.ReactNode];
14 | onClick?: () => void;
15 | oauthUrl?: string;
16 | }
17 |
18 | const LoginButton: React.FC = ({
19 | children,
20 | onClick,
21 | oauthUrl,
22 | ...props
23 | }) => {
24 | const { theme } = useContext(ThemeContext);
25 | return (
26 |
32 |
33 | {children[0]}
34 | {children[1]}
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | interface LoginProps {}
42 |
43 | let browser: any = null;
44 | let popup: any = null;
45 |
46 | export const Login: React.FC = () => {
47 | const navigate = useNavigate();
48 | const { loginConfirm } = useContext(AuthContext);
49 | const { theme } = useContext(ThemeContext);
50 | useEffect(() => {
51 | browser = window.self;
52 | browser.loggedIn = (token: any) => {
53 | if (loginConfirm) {
54 | loginConfirm(token);
55 | cookies.set("token", token);
56 | }
57 | if (popup && !popup.closed) {
58 | popup.close();
59 | }
60 | };
61 | });
62 |
63 | const onClick = (url: string) => {
64 | popup = OpenPopUp(url, browser, popup);
65 | };
66 |
67 | return (
68 | <>
69 |
79 |
80 |
81 |
82 |
87 |
88 |
89 |
90 |
91 | Welcome to{" "}
92 |
95 | Open-Certs
96 |
97 |
98 |
99 |
100 | onClick(`${apiBaseUrl}/auth/github`)}>
101 |
102 | Log in with GitHub
103 |
104 | onClick(`${apiBaseUrl}/auth/gitlab`)}>
105 |
106 | Log in with Gitlab
107 |
108 |
109 | onClick(`${apiBaseUrl}/auth/bitbucket`)}
111 | >
112 |
113 | Log in with Bitbucket
114 |
115 |
116 |
117 |
118 | >
119 | );
120 | };
121 |
--------------------------------------------------------------------------------
/src/pages/Main.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import Lottie from "react-lottie";
3 | import * as HomeAnime from "../assets/home.json";
4 | import AuthContext from "../context/AuthContext/AuthProvider";
5 | import { Grid } from "@mui/material";
6 | import ThemeContext from "../context/ThemeContext/ThemeProvider";
7 | import MicroLogin from "../components/MicroLogin";
8 | import ProjectInput from "../components/ProjectInput";
9 | import ProjectRouter from "../context/ProjectContext/ProjectRouter";
10 |
11 | interface MainProps {}
12 |
13 | export const Main: React.FC = () => {
14 | const { isAuthenticated } = useContext(AuthContext);
15 | const { theme } = useContext(ThemeContext);
16 |
17 | return (
18 |
19 |
20 |
27 |
28 |
29 |
30 |
31 |
32 | Open-Certs
33 |
34 |
35 | Certify your open source contributions in just few steps.
36 |
37 |
38 | {!isAuthenticated ?
:
}
39 |
40 |
41 |
42 |
49 |
50 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | );
70 | };
71 |
--------------------------------------------------------------------------------
/src/pages/VerifyCertificate.tsx:
--------------------------------------------------------------------------------
1 | import { Grid } from "@mui/material";
2 | import React, { useContext, useState } from "react";
3 | import { useNavigate } from "react-router-dom";
4 | import { Button } from "../components/Button";
5 | import ThemeContext from "../context/ThemeContext/ThemeProvider";
6 |
7 | interface VerifyCertificateProps {}
8 | export const VerifyCertificate: React.FC = () => {
9 | const { theme } = useContext(ThemeContext);
10 | const navigate = useNavigate();
11 | const [certificateID, setCertificateID] = useState("");
12 |
13 | const handleVerify = async () => {
14 | const id: string = certificateID.replaceAll("-", "");
15 | navigate("/certificate/" + id);
16 | };
17 | return (
18 | <>
19 |
20 |
27 |
28 |
29 |
30 |
31 |
32 | Verify Certificate
33 |
34 |
35 | Verify certificate generated using Open-certs
36 |
37 |
38 |
39 |
40 | Certificate ID
41 |
42 | setCertificateID(e.target.value)}
50 | />
51 |
52 |
handleVerify()}
54 | className="mx-5 w-1/4 h-2/5 justify-center items-center text-base px-3 sm:px-6"
55 | color={
56 | theme === "dark" ? "secondary" : "accent-secondary"
57 | }
58 | >
59 | Verify
60 |
61 |
62 |
63 |
64 | {/*
65 |
70 | */}
71 |
72 |
73 |
74 |
75 | >
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/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/router/config.ts:
--------------------------------------------------------------------------------
1 | import Certificate from "../pages/Certificate";
2 | import { Login } from "../pages/Login";
3 | import { Main } from "../pages/Main";
4 | import { VerifyCertificate } from "../pages/VerifyCertificate";
5 |
6 | const RouterConfig = [
7 | {
8 | path: "/login",
9 | Component: Login,
10 | },
11 | {
12 | path: "/",
13 | Component: Main,
14 | },
15 | {
16 | path: "/verifyCertificate",
17 | Component: VerifyCertificate,
18 | },
19 | {
20 | path: "/certificate/:id",
21 | Component: Certificate,
22 | },
23 | {
24 | path: "/certificate",
25 | Component: Certificate,
26 | },
27 | ];
28 |
29 | export default RouterConfig;
30 |
--------------------------------------------------------------------------------
/src/router/route.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Routes, Route } from "react-router-dom";
3 | import routerConfig from "./config";
4 |
5 | interface RoutingProps {}
6 |
7 | export const Routing: React.FC = () => {
8 | return (
9 |
10 | {routerConfig.map((Rut: any) => {
11 | return (
12 | } />
13 | );
14 | })}
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/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";
6 |
--------------------------------------------------------------------------------
/src/sitemap/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-certs/oc-frontend/316d859a92f9489eeb8257f3ae5d642a7b5b8f2d/src/sitemap/.gitkeep
--------------------------------------------------------------------------------
/src/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | module.exports = {
3 | darkMode: "class",
4 | purge: {
5 | content: ["./src/**/*.tsx", "./public/index.html"],
6 | options: {
7 | safelist: ["h-8", "h-11"],
8 | },
9 | },
10 | theme: {
11 | fontFamily: {
12 | sans: [
13 | "Inter",
14 | "-apple-system",
15 | "BlinkMacSystemFont",
16 | "Segoe UI",
17 | "Roboto",
18 | "Helvetica",
19 | "Arial",
20 | "sans-serif",
21 | ],
22 | mono: ["Menlo", "Monaco", "Courier New", "monospace"],
23 | },
24 | fontSize: {
25 | tiny: "0.625rem",
26 | xs: ".75rem",
27 | sm: ".875rem",
28 | base: "1rem",
29 | lg: "1.125rem",
30 | xl: "1.25rem",
31 | "2xl": "1.5rem",
32 | "3xl": "1.875rem",
33 | "4xl": "2.25rem",
34 | "5xl": "3rem",
35 | "6xl": "4rem",
36 | "7xl": "5rem",
37 | },
38 | colors: {
39 | button: "var(--color-button-text)",
40 | transparent: "transparent",
41 | primary: {
42 | DEFAULT: "var(--color-white)",
43 | 100: "var(--color-primary-100)",
44 | 200: "var(--color-primary-200)",
45 | 300: "var(--color-primary-300)",
46 | 600: "var(--color-primary-600)",
47 | 700: "var(--color-primary-700)",
48 | 800: "var(--color-primary-800)",
49 | 900: "var(--color-primary-900)",
50 | bgDark: "var(--color-dark-background)",
51 | darkWave1bg: "var(--color-darkwave1-background)",
52 | },
53 | secondary: {
54 | DEFAULT: "var(--color-secondary)",
55 | "washed-out": "var(--color-secondary-washed-out)",
56 | dark: "var(--color-secondary-dark)",
57 | },
58 | accent: {
59 | DEFAULT: "var(--color-accent)",
60 | hover: "var(--color-accent-hover)",
61 | disabled: "var(--color-accent-disabled)",
62 | },
63 | accentGradient: {
64 | DEFAULT: "var(--color-accent-gradient)",
65 | },
66 | black: "#000",
67 | },
68 | spacing: {
69 | 0: "0px",
70 | 1: "5px",
71 | 1.5: "6px",
72 | 2: "10px",
73 | 3: "15px",
74 | 4: "20px",
75 | 4.5: "25px",
76 | 5: "30px",
77 | 5.5: "35px",
78 | 6: "40px",
79 | 6.5: "50px",
80 | 7: "60px",
81 | 7.5: "65px",
82 | 8: "75px",
83 | 9: "80px",
84 | 10: "90px",
85 | 11: "100px",
86 | 15: "150px",
87 | "5l": "10rem",
88 | "n1/2": "-50%",
89 | 24: "24rem",
90 | 400: "400px",
91 | },
92 |
93 | boxShadow: {
94 | outlineLg: "0 0 0 4pt var(--color-primary-800)",
95 | outlineMd: "0 0 0 2pt var(--color-primary-800)",
96 | outlineSm: "0 0 0 1pt var(--color-primary-800)",
97 | },
98 | borderWidth: {
99 | DEFAULT: "1px",
100 | 0: "0px",
101 | 4: "4px",
102 | 2: "2px",
103 | },
104 | extend: {
105 | borderRadius: {
106 | 5: "5px",
107 | 8: "8px",
108 | 20: "20px",
109 | 40: "40px",
110 | },
111 | borderColor: {
112 | "color-800": "var(--color-primary-800)",
113 | },
114 | outline: {
115 | "no-chrome": "none",
116 | },
117 | transitionTimingFunction: {
118 | "in-out-hard": "cubic-bezier(.77, 0, .175, 1)",
119 | },
120 | transitionDuration: {
121 | 400: "400ms",
122 | },
123 | keyframes: {
124 | breathe: {
125 | "0%, 100%": {
126 | boxShadow: "0 0 20px 2px var(--color-primary-100-translucent)",
127 | borderColor: "var(--color-primary-300)",
128 | },
129 | "50%": {
130 | boxShadow: "0 0 20px 2px transparent",
131 | borderColor: "var(--color-primary-700)",
132 | },
133 | },
134 | },
135 | animation: {
136 | "breathe-slow": "breathe 5s infinite ease-in-out",
137 | },
138 | },
139 | },
140 | variants: {
141 | backgroundColor: ({ after }) => after(["disabled"]),
142 | textColor: ["responsive", "hover", "focus", "group-hover"],
143 | scrollbar: ["rounded", "dark"],
144 | extend: {
145 | borderWidth: ["last"],
146 | },
147 | },
148 | plugins: [require("tailwind-scrollbar"), require("@tailwindcss/line-clamp")],
149 | };
150 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noFallthroughCasesInSwitch": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["src"]
20 | }
21 |
--------------------------------------------------------------------------------