├── src
├── App.css
├── index.css
├── assets
│ ├── AlarmSound.mp3
│ ├── helpers.js
│ └── react.svg
├── main.jsx
├── components
│ ├── time-setter.jsx
│ └── display.jsx
└── App.jsx
├── public
├── icons.png
└── vite.svg
├── vite.config.js
├── .gitignore
├── index.html
├── README.md
├── .eslintrc.cjs
├── package.json
└── LICENSE
/src/App.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuzweelisa/25-plus-5-clock/HEAD/public/icons.png
--------------------------------------------------------------------------------
/src/assets/AlarmSound.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ikuzweelisa/25-plus-5-clock/HEAD/src/assets/AlarmSound.mp3
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/src/assets/helpers.js:
--------------------------------------------------------------------------------
1 | export default function formatTime(time){
2 | const mins=Math.floor(time / 60);
3 | const secs=time % 60;
4 | return `${mins < 10 ? "0" + mins.toString() : mins}:${
5 | secs < 10 ? "0" + secs.toString() : secs
6 | }`;
7 | }
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.jsx'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root')).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 25+5 Clock
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
--------------------------------------------------------------------------------
/src/components/time-setter.jsx:
--------------------------------------------------------------------------------
1 | import { FaArrowDown, FaArrowUp } from "react-icons/fa";
2 |
3 | export default function TimeSetter({ time, setTime, min, max, interval, type }) {
4 |
5 | return
6 |
7 | {time / interval}
8 |
9 |
10 | }
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react/jsx-no-target-blank': 'off',
16 | 'react-refresh/only-export-components': [
17 | 'warn',
18 | { allowConstantExport: true },
19 | ],
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "25-5-clock",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "bootstrap": "^5.3.3",
14 | "react": "^18.3.1",
15 | "react-dom": "^18.3.1",
16 | "react-icons": "^5.2.1"
17 | },
18 | "devDependencies": {
19 | "@types/react": "^18.3.3",
20 | "@types/react-dom": "^18.3.0",
21 | "@vitejs/plugin-react": "^4.3.1",
22 | "eslint": "^8.57.0",
23 | "eslint-plugin-react": "^7.34.3",
24 | "eslint-plugin-react-hooks": "^4.6.2",
25 | "eslint-plugin-react-refresh": "^0.4.7",
26 | "vite": "^5.3.4"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/display.jsx:
--------------------------------------------------------------------------------
1 | import { FaPause, FaPlay,FaStopwatch } from "react-icons/fa";
2 | import formatTime from "../assets/helpers";
3 | import { FaRepeat } from "react-icons/fa6";
4 |
5 | export default function Display({
6 | displayState,
7 | reset,
8 | startStop
9 | }) {
10 | return (
11 |
{displayState.timeType}
12 |
{formatTime(displayState.time)}
13 |
14 |
15 |
16 |
17 |
18 |
)
19 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Shema Elisa
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import "bootstrap/dist/css/bootstrap.css";
3 | import TimeSetter from "./components/time-setter";
4 | import Display from "./components/display";
5 | import AlarmSound from "../src/assets/AlarmSound.mp3";
6 | import { FaStopwatch, FaPause, FaClock } from "react-icons/fa";
7 |
8 | const defaultBreakTime = 5 * 60;
9 | const defaultSession = 25 * 60;
10 | const min = 60;
11 | const max = 60 * 60;
12 | const interval = 60;
13 |
14 | export default function App() {
15 | const [breakTime, setBreakTime] = useState(defaultBreakTime);
16 | const [sessionTime, setSessionTime] = useState(defaultSession);
17 | const [displayState, setDisplayState] = useState({
18 | time: defaultSession,
19 | timeType: "session",
20 | timerRunning: false
21 | });
22 | useEffect(() => {
23 | function handleKeyPress(e) {
24 | if (e.key === " ") {
25 | e.preventDefault();
26 | startStop();
27 |
28 | }
29 | }
30 | window.addEventListener("keypress", handleKeyPress);
31 |
32 | return () => window.removeEventListener("keypress", handleKeyPress);
33 | }, [])
34 | useEffect(() => {
35 | let timerId;
36 | if (displayState.timerRunning) {
37 | timerId = window.setInterval(decrementDisplay, 1000);
38 | }
39 | return () => window.clearInterval(timerId);
40 | }, [displayState.timerRunning]);
41 |
42 | useEffect(() => {
43 | if (displayState.time === 0) {
44 | const audio = document.getElementById("beep");
45 | audio.currentTime = 0;
46 | audio.play().catch(e => console.log(e));
47 |
48 | setDisplayState(prev => ({
49 | ...prev,
50 | timeType: prev.timeType === "session" ? "break" : "session",
51 | time: prev.timeType === "session" ? breakTime : sessionTime
52 | }));
53 | }
54 | }, [displayState.time, breakTime, sessionTime]);
55 |
56 | function reset() {
57 | setBreakTime(defaultBreakTime);
58 | setSessionTime(defaultSession);
59 | setDisplayState({
60 | time: defaultSession,
61 | timeType: "session",
62 | timerRunning: false
63 | });
64 | const audio = document.getElementById("beep");
65 | audio.pause();
66 | audio.currentTime = 0;
67 | }
68 |
69 | function startStop() {
70 | setDisplayState(prev => ({
71 | ...prev,
72 | timerRunning: !prev.timerRunning
73 | }));
74 | }
75 |
76 | function decrementDisplay() {
77 | setDisplayState(prev => ({
78 | ...prev,
79 | time: prev.time - 1
80 | }));
81 | }
82 |
83 | function changeBreakTime(time) {
84 | if (!displayState.timerRunning) {
85 | setBreakTime(time);
86 | }
87 | }
88 |
89 | function changeSessionTime(time) {
90 | if (!displayState.timerRunning) {
91 | setSessionTime(time);
92 | setDisplayState({
93 | time: time,
94 | timeType: "session",
95 | timerRunning: false
96 | });
97 | }
98 | }
99 |
100 | return (
101 |
102 |
103 |
104 | CLOCK
105 |
106 |
107 |
108 |
109 |
110 | Break Length
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | Session Length
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
136 |
137 |
138 | );
139 | }
140 |
--------------------------------------------------------------------------------