├── .dockerignore ├── .github └── ISSUE_TEMPLATE │ ├── bug_report_template.md │ ├── config.yml │ ├── custom_issue_template.md │ └── feature_request_template.md ├── .gitignore ├── About.html ├── Code_of_conduct.md ├── Contributing.md ├── Dockerfile ├── History.html ├── Home.css ├── Home.html ├── LICENSE ├── Project Structure.md ├── README.md ├── Settings.html ├── Theme.html ├── Timer.html ├── World_Clock.html ├── backend ├── .env ├── .gitignore ├── Dockerfile ├── config │ ├── database.js │ └── passport.js ├── controllers │ ├── authController.js │ ├── othercontrollers.js │ ├── subscribeController.js │ └── todoController.js ├── index.js ├── middlewares │ └── auth.middleware.js ├── models │ ├── Newsletters.js │ ├── Todo.js │ └── User.js ├── package-lock.json ├── package.json ├── passport.js ├── routes │ ├── authRoutes.js │ ├── googleauth.js │ └── todoRoutes.js └── vercel.json ├── components.json ├── eslint.config.js ├── folder_structure.txt ├── footer.css ├── footer.html ├── index.html ├── jsconfig.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── preloader.js ├── preloaderStyle.css ├── public ├── bg.jpg ├── vite.svg └── wallpaperflare.com_wallpaper.jpg ├── readme1.md ├── src ├── App.css ├── App.jsx ├── assets │ ├── afternoonBackground.png │ ├── afternoonBackground.webp │ ├── clock.png │ ├── clock.webp │ ├── clock_img.png │ ├── clock_img.webp │ ├── counter-app-video-demo2 (1) (2).mp4 │ ├── dark.jpg │ ├── dark.webp │ ├── darklogo1.png │ ├── darklogo1.webp │ ├── image_with_black_background.png │ ├── image_with_black_background.webp │ ├── login.png │ ├── login.webp │ ├── logo.png │ ├── logo.webp │ ├── logo1.png │ ├── logo1.webp │ ├── morningBackground.png │ ├── morningBackground.webp │ ├── nightBackground.png │ ├── nightBackground.webp │ ├── react.svg │ ├── signup.png │ ├── signup.webp │ ├── timer1.png │ └── timer1.webp ├── components │ ├── About.jsx │ ├── Auth │ │ ├── LoginForm.jsx │ │ ├── LoginImage.jsx │ │ ├── PasswordRecovery.jsx │ │ ├── Review.jsx │ │ ├── SignupForm.jsx │ │ ├── SignupImage.jsx │ │ └── Template.jsx │ ├── CumulativeTimeChart.jsx │ ├── Design.jsx │ ├── Footer.jsx │ ├── GoogleTranslate.jsx │ ├── LapAnalysis.jsx │ ├── LapBarChart.jsx │ ├── LapPieChart.jsx │ ├── LapVisualization.jsx │ ├── ParticlesComponent.jsx │ ├── SpotifyPlayer.css │ ├── SpotifyPlayer.jsx │ ├── SwitchTab.jsx │ ├── audio │ │ └── alert-85101.mp3 │ ├── controls.jsx │ ├── css │ │ ├── Footer.css │ │ └── navbar.css │ ├── laplist.jsx │ ├── navbar.jsx │ ├── pop-up.jsx │ ├── timer.jsx │ └── ui │ │ ├── button.jsx │ │ ├── card.jsx │ │ └── scroll-area.jsx ├── index.css ├── lib │ └── utils.js ├── main.jsx ├── output.css ├── pages │ ├── AutoCounterPage.jsx │ ├── Contributors.css │ ├── Contributors.jsx │ ├── Counter.jsx │ ├── Error404.jsx │ ├── Feedback.css │ ├── Feedback.jsx │ ├── PrivacyPolicy.css │ ├── PrivacyPolicy.jsx │ ├── SignUpPage.css │ ├── SignUpPage.jsx │ ├── TermsPage.jsx │ ├── TimerPage.css │ ├── TimerPage.jsx │ ├── Todo.jsx │ ├── WorkTracker.jsx │ └── WorldClockPage.jsx ├── reducer │ └── index.js ├── services │ ├── apiConnector.js │ ├── apis.js │ └── operations │ │ └── authAPI.js ├── slices │ ├── authSlice.js │ └── profileSlice.js └── validations │ └── validation.js ├── tailwind.config.js ├── vercel.json └── vite.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | yarn-error.log 4 | 5 | dist 6 | build 7 | 8 | .env 9 | .env.local 10 | .env.*.local 11 | 12 | *.log 13 | 14 | Dockerfile 15 | .dockerignore 16 | 17 | .git 18 | .gitignore 19 | 20 | .vscode 21 | .idea 22 | *.swp 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐞 Bug Report" 3 | about: "Report a bug in the Counter App." 4 | title: "[Bug]: " 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | # 🐞 Bug Report 11 | 12 | ### 📋 Description 13 | 14 | 15 | ### ⚙️ Steps to Reproduce 16 | Steps to reproduce the behavior: 17 | 1. 18 | 2. 19 | 3. 20 | 21 | ### ✔️ Expected Behavior 22 | 23 | 24 | ### ❌ Actual Behavior 25 | 26 | 27 | ### 📷 Screenshots 28 | 29 | 30 | ### 🖥️ Environment (if applicable): 31 | - **OS**: (e.g., Windows, macOS, Linux) 32 | - **Browser**: (e.g., Chrome, Safari, Firefox) 33 | - **Version**: (if relevant) 34 | 35 | ### 🔧 Additional Information 36 | 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Guidelines 4 | url: https://github.com/param-code/counter-app/blob/main/Contributing.md 5 | about: Before opening a new issue, please make sure to read CONTRIBUTING.md 6 | - name: Questions or need suggestions? 7 | url: https://github.com/param-code/counter-app/discussions/new?category=q-a 8 | about: You can create a Q&A discussion and ask for clarifications 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom_issue_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "💡 Custom Issue" 3 | about: "Create a custom issue related to the Counter App." 4 | title: "[Custom]: " 5 | labels: enhancement, discussion 6 | assignees: '' 7 | 8 | --- 9 | 10 | # 💡 Custom Issue 11 | 12 | ### 📝 Description 13 | 14 | 15 | ### 🔍 Steps to Reproduce (if applicable) 16 | 1. 17 | 2. 18 | 3. 19 | 20 | ### 🎯 Expected Outcome 21 | 22 | 23 | ### 📸 Screenshots or Additional Information 24 | 25 | 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🚀 Feature Request" 3 | about: "Suggest a new feature or enhancement for the Counter App." 4 | title: "[Feature]: " 5 | labels: enhancement, feature request 6 | assignees: '' 7 | 8 | --- 9 | 10 | # 🚀 Feature Request 11 | 12 | ### 📝 Feature Description 13 | 14 | 15 | ### 🔧 How Will This Enhance the App? 16 | 17 | 18 | ### 🎯 Use Cases 19 | 20 | 21 | ### 📦 Suggested Implementation 22 | 23 | 24 | ### 📸 Additional Context 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /About.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/About.html -------------------------------------------------------------------------------- /Code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # ✨Contributor Covenant Code of Conduct✨ 2 | 3 | --- 4 | 5 | ## 🌟Our Pledge 6 | --- 7 | We as members, contributors, and leaders pledge to make participation in our 8 | community a harassment-free experience for everyone, regardless of age, body 9 | size, visible or invisible disability, ethnicity, sex characteristics, gender 10 | identity and expression, level of experience, education, socio-economic status, 11 | nationality, personal appearance, race, religion, or sexual identity 12 | and orientation. 13 | 14 | We pledge to act and interact in ways that contribute to an open, welcoming, 15 | diverse, inclusive, and healthy community. 16 | 17 | --- 18 | ## 🚦Our Standards 19 | 20 | Examples of behavior that contributes to a positive environment for our 21 | community include: 22 | 23 | * 💖Demonstrating empathy and kindness toward other people 24 | * 🤝Being respectful of differing opinions, viewpoints, and experiences 25 | * 📝Giving and gracefully accepting constructive feedback 26 | * 🌱Accepting responsibility and apologizing to those affected by our mistakes, 27 | and learning from the experience 28 | * 🎯Focusing on what is best not just for us as individuals, but for the 29 | overall community 30 | 31 | Examples of unacceptable behavior include: 32 | 33 | * 🚫The use of sexualized language or imagery, and sexual attention or 34 | advances of any kind 35 | * 🛑Trolling, insulting or derogatory comments, and personal or political attacks 36 | * ⚠️Public or private harassment 37 | * 🔒Publishing others' private information, such as a physical or email 38 | address, without their explicit permission 39 | * ❌Other conduct which could reasonably be considered inappropriate in a 40 | professional setting 41 | 42 | --- 43 | 44 | ## 👨‍⚖️Enforcement Responsibilities 45 | 46 | Community leaders are responsible for clarifying and enforcing our standards of 47 | acceptable behavior and will take appropriate and fair corrective action in 48 | response to any behavior that they deem inappropriate, threatening, offensive, 49 | or harmful. 50 | 51 | Community leaders have the right and responsibility to remove, edit, or reject 52 | comments, commits, code, wiki edits, issues, and other contributions that are 53 | not aligned to this Code of Conduct, and will communicate reasons for moderation 54 | decisions when appropriate. 55 | 56 | --- 57 | ## 🌐Scope 58 | 59 | This Code of Conduct applies within all community spaces, and also applies when 60 | an individual is officially representing the community in public spaces. 61 | Examples of representing our community include using an official e-mail address, 62 | posting via an official social media account, or acting as an appointed 63 | representative at an online or offline event. 64 | 65 | --- 66 | ## 📋Pull Request Guidelines 67 | 68 | Contributions, including pull requests, must align with this Code of Conduct. 69 | All contributors are expected to submit work that fosters a positive and inclusive community. 70 | Inappropriate behavior, language, or content in pull requests may result in the rejection of 71 | the contribution and further enforcement actions as outlined in the guidelines. 72 | 73 | --- 74 | ## 🛡️Enforcement 75 | 76 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 77 | reported to the community leaders responsible for enforcement at 78 | . 79 | All complaints will be reviewed and investigated promptly and fairly. 80 | 81 | All community leaders are obligated to respect the privacy and security of the 82 | reporter of any incident. 83 | 84 | --- 85 | ## 📖Enforcement Guidelines 86 | 87 | Community leaders will follow these Community Impact Guidelines in determining 88 | the consequences for any action they deem in violation of this Code of Conduct: 89 | 90 | ### 1. ✏️Correction 91 | 92 | **Community Impact**: Use of inappropriate language or other behavior deemed 93 | unprofessional or unwelcome in the community. 94 | 95 | **Consequence**: A private, written warning from community leaders, providing 96 | clarity around the nature of the violation and an explanation of why the 97 | behavior was inappropriate. A public apology may be requested. 98 | 99 | ### 2. ⚠️Warning 100 | 101 | **Community Impact**: A violation through a single incident or series 102 | of actions. 103 | 104 | **Consequence**: A warning with consequences for continued behavior. No 105 | interaction with the people involved, including unsolicited interaction with 106 | those enforcing the Code of Conduct, for a specified period of time. This 107 | includes avoiding interactions in community spaces as well as external channels 108 | like social media. Violating these terms may lead to a temporary or 109 | permanent ban. 110 | 111 | ### 3. ⏳Temporary Ban 112 | 113 | **Community Impact**: A serious violation of community standards, including 114 | sustained inappropriate behavior. 115 | 116 | **Consequence**: A temporary ban from any sort of interaction or public 117 | communication with the community for a specified period of time. No public or 118 | private interaction with the people involved, including unsolicited interaction 119 | with those enforcing the Code of Conduct, is allowed during this period. 120 | Violating these terms may lead to a permanent ban. 121 | 122 | ### 4. 🚫Permanent Ban 123 | 124 | **Community Impact**: Demonstrating a pattern of violation of community 125 | standards, including sustained inappropriate behavior, harassment of an 126 | individual, or aggression toward or disparagement of classes of individuals. 127 | 128 | **Consequence**: A permanent ban from any sort of public interaction within 129 | the community. 130 | 131 | --- 132 | ## 📜Attribution 133 | 134 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 135 | version 2.0, available at 136 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 137 | 138 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 139 | enforcement ladder](https://github.com/mozilla/diversity). 140 | 141 | [homepage]: https://www.contributor-covenant.org 142 | 143 | For answers to common questions about this code of conduct, see the FAQ at 144 | https://www.contributor-covenant.org/faq. Translations are available at 145 | https://www.contributor-covenant.org/translations. 146 | 147 | --- 148 | # 🌟Conclusion 149 | By adhering to this Code of Conduct, we create a respectful, inclusive, and empowering environment for everyone involved in our community. Together, we can build a space where all members feel safe, supported, and encouraged to contribute their best. We invite every member to take responsibility for upholding these standards, ensuring that our community remains open and welcoming to all. 150 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | #step 1 2 | FROM node:14-alpine AS build 3 | 4 | 5 | WORKDIR /usr/src/app 6 | 7 | COPY package*.json ./ 8 | 9 | 10 | RUN npm install 11 | 12 | 13 | COPY . . 14 | #step 2 15 | 16 | RUN npm run build 17 | 18 | FROM nginx:alpine 19 | 20 | COPY --from=build /usr/src/app/dist /usr/share/nginx/html 21 | 22 | EXPOSE 80 23 | 24 | CMD ["nginx", "-g", "daemon off;"] 25 | -------------------------------------------------------------------------------- /History.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/History.html -------------------------------------------------------------------------------- /Home.css: -------------------------------------------------------------------------------- 1 | footer { 2 | background-color: #05365f; 3 | color: #f6f0f0; 4 | text-align: center; 5 | position: fixed; 6 | bottom: 0; 7 | width: 100%; 8 | padding: 10px 0; 9 | } 10 | 11 | .footer-content { 12 | justify-content: space-between; 13 | align-items: center; 14 | max-width: 1200px; 15 | margin: 0 auto; 16 | padding: 0 20px; 17 | } 18 | 19 | .footer-nav { 20 | list-style-type: none; 21 | display:flexbox; 22 | gap: 60px; 23 | margin: 20; 24 | padding:0; 25 | } 26 | .footer-nav a { 27 | color: #ccc; 28 | text-decoration: none; 29 | font-weight: 500; 30 | gap: 20px; 31 | } 32 | 33 | .footer-nav a:hover { 34 | text-decoration: underline; 35 | gap:30px; 36 | } 37 | 38 | .footer-copy { 39 | font-size: 0.9em; 40 | color: #ccc; 41 | } 42 | 43 | body { 44 | margin:0; 45 | min-height: 100vh; 46 | } 47 | html { 48 | scroll-behavior: smooth; 49 | } 50 | /* Footer content styling */ 51 | .footer-content { 52 | background-color: #333; /* Background color of footer */ 53 | color: #fff; /* Text color */ 54 | padding: 20px; 55 | text-align: center; 56 | } 57 | 58 | /* Footer navigation bar styling */ 59 | .footer-nav { 60 | list-style: none; /* Removes bullets from the list */ 61 | padding: 0; /* Removes padding */ 62 | margin: 0; /* Removes margin */ 63 | display: flex; /* Flexbox for horizontal layout */ 64 | justify-content: center; /* Centers the navigation links */ 65 | } 66 | 67 | /* Footer nav links */ 68 | .footer-nav li { 69 | margin: 0 15px; /* Space between links */ 70 | } 71 | 72 | .footer-nav a { 73 | color: #fff; /* Link color */ 74 | text-decoration: none; /* Removes underlines */ 75 | font-size: 16px; 76 | font-weight: bold; 77 | padding: 5px 10px; 78 | border-radius: 5px; 79 | transition: background-color 0.3s ease; /* Adds a transition effect for hover */ 80 | } 81 | 82 | /* Hover effect for navigation links */ 83 | .footer-nav a:hover { 84 | background-color: #555; /* Change background on hover */ 85 | color: #ffcc00; /* Change text color on hover */ 86 | } 87 | 88 | /* Footer copyright text styling */ 89 | .footer-copy { 90 | margin-top: 20px; 91 | font-size: 14px; 92 | color: #ccc; /* Lighter text color for copyright info */ 93 | } 94 | 95 | /* Section styling */ 96 | section { 97 | padding: 60px 20px; /* Adds space inside the section */ 98 | margin: 40px 0; /* Adds space between sections */ 99 | background-color: #f9f9f9; /* Background color for the section */ 100 | border-radius: 10px; /* Rounded corners for the section */ 101 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Subtle shadow for depth */ 102 | } 103 | 104 | /* Section titles (h1) styling */ 105 | section h1 { 106 | font-size: 28px; 107 | color: #333; 108 | text-align: center; /* Centers the title */ 109 | margin-bottom: 20px; 110 | } 111 | 112 | /* Section paragraph (p) styling */ 113 | section p { 114 | font-size: 16px; 115 | color: #666; 116 | text-align: center; /* Centers the text */ 117 | line-height: 1.6; 118 | } 119 | 120 | /* Media query for smaller screens */ 121 | @media (max-width: 768px) { 122 | .footer-nav { 123 | flex-direction: column; /* Stacks the navigation links vertically */ 124 | } 125 | 126 | .footer-nav li { 127 | margin: 10px 0; /* Adds space between stacked links */ 128 | } 129 | 130 | section { 131 | padding: 40px 10px; /* Reduce padding for smaller screens */ 132 | } 133 | 134 | section h1 { 135 | font-size: 24px; /* Adjust title size */ 136 | } 137 | 138 | section p { 139 | font-size: 14px; /* Adjust paragraph size */ 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Counter Timer 7 | 8 | 9 | 15 | 16 | 17 |
18 |
31 | 32 | 33 |
34 |

Home Section

35 |

This is the main page where you can start using the timer features.

36 |
37 | 38 |
39 |

About Section

40 |

This app helps you track time with various tools like a timer, world clock, and history tracking.

41 |
42 | 43 |
44 |

Timer Section

45 |

Use the timer to count down or track time intervals for various tasks.

46 |
47 | 48 |
49 |

World Clock Section

50 |

Check the current time in various locations around the globe.

51 |
52 | 53 |
54 |

History Section

55 |

Review your past counter sessions.

56 |
57 | 58 |
59 |

Settings Section

60 |

Configure the app to match your preferences, including setting time zones and notifications.

61 | 62 |
63 | 64 |
65 |

Themes Section

66 |

Switch between light and dark themes to suit your viewing preference.

67 |
68 | 69 | 75 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Paramveer Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Project Structure.md: -------------------------------------------------------------------------------- 1 | ## Project Structure 📂 2 | ``` 3 | counter-app 4 | │ 5 | ├──.github/ISSUE_TEMPLATE 6 | │ ├──bug_report_template.md 7 | │ ├──config.yml 8 | │ ├──custom_issue_template.md 9 | │ └──feature_request_template.md 10 | │ 11 | ├── backend 12 | │ ├──config 13 | │ │ ├──database.js 14 | │ │ └──passport.js 15 | │ ├──controllers 16 | │ │ ├─authController.js 17 | │ │ ├─othercontrollers.js 18 | │ │ ├─subscribeController.js 19 | │ │ └──todoController.js 20 | │ │ 21 | │ ├── middlewares 22 | │ │ └──auth.middleware.js 23 | │ │ 24 | │ ├──models 25 | │ │ ├─Newsletters.js 26 | │ │ ├─Todo.js 27 | │ │ └──User.js 28 | │ │ 29 | │ ├──routes 30 | │ │ ├─authRoutes.js 31 | │ │ └──todoRoutes.js 32 | │ │ 33 | │ ├──.env 34 | │ ├──.gitignore 35 | │ ├──Dockerfile 36 | │ ├──index.js 37 | │ ├──package-lock.json 38 | │ ├──package.json 39 | │ └──vercel.json 40 | │ 41 | ├──counter-app 42 | ├──public 43 | │ ├──bg.jpg 44 | │ ├──vite.svg 45 | │ └──wallpaperflare.com_wallpaper.jpg 46 | ├──src 47 | │ ├──assets 48 | │ ├──components 49 | │ ├──lib 50 | │ ├──pages 51 | │ ├──reducer 52 | │ ├──services 53 | │ ├──slices 54 | │ ├──validations 55 | │ ├──App.css 56 | │ ├──App.jsx 57 | │ ├──index.css 58 | │ ├──main.jsx 59 | │ └──output.css 60 | │ 61 | ├──.dockerignore 62 | ├──.gitignore 63 | ├──About.html 64 | ├──Code_of_conduct.md 65 | ├──Contributing.md 66 | ├──Dockerfile 67 | ├──History.html 68 | ├──Home.css 69 | ├──Home.html 70 | ├──LICENSE 71 | ├──README.md 72 | ├──Settings.html 73 | ├──Theme.html 74 | ├──Timer.html 75 | ├──World_Clock.html 76 | ├──components.json 77 | ├──eslint.config.js 78 | ├──folder_structure.txt 79 | ├──footer.css 80 | ├──footer.html 81 | ├──index.html 82 | ├──jsconfig.json 83 | ├──package-lock.json 84 | ├──package.json├── 85 | ├──postcss.config.js 86 | ├──preloader.js 87 | ├──preloaderStyle.css 88 | ├──readme1.md 89 | ├──tailwind.config.js 90 | ├──vercel.json 91 | └──vite.config.js 92 | ``` 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # ⏱️ Counter App - Stopwatch Application 3 | 4 | Welcome to the **Counter App**, a simple stopwatch application built using **React** and **Vite**. This app allows users to measure time intervals with features for starting, pausing, and resetting the stopwatch. 5 | 6 |

7 | 8 | 9 | 10 | 11 |

12 | 13 | ### 🌐 Backend is available at https://counter-app-backend.vercel.app/ 14 | 15 | ## 📈 GitHub Repository Stats 16 | | 🌟 **Stars** | 🍴 **Forks** | 🐛 **Issues** | 🔔 **Open PRs** | 🔕 **Closed PRs** | 🛠️ **Languages** | ✅ **Contributors** | 17 | |--------------|--------------|---------------|-----------------|------------------|------------------|------------------| 18 | | ![GitHub stars](https://img.shields.io/github/stars/param-code/counter-app) | ![forks](https://img.shields.io/github/forks/param-code/counter-app) | ![issues](https://img.shields.io/github/issues/param-code/counter-app?color=32CD32) | ![pull requests](https://img.shields.io/github/issues-pr/param-code/counter-app?color=FFFF8F) | ![Closed PRs](https://img.shields.io/github/issues-pr-closed/param-code/counter-app?color=20B2AA) | ![Languages](https://img.shields.io/github/languages/count/param-code/counter-app?color=20B2AA) | ![Contributors](https://img.shields.io/github/contributors/param-code/counter-app?color=00FA9A) | 19 | 20 | ## 📋 Table of Contents 21 | 22 | - [Introduction](#introduction) 23 | - [Features](#features) 24 | - [Installation](#installation) 25 | - [Usage](#usage) 26 | - [How It Works](#how-it-works) 27 | - [Functionality Overview](#functionality-overview) 28 | - [Video Demo](#video-demo) 29 | - [Contributing](#contributing) 30 | - [License](#license) 31 | - [Our Valuable Contributors](#our-valuable-contributors) 32 | 33 | ## 🛠️ Features 34 | 35 | - 🚀 Start, pause, and reset stopwatch functionality 36 | - ⏱️ Real-time time display 37 | - 💻 Easy-to-use and responsive user interface 38 | - ⚡ Fast performance with Vite for development 39 | - 🌐 Deployed version available (optional: include link) 40 | 41 | ## 🧰 Installation 42 | 43 | Follow these steps to get the Counter App up and running on your local machine. 44 | 45 | 1. **Clone the repository** 46 | ```bash 47 | git clone https://github.com/yourusername/counter-app.git 48 | ``` 49 | 2. **Navigate to the project directory** 50 | ```bash 51 | cd counter-app 52 | ``` 53 | 3. **Install dependencies** 54 | ```bash 55 | npm install 56 | ``` 57 | 4. **Start the development server** 58 | ```bash 59 | npm run dev 60 | ``` 61 | 62 | Vite will launch the app in your default browser at `http://localhost:5173`. 63 | 64 | ## 🖥️ Usage 65 | 66 | 1. **Start/Stop**: Click the 'Start' button to begin timing, and the same button will turn into 'Stop' when the timer is running. 67 | 2. **Pause/Resume**: Pause the stopwatch by pressing the 'Stop' button and resume with 'Start'. 68 | 3. **Reset**: Reset the timer to zero by clicking the 'Reset' button. 69 | 70 | ## ⚙️ How It Works 71 | 72 | The Counter App uses React's state management to control the stopwatch functionality. When the 'Start' button is pressed, the app starts counting time using `setInterval()`, and when paused or stopped, it clears the interval. Time is displayed in real-time by continuously updating the state. 73 | 74 | ### 🔍 Functionality Overview: 75 | - **Start Timer**: Initializes the timer using a `setInterval` function, updating every 100 milliseconds. 76 | - **Pause Timer**: Stops the interval without resetting the elapsed time. 77 | - **Reset Timer**: Clears the interval and resets the state to the initial value of `00:00:00`. 78 | 79 | ## 🎥 Video Demo 80 | 81 | Check out the app in action by watching this demo: 82 | 83 | 87 | 88 | 89 | ## 🤝 Contributing 90 | 91 | Contributions are welcome! If you would like to make any changes, feel free to fork the repository and submit a pull request. 92 | 93 | 1. Fork the repository 94 | 2. Clone the forked repository `git clone https://github.com/yourusername/counter-app.git` 95 | 3. Create a new branch for your feature: `git checkout -b feature-branch` 96 | 4. Commit your changes: `git commit -m 'Add new feature'` 97 | 5. Push to the branch: `git push origin feature-branch` 98 | 6. Submit a pull request 99 | 100 | For more detailed guidelines on contributing, please refer to the [Contributing.md](Contributing.md) file. 101 | 102 | ## 📜 License 103 | 104 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 105 | 106 | ## 🙌 Our Valuable Contributors 107 | 108 | [![Contributors](https://contrib.rocks/image?repo=param-code/counter-app)](https://github.com/param-code/counter-app/graphs/contributors) 109 | 110 | ![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png) 111 | 112 | 113 | 114 | ## ⭐ Stargazers ❤️ 115 | 116 |
117 | 118 | [![Stargazers repo roster for @param-code/counter-app](https://reporoster.com/stars/dark/param-code/counter-app)](https://github.com/param-code/counter-app/stargazers) 119 | 120 | 121 |
122 | 123 | ## 🍴 Forkers ❤️ 124 | 125 | [![Forkers repo roster for @param-code/counter-app](https://reporoster.com/forks/dark/param-code/counter-app)](https://github.com/param-code/counter-app/network/members) 126 | 127 | 128 | 129 | 130 | ![-----------------------------------------------------](https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png) 131 | 132 | 133 |
134 | 135 | Back to Top 136 | 137 |
-------------------------------------------------------------------------------- /Settings.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/Settings.html -------------------------------------------------------------------------------- /Theme.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 11 | 17 |
18 | 19 | -------------------------------------------------------------------------------- /Timer.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/Timer.html -------------------------------------------------------------------------------- /World_Clock.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/World_Clock.html -------------------------------------------------------------------------------- /backend/.env: -------------------------------------------------------------------------------- 1 | MONGODB_URL=mongodb://127.0.0.1:27017/counter_app 2 | JWT_SECRET="MYSECRET" 3 | PORT=4000 4 | GOOGLE_CLIENT_ID=your_client_id 5 | GOOGLE_CLIENT_SECRET=your_client_secret 6 | 7 | #your email 8 | SMTP_EMAIL=your_email_id_for nodemailer_which_have_passkey 9 | # To create a passkey on the phone or computer you’re on: 10 | 11 | # 1. Go to https://myaccount.google.com/signinoptions/passkeys. 12 | # 2. Tap Create a passkey and then Continue.(You'll be required to unlock your device.) 13 | # 3. A 16 character passkey is generated which you can use in below. 14 | 15 | SMTP_PASSWORD=your_passkey 16 | 17 | #your mail id where you want to accept the feedback mail 18 | MAIL=your_mail_where_you_accept_feedback_mails -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | #.env -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install --production 8 | 9 | COPY . . 10 | 11 | EXPOSE 4000 12 | 13 | CMD ["node", "index.js"] 14 | -------------------------------------------------------------------------------- /backend/config/database.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | const mongoose = require("mongoose"); 4 | require("dotenv").config(); 5 | 6 | const database_name = "counter_app"; 7 | 8 | exports.dbConnect = () => { 9 | mongoose 10 | .connect(process.env.MONGODB_URL, { 11 | useNewUrlParser: true, 12 | useUnifiedTopology: true, 13 | }) 14 | .then(() => { 15 | console.log("Db connect successfully"); 16 | }) 17 | .catch((err) => { 18 | console.log("Error in db connection"); 19 | }); 20 | }; 21 | -------------------------------------------------------------------------------- /backend/config/passport.js: -------------------------------------------------------------------------------- 1 | const passport = require("passport"); 2 | const GoogleStrategy = require("passport-google-oauth20").Strategy; 3 | const User = require("../models/User"); 4 | 5 | passport.use( 6 | new GoogleStrategy( 7 | { 8 | clientID: process.env.GOOGLE_CLIENT_ID, 9 | clientSecret: process.env.GOOGLE_CLIENT_SECRET, 10 | callbackURL: "http://localhost:5000/auth/google/callback", 11 | }, 12 | async (accessToken, refreshToken, profile, done) => { 13 | try { 14 | let user = await User.findOne({ googleId: profile.id }); 15 | if (!user) { 16 | user = await User.create({ 17 | googleId: profile.id, 18 | name: profile.displayName, 19 | email: profile.emails[0].value, 20 | avatar: profile.photos[0].value, 21 | }); 22 | } 23 | done(null, user); 24 | } catch (err) { 25 | done(err, null); 26 | } 27 | } 28 | ) 29 | ); 30 | 31 | passport.serializeUser((user, done) => done(null, user.id)); 32 | passport.deserializeUser((id, done) => { 33 | User.findById(id, (err, user) => done(err, user)); 34 | }); 35 | -------------------------------------------------------------------------------- /backend/controllers/authController.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require("bcrypt"); 2 | const jwt = require("jsonwebtoken"); 3 | const User = require("../models/User"); 4 | 5 | async function signup(req, res) { 6 | try { 7 | const { firstName, lastName, email, password, confirmPassword } = req.body; 8 | 9 | if (!firstName || !lastName || !email || !password || !confirmPassword) { 10 | return res.status(403).send({ 11 | success: false, 12 | message: "All Fields are required", 13 | }); 14 | } 15 | 16 | if (!email.includes("@gmail.com")) { 17 | return res.status(400).send({ 18 | success: false, 19 | message: "Invalid Email!", 20 | }); 21 | } 22 | 23 | if (password !== confirmPassword) { 24 | return res.status(400).json({ 25 | success: false, 26 | message: 27 | "Password and Confirm Password do not match. Please try again.", 28 | }); 29 | } 30 | 31 | const existingUser = await User.findOne({ email }); 32 | if (existingUser) { 33 | return res.status(400).json({ 34 | success: false, 35 | message: "User already exists. Please login to continue.", 36 | }); 37 | } 38 | 39 | const hashedPassword = await bcrypt.hash(password, 10); 40 | 41 | const user = await User.create({ 42 | firstName, 43 | lastName, 44 | email, 45 | password: hashedPassword, 46 | image: `https://api.dicebear.com/5.x/initials/svg?seed=${firstName} ${lastName}`, 47 | }); 48 | 49 | return res.status(200).json({ 50 | success: true, 51 | user, 52 | message: "User registered successfully!", 53 | }); 54 | } catch (error) { 55 | console.log(error.message); 56 | return res.status(500).json({ 57 | success: false, 58 | message: "User cannot be registered, Please try again!", 59 | }); 60 | } 61 | } 62 | 63 | async function login(req, res) { 64 | try { 65 | const { email, password } = req.body; 66 | 67 | if (!email || !password) { 68 | return res.status(400).json({ 69 | success: false, 70 | message: `All fields are required!`, 71 | }); 72 | } 73 | 74 | const user = await User.findOne({ email }); 75 | 76 | if (!user) { 77 | return res.status(401).json({ 78 | success: false, 79 | message: `User is not Registered with Us. Please SignUp to Continue.`, 80 | }); 81 | } 82 | 83 | if (await bcrypt.compare(password, user.password)) { 84 | const token = jwt.sign( 85 | { 86 | email: user.email, 87 | id: user._id, 88 | }, 89 | process.env.JWT_SECRET, 90 | { 91 | expiresIn: "24h", 92 | } 93 | ); 94 | 95 | user._doc.token = token; 96 | user._doc.password = undefined; 97 | 98 | const options = { 99 | expires: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000), 100 | httpOnly: true, 101 | }; 102 | 103 | res.cookie("token", token, options).status(200).json({ 104 | success: true, 105 | token, 106 | user, 107 | message: `User Login Successfully!`, 108 | }); 109 | } else { 110 | return res.status(401).json({ 111 | success: false, 112 | message: `Password is incorrect`, 113 | }); 114 | } 115 | } catch (error) { 116 | console.error(error); 117 | return res.status(500).json({ 118 | success: false, 119 | message: `Login Failure, Please Try Again!`, 120 | }); 121 | } 122 | } 123 | 124 | async function logout(req, res) { 125 | try { 126 | console.log("req.user:", req.user); 127 | await User.findByIdAndUpdate( 128 | req.user._id, 129 | { 130 | $unset: { 131 | token: 1, 132 | }, 133 | }, 134 | { 135 | new: true, 136 | } 137 | ); 138 | 139 | const options = { 140 | httpOnly: true, 141 | secure: true, 142 | }; 143 | 144 | res 145 | .status(200) 146 | .clearCookie("token", options) 147 | .json({ message: "User logged out successfully" }); 148 | } catch (error) { 149 | res.status(500).json({ error: "An error occurred during logout" }); 150 | } 151 | } 152 | 153 | function googleCallback(req, res) { 154 | res.redirect("/profile"); 155 | } 156 | 157 | module.exports = { 158 | signup, 159 | login, 160 | logout, 161 | googleCallback, 162 | }; 163 | -------------------------------------------------------------------------------- /backend/controllers/othercontrollers.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer'); 2 | 3 | exports.sendFeedbackEmail = async (req, res) => { 4 | const { name, email, message, rating } = req.body; // Capture rating from the request 5 | console.log("Sending email with the following details:", { name, email, message, rating }); 6 | // Ensure all required fields are provided 7 | if (!name || !email || !message || rating === undefined) { 8 | console.log("Sending email with the following details 2:", { name, email, message, rating }); 9 | return res.status(400).json({ success: false, message: 'All fields including rating are required' }); 10 | } 11 | 12 | try { 13 | console.log("Sending email with the following details 3:", { name, email, message, rating }); 14 | const transporter = nodemailer.createTransport({ 15 | service: 'gmail', 16 | auth: { 17 | user: process.env.SMTP_EMAIL, 18 | pass: process.env.SMTP_PASSWORD, 19 | }, 20 | }); 21 | 22 | // Generate stars for rating 23 | const totalStars = 5; 24 | const filledStars = '★'.repeat(rating); // Filled stars based on rating 25 | const emptyStars = '☆'.repeat(totalStars - rating); // Empty stars for the rest 26 | const starDisplay = `

${filledStars}${emptyStars}

`; 27 | 28 | const mailOptions = { 29 | from: `"${name}" <${email}>`, // Display sender's name and email 30 | to: process.env.MAIL, // Make sure RESPONSE_MAIL is set in the .env 31 | subject: `Feedback from ${name}`, 32 | text: message, 33 | html: ` 34 |

You have received a new message from the Feedback form:

35 |

Contact Details:

36 | 40 |

Message:

41 |

${message}

42 |

Rating:

43 | ${starDisplay} 44 | `, // HTML formatted message body with rating stars 45 | }; 46 | 47 | // Send the email 48 | await transporter.sendMail(mailOptions); 49 | 50 | // Respond with success if email is sent 51 | return res.status(200).json({ success: true, message: 'Feedback sent successfully!' }); 52 | } catch (error) { 53 | console.error('Error sending feedback email:', error); 54 | return res.status(500).json({ success: false, message: 'Error sending feedback email' }); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /backend/controllers/subscribeController.js: -------------------------------------------------------------------------------- 1 | 2 | const nodemailer = require('nodemailer'); 3 | const Newsletters = require('../models/Newsletters'); 4 | const transporter = nodemailer.createTransport({ 5 | service: 'gmail', // Or your preferred email service 6 | auth: { 7 | user: process.env.SMTP_EMAIL, // Your email address 8 | pass: process.env.SMTP_PASSWORD, // Your email password or app-specific password 9 | }, 10 | }); 11 | exports.subscribe = async (req, res) => { 12 | const { email} = req.body; 13 | try { 14 | 15 | const existingSubscription = await Newsletters.findOne({ email }); 16 | if (existingSubscription) { 17 | return res.status(400).json({ message: 'This email is already subscribed.' }); 18 | } 19 | 20 | // Step 2: Save the new subscription to the database 21 | const newSubscription = new Newsletters({ email }); 22 | await newSubscription.save(); 23 | 24 | const mailOptions = { 25 | from: process.env.SMTP_EMAIL, 26 | to: email, 27 | subject: 'Subscription Confirmation', 28 | html: ` 29 |

Thank you for subscribing!

30 |

You have successfully subscribed to our platform.

31 |

Stay tuned for updates and exclusive offers!

32 | 34 | Explore More 35 | 36 |

Best Regards,
Hiring Portal

37 | `, 38 | }; 39 | 40 | await transporter.sendMail(mailOptions); 41 | 42 | res.status(200).json({ message: 'Subscription successful. Confirmation email sent.' }); 43 | } catch (error) { 44 | console.error('Subscription error:', error); 45 | res.status(500).json({ message: 'Subscription failed.' }); 46 | } 47 | } -------------------------------------------------------------------------------- /backend/controllers/todoController.js: -------------------------------------------------------------------------------- 1 | const Todo = require("../models/Todo"); 2 | 3 | async function addTodo(req, res) { 4 | try { 5 | const { task, userId } = req.body; 6 | 7 | console.log(req.body); 8 | if (!task || !userId) { 9 | return res.status(403).send({ 10 | success: false, 11 | message: "All Fields are required", 12 | }); 13 | } 14 | const newTodo = await Todo.create({ 15 | task, 16 | userId, 17 | }); 18 | 19 | return res.status(200).json({ 20 | success: true, 21 | newTodo, 22 | message: "Todo added successfully!", 23 | }); 24 | } catch (error) { 25 | console.log(error.message); 26 | return res.status(500).json({ 27 | success: false, 28 | message: "Cannot add todo", 29 | }); 30 | } 31 | } 32 | 33 | async function getTodos(req, res) { 34 | try { 35 | const { userId } = req.params; 36 | 37 | // Find todos that match the provided userId 38 | const todos = await Todo.find({ userId }); 39 | 40 | if (!todos || todos.length === 0) { 41 | return res.status(404).json({ 42 | success: false, 43 | message: "No todos found for this user", 44 | }); 45 | } 46 | 47 | return res.status(200).json({ 48 | success: true, 49 | todos, 50 | }); 51 | } catch (error) { 52 | console.log(error.message); 53 | return res.status(500).json({ 54 | success: false, 55 | message: "Error fetching todos", 56 | }); 57 | } 58 | } 59 | 60 | async function deleteTodo(req, res) { 61 | try { 62 | const todoId = req.params.id; 63 | 64 | const deletedTodo = await Todo.findByIdAndDelete(todoId); 65 | 66 | if (!deletedTodo) { 67 | return res.status(404).json({ message: "Todo not found" }); 68 | } 69 | 70 | return res.status(200).json({ message: "Todo deleted successfully" }); 71 | } catch (error) { 72 | console.error(error); 73 | return res 74 | .status(500) 75 | .json({ message: "An error occurred while deleting the todo" }); 76 | } 77 | } 78 | 79 | module.exports = { 80 | addTodo, 81 | getTodos, 82 | deleteTodo, 83 | }; 84 | -------------------------------------------------------------------------------- /backend/index.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | // Importing necessary modules and packages 4 | const express = require("express"); 5 | const mongoose = require("mongoose"); 6 | const app = express(); 7 | const database = require("./config/database"); 8 | const passport = require("passport"); 9 | const session = require("express-session"); 10 | require("./passport"); // Import passport configuration 11 | const cookieParser = require("cookie-parser"); 12 | const cors = require("cors"); 13 | const { sendFeedbackEmail } = require("./controllers/othercontrollers"); 14 | const { subscribe } = require("./controllers/subscribeController"); 15 | // Loading environment variables from .env file 16 | require("dotenv").config(); 17 | const authRoutes = require("./routes/authRoutes"); 18 | const todoRoutes = require("./routes/todoRoutes"); 19 | const googleauth = require("./routes/googleauth"); 20 | 21 | // Visitor Schema 22 | const visitorSchema = new mongoose.Schema({ 23 | count: { type: Number, default: 0 } 24 | }); 25 | 26 | const Visitor = mongoose.model("Visitor", visitorSchema); 27 | 28 | // Setting up port number 29 | const PORT = process.env.PORT || 4000; 30 | 31 | // Connecting to database 32 | database.dbConnect(); 33 | 34 | // Middlewares 35 | app.use(express.json()); 36 | app.use(express.urlencoded({ extended: true })); 37 | app.use(cookieParser()); 38 | app.use( 39 | cors({ 40 | origin: ["http://localhost:5173", "http://localhost:3000"], 41 | credentials: true, 42 | }) 43 | ); 44 | 45 | app.use( 46 | session({ 47 | secret: process.env.JWT_SECRET, 48 | resave: false, 49 | saveUninitialized: true, 50 | }) 51 | ); 52 | 53 | require("./config/passport"); 54 | app.use(passport.initialize()); 55 | app.use(passport.session()); 56 | 57 | // Setting up routes 58 | app.use("/api/v1/auth", authRoutes); 59 | app.use("/api/v1/todo", todoRoutes); 60 | app.use("/auth", googleauth) 61 | 62 | // Testing the server 63 | app.get("/", (req, res) => { 64 | return res.json({ 65 | success: true, 66 | message: "Your server is up and running ...", 67 | }); 68 | }); 69 | 70 | app.post("/contact", sendFeedbackEmail); 71 | app.post("/subscribe", subscribe); 72 | 73 | // Listening to the server 74 | app.listen(PORT, () => { 75 | console.log(`App is listening at ${PORT}`); 76 | }); 77 | 78 | // Endpoint to get visitor count 79 | // Import Node's `crypto` module to create a unique ID for each visitor 80 | const crypto = require("crypto"); 81 | 82 | // Create a temporary set to store recent visitor IDs 83 | const recentVisitors = new Set(); 84 | 85 | app.get("/api/visitor-count", async (req, res) => { 86 | // Generate a unique ID based on the visitor's IP and browser information 87 | const visitorId = crypto 88 | .createHash("md5") 89 | .update(req.ip + req.headers["user-agent"]) 90 | .digest("hex"); 91 | 92 | // If the visitor has already been counted recently, skip incrementing 93 | if (recentVisitors.has(visitorId)) { 94 | const visitor = await Visitor.findOne(); 95 | return res.json({ count: visitor.count }); 96 | } 97 | 98 | // Add the visitor to the recentVisitors set 99 | recentVisitors.add(visitorId); 100 | 101 | // Remove the visitor from the cache after 30 seconds to allow counting later if they revisit 102 | setTimeout(() => { 103 | recentVisitors.delete(visitorId); 104 | }, 3000); // 3 seconds 105 | 106 | // Increment visitor count 107 | let visitor = await Visitor.findOne(); 108 | if (!visitor) { 109 | visitor = new Visitor({ count: 1 }); 110 | } else { 111 | visitor.count += 1; 112 | } 113 | await visitor.save(); 114 | 115 | // Send the updated count to the client 116 | res.json({ count: visitor.count }); 117 | }); 118 | 119 | // End of code. 120 | -------------------------------------------------------------------------------- /backend/middlewares/auth.middleware.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const User = require("../models/User.js"); 3 | 4 | const verifyJWT = async (req, res, next) => { 5 | try { 6 | const token = 7 | req.cookies?.token || req.header("Authorization")?.replace("Bearer ", ""); 8 | 9 | if (!token) { 10 | return res.status(401).json({ message: "Unauthorized request" }); 11 | } 12 | 13 | const decodedToken = jwt.verify(token, process.env.JWT_SECRET); 14 | 15 | const user = await User.findById(decodedToken?.id); 16 | 17 | if (!user) { 18 | return res.status(401).json({ message: "Invalid Access Token" }); 19 | } 20 | 21 | req.user = user; 22 | next(); 23 | } catch (error) { 24 | return res 25 | .status(401) 26 | .json({ message: error?.message || "Invalid access token" }); 27 | } 28 | }; 29 | 30 | module.exports = { verifyJWT }; 31 | -------------------------------------------------------------------------------- /backend/models/Newsletters.js: -------------------------------------------------------------------------------- 1 | // models/Subscription.js 2 | const mongoose = require('mongoose'); 3 | 4 | const Newsletter = new mongoose.Schema({ 5 | email: { 6 | type: String, 7 | required: true, 8 | unique: true, // Ensures no duplicate emails 9 | match: /.+\@.+\..+/ // Basic regex to validate email format 10 | }, 11 | subscribedAt: { 12 | type: Date, 13 | default: Date.now 14 | } 15 | }); 16 | 17 | module.exports = mongoose.model('Subscription', Newsletter); 18 | -------------------------------------------------------------------------------- /backend/models/Todo.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const todoSchema = new mongoose.Schema({ 4 | task: { 5 | type: String, 6 | required: true, 7 | trim: true, 8 | }, 9 | userId: { 10 | type: mongoose.Schema.Types.ObjectId, 11 | ref: "User", 12 | required: true, 13 | }, 14 | }); 15 | 16 | module.exports = mongoose.model("Todo", todoSchema); 17 | -------------------------------------------------------------------------------- /backend/models/User.js: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | // Import the Mongoose library 4 | const mongoose = require("mongoose"); 5 | 6 | // Define the user schema using the Mongoose Schema constructor 7 | const userSchema = new mongoose.Schema( 8 | { 9 | // Define the name field with type String, required, and trimmed 10 | firstName: { 11 | type: String, 12 | required: true, 13 | trim: true, 14 | }, 15 | lastName: { 16 | type: String, 17 | required: true, 18 | trim: true, 19 | }, 20 | // Define the email field with type String, required, and trimmed 21 | email: { 22 | type: String, 23 | required: true, 24 | trim: true, 25 | }, 26 | 27 | // Define the password field with type String and required 28 | password: { 29 | type: String, 30 | required: true, 31 | }, 32 | token: { 33 | type: String, 34 | }, 35 | image: { 36 | type: String, 37 | required: true, 38 | }, 39 | googleId: { 40 | type: String, 41 | unique: true, 42 | sparse: true, // This allows either Google login or email/password login 43 | } 44 | 45 | }, 46 | { timestamps: true } 47 | ); 48 | 49 | // Export the Mongoose model for the user schema, using the name "user" 50 | module.exports = mongoose.model("user", userSchema); 51 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "start": "node index.js", 7 | "dev": "nodemon index.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "backend": "file:", 14 | "bcrypt": "^5.1.1", 15 | "cookie-parser": "^1.4.6", 16 | "cors": "^2.8.5", 17 | "dotenv": "^16.4.5", 18 | "express": "^4.21.1", 19 | "express-session": "^1.18.1", 20 | "jsonwebtoken": "^9.0.2", 21 | "mongoose": "^8.7.0", 22 | "nodemailer": "^6.9.15", 23 | "nodemon": "^3.1.7", 24 | "passport": "^0.7.0", 25 | "passport-google-oauth20": "^2.0.0" 26 | }, 27 | "devDependencies": {}, 28 | "description": "" 29 | } 30 | -------------------------------------------------------------------------------- /backend/passport.js: -------------------------------------------------------------------------------- 1 | const passport = require("passport"); 2 | const GoogleStrategy = require("passport-google-oauth20").Strategy; 3 | const User = require("./models/User"); // Import your User model 4 | 5 | passport.use( 6 | new GoogleStrategy( 7 | { 8 | clientID: process.env.GOOGLE_CLIENT_ID, 9 | clientSecret: process.env.GOOGLE_CLIENT_SECRET, 10 | callbackURL: "/auth/google/callback", 11 | }, 12 | async (accessToken, refreshToken, profile, done) => { 13 | try { 14 | // Check if user already exists 15 | let user = await User.findOne({ googleId: profile.id }); 16 | if (user) { 17 | return done(null, user); 18 | } 19 | 20 | // If user doesn't exist, create a new one 21 | user = new User({ 22 | firstName: profile.name.givenName, 23 | lastName: profile.name.familyName, 24 | email: profile.emails[0].value, 25 | googleId: profile.id, 26 | image: profile.photos[0].value, 27 | }); 28 | await user.save(); 29 | done(null, user); 30 | } catch (error) { 31 | done(error, false); 32 | } 33 | } 34 | ) 35 | ); 36 | 37 | // Serialize user information into session 38 | passport.serializeUser((user, done) => done(null, user.id)); 39 | 40 | // Deserialize user information from session 41 | passport.deserializeUser(async (id, done) => { 42 | const user = await User.findById(id); 43 | done(null, user); 44 | }); 45 | -------------------------------------------------------------------------------- /backend/routes/authRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const passport = require("passport"); 3 | const { 4 | signup, 5 | login, 6 | logout, 7 | googleCallback, 8 | } = require("../controllers/authController"); 9 | const { verifyJWT } = require("../middlewares/auth.middleware"); 10 | 11 | const router = express.Router(); 12 | 13 | router.post("/signup", signup); 14 | router.post("/login", login); 15 | router.get("/logout", verifyJWT, logout); 16 | router.get( 17 | "/google", 18 | passport.authenticate("google", { scope: ["profile", "email"] }) 19 | ); 20 | router.get( 21 | "/google/callback", 22 | passport.authenticate("google", { failureRedirect: "/login" }), 23 | googleCallback 24 | ); 25 | 26 | module.exports = router; 27 | -------------------------------------------------------------------------------- /backend/routes/googleauth.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const passport = require("passport"); 3 | 4 | const router = express.Router(); 5 | 6 | // Route to initiate Google authentication 7 | router.get( 8 | "/google", 9 | passport.authenticate("google", { scope: ["profile", "email"] }) 10 | ); 11 | 12 | // Google OAuth callback route 13 | router.get( 14 | "/google/callback", 15 | passport.authenticate("google", { failureRedirect: "/" }), 16 | (req, res) => { 17 | // Redirect to a page after successful login (e.g., dashboard) 18 | res.redirect("/dashboard"); 19 | } 20 | ); 21 | 22 | // Logout route 23 | router.get("/logout", (req, res) => { 24 | req.logout(err => { 25 | if (err) { 26 | console.error(err); 27 | return res.redirect("/"); // Redirect to home on error 28 | } 29 | res.redirect("/"); // Redirect to home after logout 30 | }); 31 | }); 32 | 33 | module.exports = router; 34 | -------------------------------------------------------------------------------- /backend/routes/todoRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { addTodo, getTodos, deleteTodo } = require("../controllers/todoController"); 3 | const { verifyJWT } = require("../middlewares/auth.middleware"); 4 | 5 | const router = express.Router(); 6 | 7 | router.post("/add", verifyJWT, addTodo); 8 | router.get("/:userId", verifyJWT, getTodos); 9 | router.delete("/:id", verifyJWT, deleteTodo); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /backend/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "index.js", 6 | "use": "@vercel/node", 7 | "config": { "includeFiles": ["dist/**"] } 8 | } 9 | ], 10 | "routes": [ 11 | { 12 | "src": "/(.*)", 13 | "dest": "index.js" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": false, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import react from 'eslint-plugin-react' 4 | import reactHooks from 'eslint-plugin-react-hooks' 5 | import reactRefresh from 'eslint-plugin-react-refresh' 6 | 7 | export default [ 8 | { ignores: ['dist'] }, 9 | { 10 | files: ['**/*.{js,jsx}'], 11 | languageOptions: { 12 | ecmaVersion: 2020, 13 | globals: globals.browser, 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | ecmaFeatures: { jsx: true }, 17 | sourceType: 'module', 18 | }, 19 | }, 20 | settings: { react: { version: '18.3' } }, 21 | plugins: { 22 | react, 23 | 'react-hooks': reactHooks, 24 | 'react-refresh': reactRefresh, 25 | }, 26 | rules: { 27 | ...js.configs.recommended.rules, 28 | ...react.configs.recommended.rules, 29 | ...react.configs['jsx-runtime'].rules, 30 | ...reactHooks.configs.recommended.rules, 31 | 'react/jsx-no-target-blank': 'off', 32 | 'react/prop-types':'off', 33 | 'react-refresh/only-export-components': [ 34 | 'warn', 35 | { allowConstantExport: true }, 36 | ], 37 | }, 38 | }, 39 | ] 40 | -------------------------------------------------------------------------------- /folder_structure.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/folder_structure.txt -------------------------------------------------------------------------------- /footer.css: -------------------------------------------------------------------------------- 1 | footer { 2 | background-color: #05365f; 3 | color: #f6f0f0; 4 | text-align: center; 5 | position: fixed; 6 | bottom: 0; 7 | width: 100%; 8 | padding: 10px 0; 9 | } 10 | 11 | .footer-content { 12 | justify-content: space-between; 13 | align-items: center; 14 | max-width: 1200px; 15 | margin: 0 auto; 16 | padding: 0 20px; 17 | } 18 | 19 | .footer-nav { 20 | list-style-type: none; 21 | display:flexbox; 22 | gap: 60px; 23 | margin: 20; 24 | padding:0; 25 | } 26 | .footer-nav a { 27 | color: #ccc; 28 | text-decoration: none; 29 | font-weight: 500; 30 | gap: 20px; 31 | } 32 | 33 | .footer-nav a:hover { 34 | text-decoration: underline; 35 | gap:30px; 36 | } 37 | 38 | .footer-copy { 39 | font-size: 0.9em; 40 | color: #ccc; 41 | } 42 | 43 | body { 44 | margin:0; 45 | min-height: 100vh; 46 | } 47 | html { 48 | scroll-behavior: smooth; 49 | } 50 | /* Footer content styling */ 51 | .footer-content { 52 | background-color: #333; /* Background color of footer */ 53 | color: #fff; /* Text color */ 54 | padding: 20px; 55 | text-align: center; 56 | } 57 | 58 | /* Footer navigation bar styling */ 59 | .footer-nav { 60 | list-style: none; /* Removes bullets from the list */ 61 | padding: 0; /* Removes padding */ 62 | margin: 0; /* Removes margin */ 63 | display: flex; /* Flexbox for horizontal layout */ 64 | justify-content: center; /* Centers the navigation links */ 65 | } 66 | 67 | /* Footer nav links */ 68 | .footer-nav li { 69 | margin: 0 15px; /* Space between links */ 70 | } 71 | 72 | .footer-nav a { 73 | color: #fff; /* Link color */ 74 | text-decoration: none; /* Removes underlines */ 75 | font-size: 16px; 76 | font-weight: bold; 77 | padding: 5px 10px; 78 | border-radius: 5px; 79 | transition: background-color 0.3s ease; /* Adds a transition effect for hover */ 80 | } 81 | 82 | /* Hover effect for navigation links */ 83 | .footer-nav a:hover { 84 | background-color: #555; /* Change background on hover */ 85 | color: #ffcc00; /* Change text color on hover */ 86 | } 87 | 88 | /* Footer copyright text styling */ 89 | .footer-copy { 90 | margin-top: 20px; 91 | font-size: 14px; 92 | color: #ccc; /* Lighter text color for copyright info */ 93 | } 94 | 95 | /* Section styling */ 96 | section { 97 | padding: 60px 20px; /* Adds space inside the section */ 98 | margin: 40px 0; /* Adds space between sections */ 99 | background-color: #f9f9f9; /* Background color for the section */ 100 | border-radius: 10px; /* Rounded corners for the section */ 101 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Subtle shadow for depth */ 102 | } 103 | 104 | /* Section titles (h1) styling */ 105 | section h1 { 106 | font-size: 28px; 107 | color: #333; 108 | text-align: center; /* Centers the title */ 109 | margin-bottom: 20px; 110 | } 111 | 112 | /* Section paragraph (p) styling */ 113 | section p { 114 | font-size: 16px; 115 | color: #666; 116 | text-align: center; /* Centers the text */ 117 | line-height: 1.6; 118 | } 119 | 120 | /* Media query for smaller screens */ 121 | @media (max-width: 768px) { 122 | .footer-nav { 123 | flex-direction: column; /* Stacks the navigation links vertically */ 124 | } 125 | 126 | .footer-nav li { 127 | margin: 10px 0; /* Adds space between stacked links */ 128 | } 129 | 130 | section { 131 | padding: 40px 10px; /* Reduce padding for smaller screens */ 132 | } 133 | 134 | section h1 { 135 | font-size: 24px; /* Adjust title size */ 136 | } 137 | 138 | section p { 139 | font-size: 14px; /* Adjust paragraph size */ 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /footer.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/footer.html -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Counter App 10 | 11 | 12 | 15 | 16 | 43 | 44 | 65 | 66 |
67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // ... 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": [ 7 | "./src/*" 8 | ] 9 | } 10 | // ... 11 | } 12 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter-project", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "start": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview", 11 | "server": "cd backend && npm run dev", 12 | "dev": "concurrently -n \"client,server\" -c \"bgBlue,bgYellow\" \"npm start\" \"npm run server\"", 13 | "predeploy": "npm run build", 14 | "deploy": "gh-pages -d build" 15 | }, 16 | "dependencies": { 17 | "@chakra-ui/react": "^2.10.1", 18 | "@fortawesome/fontawesome-svg-core": "^6.6.0", 19 | "@fortawesome/free-brands-svg-icons": "^6.6.0", 20 | "@fortawesome/free-solid-svg-icons": "^6.6.0", 21 | "@fortawesome/react-fontawesome": "^0.2.2", 22 | "@radix-ui/react-scroll-area": "^1.1.0", 23 | "@radix-ui/react-slot": "^1.1.0", 24 | "@react-oauth/google": "^0.12.1", 25 | "@reduxjs/toolkit": "^2.2.8", 26 | "@tsparticles/react": "^3.0.0", 27 | "@tsparticles/slim": "^3.5.0", 28 | "axios": "^1.7.7", 29 | "bcrypt": "^5.1.1", 30 | "class-variance-authority": "^0.7.0", 31 | "clsx": "^2.1.1", 32 | "concurrently": "^9.0.1", 33 | "counter-project": "file:", 34 | "dotenv": "^16.4.5", 35 | "express-session": "^1.18.1", 36 | "framer-motion": "^11.11.8", 37 | "jsonwebtoken": "^9.0.2", 38 | "lucide-react": "^0.447.0", 39 | "moment": "^2.30.1", 40 | "moment-timezone": "^0.5.45", 41 | "mongoose": "^8.7.2", 42 | "next-themes": "^0.3.0", 43 | "passport": "^0.7.0", 44 | "passport-google-oauth20": "^2.0.0", 45 | "react": "^18.3.1", 46 | "react-clock": "^5.0.0", 47 | "react-dom": "^18.3.1", 48 | "react-hot-toast": "^2.4.1", 49 | "react-icons": "^5.3.0", 50 | "react-redux": "^9.1.2", 51 | "react-router-dom": "^6.26.2", 52 | "react-toastify": "^10.0.6", 53 | "recharts": "^2.13.0", 54 | "styled-components": "^6.1.13", 55 | "tailwind-merge": "^2.5.2", 56 | "tailwindcss-animate": "^1.0.7", 57 | "tsparticles": "^3.5.0", 58 | "yup": "^1.4.0" 59 | }, 60 | "devDependencies": { 61 | "@eslint/js": "^9.9.0", 62 | "@shadcn/ui": "^0.0.4", 63 | "@tailwindcss/aspect-ratio": "^0.4.2", 64 | "@tailwindcss/forms": "^0.5.9", 65 | "@tailwindcss/typography": "^0.5.15", 66 | "@types/react": "^18.3.3", 67 | "@types/react-dom": "^18.3.0", 68 | "@vitejs/plugin-react": "^4.3.1", 69 | "autoprefixer": "^10.4.20", 70 | "eslint": "^9.9.0", 71 | "eslint-plugin-react": "^7.35.0", 72 | "eslint-plugin-react-hooks": "^5.1.0-rc.0", 73 | "eslint-plugin-react-refresh": "^0.4.9", 74 | "globals": "^15.9.0", 75 | "postcss": "^8.4.45", 76 | "tailwindcss": "^3.4.10", 77 | "vite": "^5.4.8", 78 | "vite-plugin-babel": "^1.2.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /preloader.js: -------------------------------------------------------------------------------- 1 | // /preloader js styling 2 | 3 | let preloader = document.querySelector("#preloader"); 4 | 5 | window.addEventListener("load",function(e){ 6 | 7 | preloader.style.display = "none"; 8 | 9 | }); -------------------------------------------------------------------------------- /public/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/public/bg.jpg -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/wallpaperflare.com_wallpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/public/wallpaperflare.com_wallpaper.jpg -------------------------------------------------------------------------------- /readme1.md: -------------------------------------------------------------------------------- 1 | ## Important Guidelines ⚡ 2 | 3 | 1. Contributors should only work on issues that have been assigned to them. 4 | 2. Each pull request should be associated with one issue only. 5 | 3. No minor text edits should be submitted unless necessary. 6 | 4. Unethical behavior, tampering with files, or harassment will result in disqualification. 7 | 5. Follow the community guidelines while contributing to ensure a healthy collaborative environment. 8 | 6. No Issue Repetitions are allowed. 9 | 7. Check the issues before you raise an issue. 10 | 8. No Plagiarism of Codes. 11 | 12 | ## Community Guidelines 🤝 13 | 14 | Please follow these guidelines while contributing: 15 | 16 | - Be respectful and considerate towards others. 17 | - Use inclusive language and foster a welcoming environment. 18 | - Avoid personal attacks, harassment, or discrimination. 19 | - Keep discussions focused on constructive topics. 20 | 21 | - ## Code Reviews ✅ 22 | 23 | - Be open to feedback from other contributors. 24 | - Participate in code reviews to help improve the project. 25 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | /* Custom scrollbar styles for Chrome, Edge, Safari */ 2 | ::-webkit-scrollbar { 3 | width: 12px; /* width for vertical scrollbar */ 4 | height: 12px; /* height for horizontal scrollbar */ 5 | } 6 | 7 | ::-webkit-scrollbar-track { 8 | background: #f1f1f1; /* Track background */ 9 | border-radius: 10px; /* Roundness of the track */ 10 | } 11 | 12 | ::-webkit-scrollbar-thumb { 13 | background-color: #a6f1f3; /* Scrollbar thumb color */ 14 | border-radius: 20px; /* Increase roundness for thumb */ 15 | border: 3px solid #f1f1f1; /* Space around thumb to create a more rounded appearance */ 16 | } 17 | 18 | /* Hover effect */ 19 | ::-webkit-scrollbar-thumb:hover { 20 | background-color: #555; /* Darken on hover */ 21 | } 22 | 23 | /* Firefox */ 24 | * { 25 | scrollbar-width: thin; 26 | scrollbar-color: #8ca0bb #f1f1f1; 27 | } 28 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; 3 | import WorldClockPage from "./pages/WorldClockPage"; 4 | // import SignUpPage from "./pages/SignUpPage"; 5 | import TimerPage from "./pages/TimerPage"; 6 | import AutoCounterPage from "./pages/AutoCounterPage"; 7 | import Design from "./components/Design"; 8 | import Counter from "./pages/Counter"; 9 | import Contributors from "./pages/Contributors"; 10 | import Feedback from "./pages/Feedback"; 11 | import PrivacyPolicy from "./pages/PrivacyPolicy"; 12 | import Footer from "./components/Footer.jsx"; 13 | import SpotifyPlayer from "./components/SpotifyPlayer"; 14 | import About from "./components/About"; 15 | import './App.css'; 16 | 17 | // import AboutPage from './pages/AboutPage'; // Import About page 18 | // import HistoryPage from './pages/HistoryPage'; // Import History page 19 | // import SettingsPage from './pages/SettingsPage'; // Import Settings page 20 | // import ThemesPage from './pages/ThemesPage'; // Import Themes page 21 | // import Footer from './Footer'; // Import the Footer component 22 | import Template from "./components/Auth/Template"; 23 | import WorkTracker from "./pages/WorkTracker"; 24 | import TermsPage from "./pages/TermsPage"; 25 | import Error404 from "./pages/Error404"; 26 | import PasswordRecovery from "./components/Auth/PasswordRecovery"; 27 | import Todo from "./pages/Todo"; 28 | 29 | const App = () => { 30 | return ( 31 | 32 |
33 | {/* Particles design will be displayed globally */} 34 | 35 | 36 | 37 | } /> 38 | } /> 39 | {/* } /> */} 40 | } /> 41 | } /> 42 | } /> 43 | } /> 44 | } /> 45 | } /> 46 | } /> 47 | } /> 48 | } /> 49 | } /> 50 | } /> 51 | {/* } /> */} 52 | } /> 53 | } /> 54 | } /> 55 | } /> 56 | } /> 57 | }/> 58 | } 61 | />{" "} 62 | {/* Add PasswordRecovery route */} 63 | } />{" "} 64 | {/* Add NotFoundPage route */} 65 | } /> 66 | } /> 67 | } /> 68 | } /> 69 | 70 |
72 |
73 | ); 74 | }; 75 | 76 | // Define the new sections at the bottom of App.jsx 77 | 78 | /*const AboutPage = () => { 79 | return ( 80 |
81 |

About Page

82 |

This is the About section of the Counter App, explaining its features and purpose.

83 |
84 | ); 85 | };*/ 86 | 87 | /*const HistoryPage = () => { 88 | return ( 89 |
90 |

History Page

91 |

This is the History section of the Counter App where you can review your past counter sessions.

92 |
93 | ); 94 | };*/ 95 | 96 | /*const SettingsPage = () => { 97 | return ( 98 |
99 |

Settings Page

100 |

This is the Settings section of the Counter App where you can configure app settings like notifications and time zones.

101 |
102 | ); 103 | };*/ 104 | 105 | /*const ThemesPage = () => { 106 | return ( 107 |
108 |

Themes Page

109 |

This is the Themes section of the Counter App where you can switch between light and dark modes.

110 |
111 | ); 112 | };*/ 113 | 114 | // Define the NotFoundPage component to display when no routes match 115 | const NotFoundPage = () => { 116 | return ( 117 |
118 |

404 - Page Not Found

119 |

Oops! The page you're looking for doesn't exist.

120 |
121 | ); 122 | }; 123 | 124 | } />; 125 | 126 | } />; 127 | 128 | export default App; 129 | -------------------------------------------------------------------------------- /src/assets/afternoonBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/afternoonBackground.png -------------------------------------------------------------------------------- /src/assets/afternoonBackground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/afternoonBackground.webp -------------------------------------------------------------------------------- /src/assets/clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/clock.png -------------------------------------------------------------------------------- /src/assets/clock.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/clock.webp -------------------------------------------------------------------------------- /src/assets/clock_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/clock_img.png -------------------------------------------------------------------------------- /src/assets/clock_img.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/clock_img.webp -------------------------------------------------------------------------------- /src/assets/counter-app-video-demo2 (1) (2).mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/counter-app-video-demo2 (1) (2).mp4 -------------------------------------------------------------------------------- /src/assets/dark.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/dark.jpg -------------------------------------------------------------------------------- /src/assets/dark.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/dark.webp -------------------------------------------------------------------------------- /src/assets/darklogo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/darklogo1.png -------------------------------------------------------------------------------- /src/assets/darklogo1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/darklogo1.webp -------------------------------------------------------------------------------- /src/assets/image_with_black_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/image_with_black_background.png -------------------------------------------------------------------------------- /src/assets/image_with_black_background.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/image_with_black_background.webp -------------------------------------------------------------------------------- /src/assets/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/login.png -------------------------------------------------------------------------------- /src/assets/login.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/login.webp -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/logo.webp -------------------------------------------------------------------------------- /src/assets/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/logo1.png -------------------------------------------------------------------------------- /src/assets/logo1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/logo1.webp -------------------------------------------------------------------------------- /src/assets/morningBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/morningBackground.png -------------------------------------------------------------------------------- /src/assets/morningBackground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/morningBackground.webp -------------------------------------------------------------------------------- /src/assets/nightBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/nightBackground.png -------------------------------------------------------------------------------- /src/assets/nightBackground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/nightBackground.webp -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/signup.png -------------------------------------------------------------------------------- /src/assets/signup.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/signup.webp -------------------------------------------------------------------------------- /src/assets/timer1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/timer1.png -------------------------------------------------------------------------------- /src/assets/timer1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/assets/timer1.webp -------------------------------------------------------------------------------- /src/components/Auth/LoginForm.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from "react"; 2 | import { AiOutlineEye, AiOutlineEyeInvisible } from "react-icons/ai"; 3 | import { useDispatch } from "react-redux"; 4 | import { useNavigate } from "react-router-dom"; 5 | import { loginValidation } from "@/validations/validation"; 6 | import { login } from "../../services/operations/authAPI"; 7 | 8 | function LoginForm() { 9 | const navigate = useNavigate(); 10 | const dispatch = useDispatch(); 11 | const [email, setEmail] = useState(""); 12 | const [password, setPassword] = useState(""); 13 | const [errors, setErrors] = useState({}); 14 | const [showPassword, setShowPassword] = useState(false); 15 | 16 | const handleOnSubmit = async (e) => { 17 | e.preventDefault(); 18 | 19 | try { 20 | await loginValidation.validate( 21 | { email, password }, 22 | { abortEarly: false } 23 | ); 24 | setErrors({}); 25 | } catch (error) { 26 | const newErrors = {}; 27 | error.inner.forEach((err) => { 28 | newErrors[err.path] = err.message; 29 | }); 30 | setErrors(newErrors); 31 | return; 32 | } 33 | 34 | dispatch(login(email, password, navigate)); 35 | }; 36 | 37 | const handlePasswordRecovery = () => { 38 | navigate("/password-recovery"); 39 | }; 40 | 41 | const togglePasswordVisibility = useCallback(() => { 42 | setShowPassword((prev) => !prev); 43 | }, []); 44 | 45 | return ( 46 |
47 |
52 | 67 | 94 | 100 | 107 | 108 |
109 |
110 | 111 | OR 112 | 113 |
114 |
115 |
116 | Do not have an account? 117 |
118 | 124 |
125 | 126 |
127 |
128 | 129 | 130 |
131 | ); 132 | } 133 | 134 | export default LoginForm; 135 | -------------------------------------------------------------------------------- /src/components/Auth/LoginImage.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { motion } from "framer-motion"; 3 | 4 | const LoginImage = () => { 5 | const [transform, setTransform] = useState({ rotateX: 0, rotateY: 0 }); 6 | 7 | const handleMouseMove = (e) => { 8 | const rect = e.currentTarget.getBoundingClientRect(); 9 | const xPos = (e.clientX - rect.left) / rect.width - 0.5; 10 | const yPos = (e.clientY - rect.top) / rect.height - 0.5; 11 | 12 | setTransform({ 13 | rotateX: yPos * 20, 14 | rotateY: -xPos * 20, 15 | }); 16 | }; 17 | 18 | const handleMouseLeave = () => { 19 | setTransform({ rotateX: 0, rotateY: 0 }); 20 | }; 21 | 22 | return ( 23 | 33 | 51 | 52 | ); 53 | }; 54 | 55 | export default LoginImage; 56 | -------------------------------------------------------------------------------- /src/components/Auth/PasswordRecovery.jsx: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import { useState } from "react"; 4 | import { useNavigate } from "react-router-dom"; 5 | import { useDispatch } from "react-redux"; 6 | import { passwordRecovery } from "../../services/operations/authAPI"; // Correct import 7 | import { FaEye, FaEyeSlash } from "react-icons/fa"; // Import icons 8 | 9 | const PasswordRecovery = () => { 10 | const navigate = useNavigate(); 11 | const dispatch = useDispatch(); 12 | const [email, setEmail] = useState(""); 13 | const [message, setMessage] = useState(""); 14 | 15 | const handleOnSubmit = async (e) => { 16 | e.preventDefault(); 17 | try { 18 | await dispatch(passwordRecovery(email)); 19 | setMessage( 20 | "Password recovery instructions have been sent to your email." 21 | ); 22 | } catch (error) { 23 | setMessage("Error sending password recovery instructions."); 24 | } 25 | }; 26 | 27 | // Tile-style glassmorphism effect 28 | const glassTileStyle = { 29 | background: "rgba(255, 255, 255, 0.1)", // More transparent glassy effect 30 | borderRadius: "20px", // Increased rounded corners for tile effect 31 | boxShadow: "0 8px 30px rgba(0, 0, 0, 0.2)", // Tile-like shadow 32 | backdropFilter: "blur(30px)", // Increased blur for glass effect 33 | border: "1px solid rgba(255, 255, 255, 0.3)", // Optional: border to enhance tile 34 | padding: "2rem", // Padding inside the form 35 | maxWidth: "420px", // Fixed max width to simulate a tile size 36 | width: "100%", 37 | margin: "0 auto", // Center the form horizontally 38 | }; 39 | 40 | return ( 41 |
42 |

43 | Password Recovery 44 |

45 | 46 |
51 |
52 | 58 | setEmail(e.target.value)} 63 | className='mt-1 block w-full p-2 border border-gray-300 rounded dark:border-gray-600 dark:bg-gray-700 dark:text-white focus:outline-none focus:border-blue-500' 64 | required 65 | /> 66 |
67 | 68 | 74 | 75 | {message &&
{message}
} 76 | 77 |
78 |

79 | Remember your password?{" "} 80 | navigate("/login")} 83 | > 84 | Sign In Here 85 | 86 |

87 |

navigate("/")} 90 | > 91 | Home 92 |

93 |
94 |
95 |
96 | ); 97 | }; 98 | 99 | export default PasswordRecovery; 100 | -------------------------------------------------------------------------------- /src/components/Auth/Review.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | 3 | const Review = () => { 4 | const [rating, setRating] = useState(0); 5 | const [hover, setHover] = useState(0); 6 | const [comment, setComment] = useState(''); 7 | const [visible, setVisible] = useState(false); 8 | 9 | // Show the component after 15 seconds 10 | useEffect(() => { 11 | const timer = setTimeout(() => { 12 | setVisible(true); 13 | }, 15000); 14 | return () => clearTimeout(timer); 15 | }, []); 16 | 17 | // Function to handle comment change 18 | const handleCommentChange = (e) => { 19 | setComment(e.target.value); 20 | }; 21 | 22 | // Function to handle form submit 23 | const handleSubmit = (e) => { 24 | e.preventDefault(); 25 | 26 | // Check if a rating is selected and a comment is provided 27 | if (rating === 0 || comment.trim() === '') { 28 | alert('Please provide both a rating and a comment before submitting.'); 29 | return; 30 | } 31 | alert(`We value your feedback. Thank you for rating us!`); 32 | 33 | // Clear the comment and rating after submission 34 | setComment(''); 35 | setRating(0); 36 | 37 | // Hide the review component after submission 38 | setVisible(false); 39 | }; 40 | 41 | return ( 42 | <> 43 | {visible && ( 44 |
45 | {/* Background overlay */} 46 |
47 | 48 | {/* Popup content */} 49 |
50 | {/* Close button */} 51 | 58 | 59 | 60 |

RATE US!

61 |

Please take a second to review our services!

62 | 63 | {/* Star Rating */} 64 |
65 | {[...Array(5)].map((_, index) => { 66 | const starValue = index + 1; 67 | return ( 68 | 83 | ); 84 | })} 85 |
86 | 87 | {/* Comment Box */} 88 |
89 | 96 | 97 | {/* Submit Button */} 98 | 104 |
105 |
106 |
107 | )} 108 | 109 | ); 110 | }; 111 | 112 | export default Review; 113 | -------------------------------------------------------------------------------- /src/components/Auth/SignupImage.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { motion } from "framer-motion"; 3 | 4 | const SignupImage = () => { 5 | const [transform, setTransform] = useState({ rotateX: 0, rotateY: 0 }); 6 | 7 | const handleMouseMove = (e) => { 8 | const rect = e.currentTarget.getBoundingClientRect(); 9 | const xPos = (e.clientX - rect.left) / rect.width - 0.5; 10 | const yPos = (e.clientY - rect.top) / rect.height - 0.5; 11 | 12 | setTransform({ 13 | rotateX: yPos * 20, 14 | rotateY: -xPos * 20, 15 | }); 16 | }; 17 | 18 | const handleMouseLeave = () => { 19 | setTransform({ rotateX: 0, rotateY: 0 }); 20 | }; 21 | 22 | return ( 23 | 33 | 51 | 52 | ); 53 | }; 54 | 55 | export default SignupImage; 56 | -------------------------------------------------------------------------------- /src/components/Auth/Template.jsx: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import { useSelector } from "react-redux"; 4 | import LoginForm from "./LoginForm"; 5 | import SignupForm from "./SignupForm"; 6 | import { FaArrowCircleLeft } from "react-icons/fa"; 7 | import { useNavigate } from "react-router-dom"; 8 | import SignupImage from "./SignupImage"; 9 | import LoginImage from "./LoginImage"; 10 | 11 | function Template({ formType }) { 12 | const { loading } = useSelector((state) => state.auth); 13 | const navigate = useNavigate(); 14 | 15 | return ( 16 |
17 |
18 | {loading ? ( 19 |
20 | ) : ( 21 |
22 |
navigate("/")} 25 | > 26 | navigate("/")} 29 | /> 30 |
31 |
32 | {/* Conditionally reverse flex direction for login */} 33 |
38 |
39 |

40 | {formType === "signup" ? "Signup" : "Login"} 41 |

42 |

43 | 44 | {formType === "signup" 45 | ? "Welcome to our platform" 46 | : "Welcome back"} 47 | 48 |

49 | {formType === "signup" ? : } 50 |
51 |
52 | {formType === "signup" ? : } 53 |
54 |
55 |
56 |
57 | )} 58 |
59 |
60 | ); 61 | } 62 | 63 | export default Template; 64 | -------------------------------------------------------------------------------- /src/components/CumulativeTimeChart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | LineChart, 4 | Line, 5 | XAxis, 6 | YAxis, 7 | Tooltip, 8 | CartesianGrid, 9 | ResponsiveContainer, 10 | } from 'recharts'; 11 | 12 | const CumulativeTimeChart = ({ laps, formatTime }) => { 13 | // Check if laps is an array and not empty 14 | if (!Array.isArray(laps) || laps.length === 0) { 15 | return

No lap data available.

; // Handle empty laps 16 | } 17 | 18 | // Calculate cumulative times 19 | const cumulativeData = laps.reduce((acc, lap, index) => { 20 | const cumulativeTime = (acc[index - 1]?.cumulativeTime || 0) + lap; // Add current lap to the previous cumulative time 21 | acc.push({ 22 | lapNumber: index + 1, 23 | cumulativeTime, 24 | }); 25 | return acc; 26 | }, []); 27 | 28 | return ( 29 |
30 |

Progress Graph

31 | 32 | 33 | 34 | 35 | formatTime(value)} /> 36 | [formatTime(value), 'Cumulative Time']} /> 37 | 38 | 39 | 40 |
41 | ); 42 | }; 43 | 44 | export default CumulativeTimeChart; 45 | -------------------------------------------------------------------------------- /src/components/Design.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import Particles from "@tsparticles/react"; 3 | import { loadSlim } from "@tsparticles/slim"; // Loading only necessary features 4 | 5 | const Design = ({ theme }) => { 6 | const [init, setInit] = useState(false); 7 | 8 | useEffect(() => { 9 | // Initializing the particles engine only once 10 | const initParticlesEngine = async (engine) => { 11 | await loadSlim(engine); 12 | setInit(true); 13 | }; 14 | 15 | initParticlesEngine(Particles.engine); 16 | }, []); 17 | 18 | const particlesLoaded = (container) => { 19 | console.log(container); 20 | }; 21 | 22 | return ( 23 | <> 24 | {init && ( 25 | 98 | )} 99 | 100 | ); 101 | }; 102 | 103 | export default Design; 104 | -------------------------------------------------------------------------------- /src/components/GoogleTranslate.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import styled from "styled-components"; 3 | 4 | const TranslateContainer = styled.div` 5 | /* added z-index here*/ 6 | position:relative; 7 | z-index:1000; 8 | .goog-te-combo { 9 | display: inline-block; 10 | background-color: #e0f2ff; 11 | border: 2px solid #0056b3; 12 | border-radius: 0.5rem; 13 | padding: 0.5rem 1rem; 14 | font-size: 0.875rem; 15 | transition: all 0.3s ease; 16 | outline: none; 17 | color: #000; 18 | font-weight: 500; 19 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); 20 | } 21 | 22 | .goog-te-combo:hover { 23 | background-color: #b3e0ff; 24 | border-color: #004494; 25 | box-shadow: 0 6px 8px rgba(0, 0, 0, 0.25); 26 | } 27 | 28 | .goog-logo-link { 29 | display: none !important; 30 | } 31 | 32 | .goog-te-gadget { 33 | color: transparent !important; 34 | } 35 | 36 | .goog-te-gadget > span > a { 37 | display: none !important; 38 | } 39 | 40 | .goog-te-gadget .goog-te-combo { 41 | color: #0056b3 !important; 42 | } 43 | 44 | #google_translate_element .goog-te-gadget-simple .goog-te-menu-value span:first-child { 45 | display: none; 46 | } 47 | 48 | #google_translate_element .goog-te-gadget-simple .goog-te-menu-value:before { 49 | content: 'Translate'; 50 | color: #0056b3; 51 | } 52 | 53 | .goog-te-banner-frame { 54 | display: none !important; 55 | } 56 | 57 | .goog-te-menu-frame { 58 | max-height: 400px !important; 59 | overflow-y: auto !important; 60 | background-color: #fff; 61 | border: 1px solid #cce5ff; 62 | border-radius: 0.5rem; 63 | } 64 | 65 | .skiptranslate > iframe { 66 | height: 0 !important; 67 | border-style: none; 68 | box-shadow: none; 69 | } 70 | `; 71 | 72 | const GoogleTranslate = () => { 73 | useEffect(() => { 74 | window.googleTranslateInit = () => { 75 | if (!window.google?.translate?.TranslateElement) { 76 | setTimeout(window.googleTranslateInit, 100); 77 | } else { 78 | new window.google.translate.TranslateElement( 79 | { 80 | pageLanguage: "en", 81 | includedLanguages: 82 | "en,hi,pa,sa,mr,ur,bn,es,ja,ko,zh-CN,es,nl,fr,de,it,ta,te", 83 | layout: window.google.translate.TranslateElement.InlineLayout.HORIZONTAL, 84 | defaultLanguage: "en", 85 | autoDisplay: false, 86 | }, 87 | "google_element" 88 | ); 89 | } 90 | }; 91 | 92 | const loadGoogleTranslateScript = () => { 93 | if (!document.getElementById("google_translate_script")) { 94 | const script = document.createElement("script"); 95 | script.type = "text/javascript"; 96 | script.src = 97 | "https://translate.google.com/translate_a/element.js?cb=googleTranslateInit"; 98 | script.id = "google_translate_script"; 99 | script.onerror = () => console.error("Error loading Google Translate script"); 100 | document.body.appendChild(script); 101 | } 102 | }; 103 | 104 | loadGoogleTranslateScript(); 105 | 106 | if (window.google && window.google.translate) { 107 | window.googleTranslateInit(); 108 | } 109 | 110 | return () => { 111 | // Cleanup logic if necessary 112 | }; 113 | }, []); 114 | 115 | return ( 116 | 117 | {/* Google Translate Element will be inserted here */} 118 | 119 | ); 120 | }; 121 | 122 | export default GoogleTranslate; 123 | -------------------------------------------------------------------------------- /src/components/LapAnalysis.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const LapAnalysis = ({ laps, formatTime }) => { 4 | if (laps.length === 0) return null; 5 | 6 | // Use the laps array directly, as it already contains lap times 7 | const lapTimes = laps; 8 | 9 | // Calculate average, fastest, and slowest lap times 10 | const averageLapTime = lapTimes.reduce((sum, time) => sum + time, 0) / lapTimes.length; 11 | const fastestLap = Math.min(...lapTimes); 12 | const slowestLap = Math.max(...lapTimes); 13 | 14 | return ( 15 |
16 |
17 |

Average Lap Time

18 |

{formatTime(Math.round(averageLapTime))}

19 |
20 |
21 |

Fastest Lap

22 |

{formatTime(fastestLap)}

23 |
24 |
25 |

Slowest Lap

26 |

{formatTime(slowestLap)}

27 |
28 |
29 | ); 30 | }; 31 | 32 | export default LapAnalysis; 33 | -------------------------------------------------------------------------------- /src/components/LapBarChart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | BarChart, 4 | Bar, 5 | XAxis, 6 | YAxis, 7 | Tooltip, 8 | CartesianGrid, 9 | ResponsiveContainer, 10 | Cell 11 | } from 'recharts'; 12 | 13 | const BarChartLapTimes = ({ laps, formatTime }) => { 14 | // Function to get color based on lap index 15 | const getLapColor = (index) => { 16 | const colors = ['#82ca9d', '#ff7f50', '#6495ed', '#da70d6', '#ffd700', '#6a5acd']; 17 | return colors[index % colors.length]; // Cycle through colors 18 | }; 19 | 20 | // Prepare the data for the chart 21 | const formattedData = laps.map((lap, index) => ({ 22 | lapNumber: index + 1, 23 | lapTime: lap, 24 | color: getLapColor(index), // Add color property 25 | })); 26 | 27 | return ( 28 |
29 |

Bar Graph

30 | 31 | 32 | 33 | 34 | 35 | [formatTime(value), 'Lap Time']} /> 36 | 37 | {formattedData.map((entry, index) => ( 38 | // Use dynamic color 39 | ))} 40 | 41 | 42 | 43 |
44 | ); 45 | }; 46 | 47 | export default BarChartLapTimes; 48 | -------------------------------------------------------------------------------- /src/components/LapPieChart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PieChart, Pie, Cell, Tooltip, Legend } from 'recharts'; 3 | 4 | const LapTimePieChart = ({ laps, formatTime }) => { 5 | // Calculate total lap time 6 | const totalLapTime = laps.reduce((acc, lap) => acc + lap, 0); 7 | 8 | // Create data for the pie chart 9 | const data = laps.map((lap, index) => ({ 10 | name: `Lap ${index + 1}`, 11 | value: lap, 12 | })); 13 | const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#FF5555']; 14 | 15 | return ( 16 |
17 |

Pie Chart

18 | 19 | `${name}: ${formatTime(value)}`} 25 | outerRadius={80} 26 | fill="#8884d8" 27 | dataKey="value" 28 | > 29 | {data.map((entry, index) => ( 30 | 31 | ))} 32 | 33 | [formatTime(value), 'Lap Time']} /> 34 | 35 | 36 |
37 | ); 38 | }; 39 | 40 | export default LapTimePieChart; 41 | -------------------------------------------------------------------------------- /src/components/LapVisualization.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const LapVisualization = ({ laps, formatTime }) => { 4 | const maxLap = Math.max(...laps); 5 | 6 | return ( 7 |
8 |

Lap Visualization

9 |
10 | {laps.map((lap, index) => ( 11 |
12 | #{index + 1} 13 |
14 |
18 |
19 | {formatTime(lap)} 20 |
21 | ))} 22 |
23 |
24 | ); 25 | }; 26 | 27 | export default LapVisualization; 28 | -------------------------------------------------------------------------------- /src/components/ParticlesComponent.jsx: -------------------------------------------------------------------------------- 1 | // ParticlesComponent.js 2 | import React from "react"; 3 | import Particles from "@tsparticles/react"; 4 | import { loadSlim } from "@tsparticles/slim"; 5 | 6 | const ParticlesComponent = React.memo(({ particleOptions, particlesLoaded }) => { 7 | return ( 8 | 13 | ); 14 | }); 15 | 16 | export default ParticlesComponent; 17 | -------------------------------------------------------------------------------- /src/components/SpotifyPlayer.css: -------------------------------------------------------------------------------- 1 | .spotify-player-container { 2 | position: fixed; 3 | right: 0; 4 | top: 50%; 5 | transform: translateY(-50%); 6 | z-index: 1000; 7 | display: flex; 8 | flex-direction: column; 9 | align-items: flex-end; 10 | } 11 | 12 | .spotify-icon { 13 | background: black; 14 | color: white; 15 | padding: 10px; 16 | border-radius: 50%; 17 | cursor: pointer; 18 | margin-bottom: 10px; 19 | } 20 | 21 | .spotify-icon img { 22 | width: 40px; 23 | height: 40px; 24 | } 25 | 26 | .spotify-iframe { 27 | border-radius: 12px; 28 | width: 300px; 29 | height: 380px; 30 | transition: transform 0.3s ease; 31 | transform: translateX(100%); 32 | overflow: hidden; 33 | } 34 | 35 | .spotify-iframe.visible { 36 | transform: translateX(0); 37 | } 38 | 39 | @media (max-width: 900px) { 40 | .spotify-player-container{ 41 | top: 35%; 42 | } 43 | .spotify-iframe { 44 | width: 250px; 45 | height: 300px; 46 | } 47 | } 48 | 49 | @media (max-width: 480px) { 50 | .spotify-iframe { 51 | width: 100%; 52 | height: 200px; 53 | max-width: 300px; 54 | } 55 | 56 | .spotify-icon { 57 | padding: 8px; 58 | } 59 | 60 | .spotify-icon img { 61 | width: 30px; 62 | height: 30px; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/SpotifyPlayer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import './SpotifyPlayer.css'; 3 | 4 | const SpotifyPlayer = () => { 5 | const [isHovered, setIsHovered] = useState(false); 6 | 7 | return ( 8 |
9 | {/* Spotify icon with hover events */} 10 |
setIsHovered(true)} 13 | onMouseLeave={() => setIsHovered(false)} 14 | > 15 | Spotify Icon 19 |
20 | 21 | {/* Spotify player iframe with hover events */} 22 | 31 |
32 | ); 33 | }; 34 | 35 | export default SpotifyPlayer; 36 | -------------------------------------------------------------------------------- /src/components/SwitchTab.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useNavigate } from "react-router-dom"; // Import useNavigate 3 | 4 | const SwitchTab = () => { 5 | const navigate = useNavigate(); // Initialize the navigate function 6 | 7 | const handleSwitch = () => { 8 | navigate("/"); // Navigate to the AutoCounter page 9 | }; 10 | 11 | return ( 12 | 15 | ); 16 | }; 17 | 18 | export default SwitchTab; 19 | -------------------------------------------------------------------------------- /src/components/audio/alert-85101.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/param-code/counter-app/9e06090d67aa9c3c943cae95cf6c777bd52d45ec/src/components/audio/alert-85101.mp3 -------------------------------------------------------------------------------- /src/components/css/Footer.css: -------------------------------------------------------------------------------- 1 | .clock-icon { 2 | width: 96px; /* Adjusting based on w-24 */ 3 | height: 100px; /* Adjusting based on h-15 */ 4 | margin-bottom: -8px; /* For -mb-2 */ 5 | transition: transform 0.3s ease, box-shadow 0.3s ease; 6 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Initial shadow */ 7 | } 8 | 9 | .clock-icon:hover { 10 | transform: translateY(-10px) rotateX(15deg) rotateY(15deg); /* 3D effect */ 11 | box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); /* Increased shadow for depth */ 12 | } 13 | ul li a { 14 | position: relative; 15 | text-decoration: none; 16 | color: #gray-400 ; /* Default text color */ 17 | transition: color 0.3s ease; /* Smooth color transition */ 18 | } 19 | 20 | ul li a::after { 21 | content: ''; 22 | position: absolute; 23 | left: 0; 24 | bottom: -3px; /* Distance from the text */ 25 | width: 0; 26 | height: 2px; /* Thickness of the underline */ 27 | background-color: white; /* Color of the underline */ 28 | transition: width 0.3s ease; /* Smooth width transition */ 29 | } 30 | 31 | ul li a:hover::after { 32 | width: 100%; /* Expand underline on hover */ 33 | } 34 | 35 | ul li a:hover { 36 | color: white; /* Change text color on hover */ 37 | } 38 | -------------------------------------------------------------------------------- /src/components/css/navbar.css: -------------------------------------------------------------------------------- 1 | .butt{ 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | } 6 | .page{ 7 | transition: all 0.2s ease-in-out; 8 | } 9 | .page:hover{ 10 | transform: scaleX(1.1); 11 | } 12 | .pages{ 13 | transition: all 0.2s ease-in-out; 14 | } 15 | .pages:hover{ 16 | /* transform: scale(1.1); */ 17 | animation: jump-shaking 0.83s infinite; 18 | } 19 | 20 | /* span.rise-shake { 21 | animation: jump-shaking 0.83s infinite; 22 | } */ 23 | 24 | @keyframes jump-shaking { 25 | 0% { transform: translateX(0) } 26 | 25% { transform: translateY(-5px) } 27 | 35% { transform: translateY(-5px) rotate(17deg) } 28 | 55% { transform: translateY(-5px) rotate(-17deg) } 29 | 65% { transform: translateY(-5px) rotate(17deg) } 30 | 75% { transform: translateY(-5px) rotate(-17deg) } 31 | 100% { transform: translateY(0) rotate(0) } 32 | } -------------------------------------------------------------------------------- /src/components/laplist.jsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import React, { memo, useEffect } from 'react' 4 | import { ScrollArea } from './ui/scroll-area' 5 | import { motion, AnimatePresence } from 'framer-motion' 6 | 7 | const LapList = ({ laps, formatTime, lapsEndRef }) => { 8 | // Auto-scroll to the latest lap 9 | useEffect(() => { 10 | if (lapsEndRef?.current) { 11 | lapsEndRef.current.scrollIntoView({ behavior: 'smooth' }) 12 | } 13 | }, [laps, lapsEndRef]) 14 | 15 | return ( 16 |
17 |

Laps

18 | 19 | 20 | 21 | {laps.map((lap, index) => ( 22 | 30 | Lap {index + 1} 31 | {formatTime(lap)} 32 | 33 | ))} 34 | 35 |
36 | 37 |
38 | ) 39 | } 40 | 41 | export default LapList 42 | -------------------------------------------------------------------------------- /src/components/pop-up.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | 4 | const PopupNotification = ({ message, onClose }) => { 5 | return ( 6 |
7 |

{message}

8 | 14 |
15 | ); 16 | }; 17 | 18 | export default PopupNotification; 19 | -------------------------------------------------------------------------------- /src/components/timer.jsx: -------------------------------------------------------------------------------- 1 | 2 | const Timer = ({ formattedTime }) => { 3 | return ( 4 |
5 |

6 | {formattedTime} 7 |

8 |
9 | ) 10 | } 11 | 12 | export default Timer 13 | -------------------------------------------------------------------------------- /src/components/ui/button.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => { 37 | const Comp = asChild ? Slot : "button" 38 | return ( 39 | () 43 | ); 44 | }) 45 | Button.displayName = "Button" 46 | 47 | export { Button, buttonVariants } 48 | -------------------------------------------------------------------------------- /src/components/ui/card.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | const Card = React.forwardRef((props, ref) => { 6 | const { className, ...rest } = props; 7 | return ( 8 |
13 | ); 14 | }); 15 | Card.displayName = "Card"; 16 | 17 | const CardHeader = React.forwardRef((props, ref) => { 18 | const { className, ...rest } = props; 19 | return ( 20 |
25 | ); 26 | }); 27 | CardHeader.displayName = "CardHeader"; 28 | 29 | const CardTitle = React.forwardRef((props, ref) => { 30 | const { className, ...rest } = props; 31 | return ( 32 |

37 | ); 38 | }); 39 | CardTitle.displayName = "CardTitle"; 40 | 41 | const CardDescription = React.forwardRef((props, ref) => { 42 | const { className, ...rest } = props; 43 | return ( 44 |

49 | ); 50 | }); 51 | CardDescription.displayName = "CardDescription"; 52 | 53 | const CardContent = React.forwardRef((props, ref) => { 54 | const { className, ...rest } = props; 55 | return ( 56 |

57 | ); 58 | }); 59 | CardContent.displayName = "CardContent"; 60 | 61 | const CardFooter = React.forwardRef((props, ref) => { 62 | const { className, ...rest } = props; 63 | return ( 64 |
69 | ); 70 | }); 71 | CardFooter.displayName = "CardFooter"; 72 | 73 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; 74 | -------------------------------------------------------------------------------- /src/components/ui/scroll-area.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const ScrollArea = React.forwardRef(({ className, children, ...props }, ref) => ( 7 | 11 | 12 | {children} 13 | 14 | 15 | 16 | 17 | )) 18 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName 19 | 20 | const ScrollBar = React.forwardRef(({ className, orientation = "vertical", ...props }, ref) => ( 21 | 33 | 34 | 35 | )) 36 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName 37 | 38 | export { ScrollArea, ScrollBar } 39 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* body { 6 | background-image: url('/bg.jpg'); 7 | background-size: cover; 8 | background-position: center; 9 | background-repeat: no-repeat; 10 | } */ 11 | 12 | 13 | .typewriter { 14 | overflow: hidden; 15 | border-right: .15em solid orange; 16 | white-space: nowrap; 17 | margin: 0 auto; 18 | letter-spacing:.15em; 19 | animation: 20 | typing 3.5s steps(40, end), 21 | blink-caret .75s step-end infinite; 22 | } 23 | 24 | @keyframes typing { 25 | from { width: 0 } 26 | to { width: 100% } 27 | } 28 | 29 | @keyframes blink-caret { 30 | from, to { border-color: transparent } 31 | 50% { border-color: orange; } 32 | } 33 | 34 | @layer base { 35 | :root { 36 | --background: 0 0% 100%; 37 | --foreground: 0 0% 3.9%; 38 | --card: 0 0% 100%; 39 | --card-foreground: 0 0% 3.9%; 40 | --popover: 0 0% 100%; 41 | --popover-foreground: 0 0% 3.9%; 42 | --primary: 0 0% 9%; 43 | --primary-foreground: 0 0% 98%; 44 | --secondary: 0 0% 96.1%; 45 | --secondary-foreground: 0 0% 9%; 46 | --muted: 0 0% 96.1%; 47 | --muted-foreground: 0 0% 45.1%; 48 | --accent: 0 0% 96.1%; 49 | --accent-foreground: 0 0% 9%; 50 | --destructive: 0 84.2% 60.2%; 51 | --destructive-foreground: 0 0% 98%; 52 | --border: 0 0% 89.8%; 53 | --input: 0 0% 89.8%; 54 | --ring: 0 0% 3.9%; 55 | --chart-1: 12 76% 61%; 56 | --chart-2: 173 58% 39%; 57 | --chart-3: 197 37% 24%; 58 | --chart-4: 43 74% 66%; 59 | --chart-5: 27 87% 67%; 60 | --radius: 0.5rem; 61 | } 62 | .dark { 63 | --background: 0 0% 3.9%; 64 | --foreground: 0 0% 98%; 65 | --card: 0 0% 3.9%; 66 | --card-foreground: 0 0% 98%; 67 | --popover: 0 0% 3.9%; 68 | --popover-foreground: 0 0% 98%; 69 | --primary: 0 0% 98%; 70 | --primary-foreground: 0 0% 9%; 71 | --secondary: 0 0% 14.9%; 72 | --secondary-foreground: 0 0% 98%; 73 | --muted: 0 0% 14.9%; 74 | --muted-foreground: 0 0% 63.9%; 75 | --accent: 0 0% 14.9%; 76 | --accent-foreground: 0 0% 98%; 77 | --destructive: 0 62.8% 30.6%; 78 | --destructive-foreground: 0 0% 98%; 79 | --border: 0 0% 14.9%; 80 | --input: 0 0% 14.9%; 81 | --ring: 0 0% 83.1%; 82 | --chart-1: 220 70% 50%; 83 | --chart-2: 160 60% 45%; 84 | --chart-3: 30 80% 55%; 85 | --chart-4: 280 65% 60%; 86 | --chart-5: 340 75% 55%; 87 | } 88 | } 89 | 90 | @layer base { 91 | * { 92 | @apply border-border; 93 | } 94 | body { 95 | @apply bg-background text-foreground; 96 | } 97 | } 98 | @media screen and (max-width: 778px) { 99 | .container { 100 | flex-direction: column; 101 | align-items: center; /* Center the items vertically */ 102 | text-align: center; /* Center align text */ 103 | } 104 | 105 | .googletranslate { 106 | width: 100%; 107 | margin-top: 8px; 108 | display: flex; 109 | justify-content: center; 110 | } 111 | 112 | .social-icons { 113 | justify-content: center; 114 | margin-top: 8px; 115 | } 116 | 117 | .text-sm { 118 | margin: 0; 119 | } 120 | } -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | import { Provider } from "react-redux"; 7 | import rootReducer from "./reducer"; 8 | import { configureStore } from "@reduxjs/toolkit"; 9 | import { Toaster } from 'react-hot-toast'; 10 | import Review from './components/Auth/Review.jsx'; 11 | 12 | const store = configureStore({ 13 | reducer: rootReducer, 14 | }); 15 | 16 | 17 | createRoot(document.getElementById("root")).render( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | 27 | -------------------------------------------------------------------------------- /src/pages/Contributors.css: -------------------------------------------------------------------------------- 1 | .contributors-container { 2 | width: 100%; 3 | height: 100%; 4 | padding-top: 2rem; 5 | overflow: hidden; 6 | transition: background 0.3s ease-in-out; /* Add transition for smooth background change */ 7 | } 8 | 9 | .github-icon { 10 | margin-right: 0.5rem; 11 | vertical-align: middle; 12 | fill: white; /* Adjust color as needed */ 13 | } 14 | 15 | .contributors-title { 16 | margin-top: 0rem; 17 | text-align: center; 18 | color: #0D9488; 19 | font-size: 2.5rem; 20 | font-weight: bold; 21 | margin-bottom: 2rem; 22 | text-transform: uppercase; 23 | } 24 | 25 | .contributors-grid { 26 | display: flex; 27 | flex-wrap: wrap; 28 | justify-content: center; 29 | gap: 2rem; 30 | margin-bottom: 4rem; 31 | } 32 | 33 | .contributor-card { 34 | position: relative; 35 | width: 100%; 36 | max-width: 25%; 37 | display: flex; 38 | flex-direction: column; 39 | align-items: center; 40 | background-color: #0D9488; 41 | border: 1px solid #004d46; 42 | border-radius: 0.5rem; 43 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 44 | padding: 1rem; 45 | overflow: hidden; 46 | transition: transform 0.4s ease, box-shadow 0.3s ease; 47 | } 48 | 49 | .contributor-card:hover { 50 | transform: scale(1.05); 51 | box-shadow: 0 4px 6px rgba(13, 148, 136, 0.752); 52 | } 53 | 54 | .contributor-card:hover p { 55 | text-shadow: 1px 1px 2px rgb(13, 148, 136), 0 0 0.2em rgb(0, 191, 255), 0 0 0.8em rgb(135, 206, 235); 56 | color: rgb(0, 0, 0); 57 | font-weight: 500; 58 | } 59 | 60 | .contributor-card:hover h2 { 61 | text-shadow: 1px 1px 2px rgba(237, 9, 176, 0.926), 0 0 0.2em rgb(0, 191, 255), 0 0 0.8em rgb(135, 206, 235); 62 | color: white; 63 | font-size: 1.04rem; 64 | font-weight: 600; 65 | text-decoration: wavy; 66 | } 67 | 68 | .contributor-card:hover .contributor-avatar { 69 | border: 3.5px solid #89e6f0; 70 | width: 5.2rem; 71 | height: 5.2rem; 72 | box-shadow: -2px 4px 10px 1px rgba(1, 41, 218, 0.75); 73 | } 74 | 75 | .contributor-card::before { 76 | content: ""; 77 | position: absolute; 78 | top: 0; 79 | left: 0; 80 | width: 100%; 81 | height: 100%; 82 | background: linear-gradient(152deg, #01988c 50%, #004d46 50%); 83 | transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out; 84 | transform: translate(-100%, -100%); 85 | opacity: 0; 86 | z-index: -1; 87 | } 88 | 89 | .contributor-card:hover::before { 90 | transform: translate(0, 0); 91 | opacity: 1; 92 | } 93 | 94 | .contributor-link { 95 | display: block; 96 | } 97 | 98 | .contributor-avatar { 99 | width: 5rem; 100 | height: 5rem; 101 | border-radius: 50%; 102 | object-fit: cover; 103 | margin-bottom: 1rem; 104 | border: 2px solid #00b4d8; 105 | transition: border 0.4s ease-in-out, height 0.4s ease-in-out, width 0.4s ease-in-out, box-shadow 0.3s ease-in-out; 106 | } 107 | 108 | .contributor-name { 109 | font-size: 1rem; 110 | font-weight: 600; 111 | color: #f3f4f6; 112 | margin-bottom: 0.5rem; 113 | transition: text-shadow 0.4s ease-in-out, font-size 0.5s ease-in-out, text-decoration 0.4s ease-in-out; 114 | } 115 | 116 | .contributor-contributions { 117 | color: #d1d5db; 118 | transition: text-shadow 0.4s ease-in-out; 119 | } 120 | 121 | @media (max-width: 1200px) { 122 | .contributor-card { 123 | max-width: 33.333%; 124 | } 125 | } 126 | 127 | @media (max-width: 992px) { 128 | .contributor-card { 129 | max-width: 50%; 130 | } 131 | } 132 | 133 | @media (max-width: 768px) { 134 | .contributor-card { 135 | max-width: 97%; 136 | } 137 | } 138 | 139 | /* Dark Mode Support */ 140 | .contributors-container.dark-mode { 141 | background: linear-gradient(to right, #333, #555); /* Change background for dark mode */ 142 | } 143 | 144 | /* Dark mode body background */ 145 | body.dark { 146 | background-color: #121212; /* Dark background for the body */ 147 | } 148 | 149 | .pagination { 150 | display: flex; 151 | justify-content: center; 152 | align-items: center; 153 | margin-top: 2rem; 154 | gap: 1rem; 155 | } 156 | 157 | .pagination-button { 158 | padding: 0.5rem 1rem; 159 | background-color: #0D9488; 160 | color: white; 161 | margin: 5px; 162 | border: none; 163 | border-radius: 0.25rem; 164 | cursor: pointer; 165 | transition: background-color 0.3s ease; 166 | } 167 | 168 | .pagination-button:hover { 169 | background-color: #005f87; 170 | } 171 | 172 | .pagination-button:disabled { 173 | background-color: #a09e9e; 174 | cursor: not-allowed; 175 | } 176 | 177 | .page-info { 178 | font-size: 1rem; 179 | color: #333; 180 | 181 | } 182 | -------------------------------------------------------------------------------- /src/pages/Contributors.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import axios from "axios"; 3 | import "./Contributors.css"; 4 | import Navbar from "../components/navbar"; 5 | 6 | function Contributors() { 7 | const [contributors, setContributors] = useState([]); 8 | const [loading, setLoading] = useState(true); 9 | const [error, setError] = useState(null); 10 | const [theme, setTheme] = useState("light"); 11 | 12 | const [currentPage, setCurrentPage] = useState(1); 13 | const contributorsPerPage = 9; 14 | 15 | useEffect(() => { 16 | const storedTheme = localStorage.getItem("theme"); 17 | if (storedTheme) { 18 | setTheme(storedTheme); 19 | } 20 | }, []); 21 | 22 | useEffect(() => { 23 | document.body.className = theme; 24 | localStorage.setItem("theme", theme); 25 | }, [theme]); 26 | 27 | useEffect(() => { 28 | async function fetchContributors() { 29 | let allContributors = []; 30 | let page = 1; 31 | 32 | try { 33 | while (true) { 34 | const response = await axios.get( 35 | `https://api.github.com/repos/param-code/counter-app/contributors`, 36 | { 37 | params: { 38 | per_page: 100, 39 | page, 40 | }, 41 | } 42 | ); 43 | const data = response.data; 44 | if (data.length === 0) { 45 | break; 46 | } 47 | allContributors = [...allContributors, ...data]; 48 | page++; 49 | } 50 | setContributors(allContributors); 51 | } catch (error) { 52 | console.error("Error fetching contributors:", error.message); 53 | setError("Failed to load contributors. Please try again later."); 54 | } finally { 55 | setLoading(false); 56 | } 57 | } 58 | fetchContributors(); 59 | }, []); 60 | 61 | const indexOfLastContributor = currentPage * contributorsPerPage; 62 | const indexOfFirstContributor = indexOfLastContributor - contributorsPerPage; 63 | const currentContributors = contributors.slice( 64 | indexOfFirstContributor, 65 | indexOfLastContributor 66 | ); 67 | 68 | const totalPages = Math.ceil(contributors.length / contributorsPerPage); 69 | 70 | const handleNextPage = () => { 71 | if (currentPage < totalPages) { 72 | setCurrentPage((prevPage) => prevPage + 1); 73 | } 74 | }; 75 | 76 | const handlePrevPage = () => { 77 | if (currentPage > 1) { 78 | setCurrentPage((prevPage) => prevPage - 1); 79 | } 80 | }; 81 | 82 | return ( 83 |
88 | setTheme((prev) => (prev === "dark" ? "light" : "dark"))} 91 | /> 92 |

Our Contributors

93 |
94 | {loading ? ( 95 |

Loading contributors...

96 | ) : error ? ( 97 |

{error}

98 | ) : currentContributors.length > 0 ? ( 99 | currentContributors.map((contributor) => ( 100 |
101 | 107 | {contributor.login} 112 | 113 |

{contributor.login}

114 |

115 | Contributions: {contributor.contributions} 116 |

117 |
118 | )) 119 | ) : ( 120 |

No contributors found.

121 | )} 122 |
123 | 124 |
125 | 132 | 133 | Page {currentPage} of {totalPages} 134 | 135 | 142 |
143 |
144 | ); 145 | } 146 | 147 | export default Contributors; 148 | -------------------------------------------------------------------------------- /src/pages/Counter.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import Navbar from "../components/navbar"; 3 | 4 | function Counter() { 5 | const [minutes, setMinutes] = useState(0); 6 | const [seconds, setSeconds] = useState(0); 7 | const [isRunning, setIsRunning] = useState(false); 8 | const [theme, setTheme] = useState("light"); // State for theme 9 | 10 | // Function to toggle theme 11 | const toggleTheme = () => { 12 | setTheme((prev) => (prev === "dark" ? "light" : "dark")); 13 | }; 14 | 15 | const handleMinutesChange = (e) => { 16 | setMinutes(Math.max(0, Math.min(59, parseInt(e.target.value)))); 17 | }; 18 | 19 | const handleSecondsChange = (e) => { 20 | setSeconds(Math.max(0, Math.min(59, parseInt(e.target.value)))); 21 | }; 22 | 23 | const startTimer = () => { 24 | if (minutes === 0 && seconds === 0) { 25 | return; 26 | } 27 | setIsRunning(true); 28 | }; 29 | 30 | const pauseTimer = () => { 31 | setIsRunning(false); 32 | }; 33 | 34 | const resetTimer = () => { 35 | setMinutes(0); 36 | setSeconds(0); 37 | setIsRunning(false); 38 | }; 39 | 40 | useEffect(() => { 41 | let intervalId; 42 | if (isRunning) { 43 | intervalId = setInterval(() => { 44 | if (seconds === 0) { 45 | if (minutes === 0) { 46 | clearInterval(intervalId); 47 | setIsRunning(false); 48 | return; 49 | } 50 | setMinutes((prev) => prev - 1); 51 | setSeconds(59); 52 | } else { 53 | setSeconds((prev) => prev - 1); 54 | } 55 | }, 1000); 56 | } 57 | return () => clearInterval(intervalId); 58 | }, [isRunning, minutes, seconds]); 59 | 60 | const formatTime = () => { 61 | const minutesStr = String(minutes).padStart(2, "0"); 62 | const secondsStr = String(seconds).padStart(2, "0"); 63 | return `${minutesStr}:${secondsStr}`; 64 | }; 65 | 66 | return ( 67 |
72 | {/* Navbar with toggleTheme function passed as a prop */} 73 | 74 |
75 |
76 |

77 | Countdown Timer 78 |

79 | 80 | {/* Input Fields */} 81 |
82 | {/* Minutes Input */} 83 |
84 | 87 | 96 |
97 | 98 | {/* Seconds Input */} 99 |
100 | 103 | 112 |
113 |
114 | 115 | {/* Timer Display */} 116 |
117 | {formatTime()} 118 |
119 | 120 | {/* Control Buttons */} 121 |
122 | {/* Start Button */} 123 | 134 | 135 | {/* Pause Button */} 136 | {isRunning && ( 137 | 143 | )} 144 | 145 | {/* Reset Button */} 146 | 152 |
153 |
154 |
155 |
156 | ); 157 | } 158 | 159 | export default Counter; 160 | -------------------------------------------------------------------------------- /src/pages/Error404.jsx: -------------------------------------------------------------------------------- 1 | /** @format */ 2 | 3 | import React from "react"; 4 | import { Link } from "react-router-dom"; 5 | 6 | const Error404 = () => { 7 | return ( 8 |
9 |
10 |
11 |
19 |
20 |
21 |

22 | Looks like you're lost 23 |

24 | 25 |

26 | The page you are looking for is not available! 27 |

28 | 29 | 32 | Home 33 | 34 |
35 |
36 |
37 |
38 | ); 39 | }; 40 | 41 | export default Error404; 42 | -------------------------------------------------------------------------------- /src/pages/Feedback.css: -------------------------------------------------------------------------------- 1 | .feedback-form-container { 2 | background: linear-gradient(135deg, #2e2e3a 0%, #1e1e2f 100%); 3 | border-radius: 20px; 4 | padding: 60px; 5 | width: 650px; 6 | margin: 50px auto; 7 | color: #f5f4ef; 8 | text-align: center; 9 | box-shadow: 0 15px 30px rgba(0, 0, 0, 0.5); 10 | transition: transform 0.3s ease; 11 | } 12 | 13 | .feedback-form-container:hover { 14 | transform: scale(1.02); 15 | } 16 | 17 | .heading { 18 | margin-bottom: 20px; 19 | font-size: 42px; 20 | font-family: 'Poppins', sans-serif; 21 | color: #17e6e6; 22 | text-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2); 23 | } 24 | 25 | .name, 26 | .email, 27 | .exp { 28 | margin-top: 20px; 29 | width: 100%; 30 | padding: 15px; 31 | background-color: #3a3a4d; 32 | border: 2px solid #17e6e6; 33 | border-radius: 10px; 34 | color: #ffffff; 35 | font-size: 16px; 36 | box-sizing: border-box; 37 | transition: border-color 0.3s, box-shadow 0.3s; 38 | } 39 | 40 | .name:focus, 41 | .email:focus, 42 | .exp:focus { 43 | outline: none; 44 | border-color: #58a7db; 45 | box-shadow: 0 0 5px rgba(24, 144, 255, 0.5); 46 | } 47 | 48 | textarea { 49 | height: 120px; 50 | resize: none; 51 | } 52 | 53 | .stars { 54 | margin-top: 20px; 55 | display: flex; 56 | justify-content: center; 57 | margin: 10px 0; 58 | } 59 | 60 | .star, 61 | .star-filled { 62 | font-size: 40px; 63 | cursor: pointer; 64 | transition: color 0.3s, transform 0.2s; 65 | } 66 | 67 | .star { 68 | color: #a3a3a3; 69 | } 70 | 71 | .star-filled { 72 | color: #f39c12; 73 | } 74 | 75 | .star:hover, 76 | .star-filled:hover { 77 | transform: scale(1.2); 78 | } 79 | 80 | .post-button { 81 | margin-top: 30px; 82 | background: linear-gradient(135deg, #17e6e6, #15b2b2); 83 | width: 220px; 84 | color: #2e2e2e; 85 | border: none; 86 | padding: 15px; 87 | border-radius: 8px; 88 | font-size: 18px; 89 | cursor: pointer; 90 | transition: background 0.3s, transform 0.2s; 91 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); 92 | } 93 | 94 | .post-button:hover { 95 | background: linear-gradient(135deg, #15b2b2, #0e8f8f); 96 | transform: translateY(-2px); 97 | } 98 | 99 | .popup-overlay { 100 | position: fixed; 101 | top: 0; 102 | left: 0; 103 | width: 100%; 104 | height: 100%; 105 | background-color: rgba(0, 0, 0, 0.7); 106 | display: flex; 107 | justify-content: center; 108 | align-items: center; 109 | } 110 | 111 | .popup { 112 | background-color: #ffffff; 113 | padding: 30px; 114 | border-radius: 15px; 115 | text-align: center; 116 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3); 117 | animation: fadeIn 0.5s ease; 118 | } 119 | 120 | .popup h3 { 121 | margin-bottom: 15px; 122 | color: #333; 123 | font-family: 'Poppins', sans-serif; 124 | } 125 | 126 | .popup p { 127 | margin-bottom: 20px; 128 | color: #555; 129 | } 130 | 131 | .close-popup-button { 132 | background-color: #17e6e6; 133 | color: #2e2e2e; 134 | border: none; 135 | padding: 12px 24px; 136 | border-radius: 5px; 137 | cursor: pointer; 138 | transition: background-color 0.3s, transform 0.2s; 139 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 140 | } 141 | 142 | .close-popup-button:hover { 143 | background-color: #15b2b2; 144 | transform: scale(1.05); 145 | } 146 | 147 | @keyframes fadeIn { 148 | from { 149 | opacity: 0; 150 | transform: translateY(-20px); 151 | } 152 | to { 153 | opacity: 1; 154 | transform: translateY(0); 155 | } 156 | } 157 | 158 | @media (max-width: 768px) { 159 | .feedback-form-container { 160 | width: 90%; 161 | padding: 30px; 162 | box-sizing: border-box; 163 | } 164 | 165 | .heading { 166 | font-size: 36px; 167 | } 168 | 169 | .post-button { 170 | width: 100%; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/pages/Feedback.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import "./Feedback.css"; 3 | import { feedbackValidation } from "@/validations/validation"; 4 | 5 | const Feedback = () => { 6 | const [name, setName] = useState(""); 7 | const [email, setEmail] = useState(""); 8 | const [rating, setRating] = useState(0); 9 | const [hoverRating, setHoverRating] = useState(0); 10 | const [feedback, setFeedback] = useState(""); 11 | const [isSubmitted, setIsSubmitted] = useState(false); 12 | const [message, setMessage] = useState(""); 13 | const [messageType, setMessageType] = useState(""); 14 | const [errors, setErrors] = useState({}); 15 | 16 | const handleRating = (rate) => { 17 | setRating(rate); 18 | }; 19 | 20 | const handleSubmit = async (e) => { 21 | e.preventDefault(); 22 | 23 | try { 24 | await feedbackValidation.validate( 25 | { name, email, feedback }, 26 | { abortEarly: false } 27 | ); 28 | setErrors({}); 29 | } catch (error) { 30 | const newErrors = {}; 31 | error.inner.forEach((err) => { 32 | newErrors[err.path] = err.message; 33 | }); 34 | setErrors(newErrors); 35 | return; 36 | } 37 | 38 | if (name && email && rating && feedback) { 39 | try { 40 | const response = await fetch("http://localhost:5000/contact", { 41 | method: "POST", 42 | headers: { 43 | "Content-Type": "application/json", 44 | }, 45 | body: JSON.stringify({ 46 | name, 47 | email, 48 | rating, 49 | message: feedback, 50 | }), 51 | }); 52 | 53 | const result = await response.json(); 54 | 55 | if (response.ok) { 56 | setMessage("Your feedback has been successfully submitted."); 57 | setMessageType("success"); 58 | setIsSubmitted(true); 59 | resetForm(); 60 | } else { 61 | setMessage(result.message || "An error occurred. Please try again."); 62 | setMessageType("error"); 63 | } 64 | } catch (error) { 65 | setMessage("Failed to submit feedback. Please try again later."); 66 | setMessageType("error"); 67 | } 68 | } else { 69 | setMessage("Please fill out all fields."); 70 | setMessageType("error"); 71 | } 72 | }; 73 | 74 | const resetForm = () => { 75 | setTimeout(() => { 76 | setName(""); 77 | setEmail(""); 78 | setRating(0); 79 | setFeedback(""); 80 | setHoverRating(0); 81 | setIsSubmitted(false); 82 | setMessage(""); 83 | setMessageType(""); 84 | }, 10000); // Reset form and message after 10 seconds 85 | }; 86 | 87 | const closePopup = () => { 88 | setIsSubmitted(false); 89 | }; 90 | 91 | return ( 92 |
93 |
94 |

Feedback Form

95 | 96 | {/* Name Input */} 97 | setName(e.target.value)} 103 | required 104 | /> 105 | {errors.name &&
{errors.name}
} 106 | {/* Email Input */} 107 | setEmail(e.target.value)} 113 | required 114 | /> 115 | {errors.email &&
{errors.email}
} 116 | {/* Feedback Textarea */} 117 |