├── 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 |
131 |
132 | 133 |
135 |
136 |
137 |
138 | ); 139 | } 140 | --------------------------------------------------------------------------------