├── public ├── favicon.ico ├── manifest.json └── index.html ├── src ├── sounds │ └── beep.mp3 ├── index.js ├── components │ ├── Timer.js │ ├── Controls.js │ ├── TimeSet.js │ └── App.js ├── hooks │ └── useInterval.js └── index.css └── package.json /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alirazaramejo/Build-a-25-5-Clock/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/sounds/beep.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alirazaramejo/Build-a-25-5-Clock/HEAD/src/sounds/beep.mp3 -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './index.css' 4 | import App from './components/App' 5 | 6 | ReactDOM.render(, document.getElementById('root')) 7 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Pomodoro Clock", 3 | "name": "Build a 25 + 5 Clock", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Timer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import moment from 'moment' 3 | 4 | const Timer = ({ currentMode, currentTime }) => { 5 | const [mode] = currentMode 6 | const [time] = currentTime 7 | return ( 8 |
9 |

{mode === 'session' ? 'Session' : 'Break'}

, 10 |

{moment(time).format('mm:ss')}

11 |
12 | ) 13 | } 14 | 15 | export default Timer; 16 | -------------------------------------------------------------------------------- /src/components/Controls.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Controls = ({ activeStatus, handleReset }) => { 4 | const [active, setActive] = activeStatus 5 | return ( 6 |
7 | 10 | 13 |
14 | ) 15 | } 16 | 17 | export default Controls; 18 | -------------------------------------------------------------------------------- /src/hooks/useInterval.js: -------------------------------------------------------------------------------- 1 | // Original Source: https://overreacted.io/making-setinterval-declarative-with-react-hooks/ 2 | import { useEffect, useRef } from 'react' 3 | 4 | export function useInterval(callback, delay) { 5 | 6 | const savedCallback = useRef() 7 | // Remember the latest callback. 8 | useEffect(() => { 9 | savedCallback.current = callback 10 | }, [callback]) 11 | 12 | // Set up the interval. 13 | useEffect(() => { 14 | function tick() { 15 | savedCallback.current() 16 | } 17 | if (delay !== null) { 18 | let id = setInterval(tick, delay) 19 | return () => clearInterval(id) 20 | } 21 | }, [delay]) 22 | } 23 | -------------------------------------------------------------------------------- /src/components/TimeSet.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const TimeSet = ({ type, value }) => { 4 | const [val, setVal] = value 5 | const handleIncrement = () => { 6 | if (val >= 60) { 7 | return null 8 | } else { 9 | setVal(val + 1) 10 | } 11 | } 12 | const handleDecrement = () => { 13 | if (val === 1) { 14 | return null 15 | } else { 16 | setVal(val - 1) 17 | } 18 | } 19 | return ( 20 |
21 |

{type} Length

22 | 25 |

{val}

26 | 29 |
30 | ) 31 | } 32 | 33 | export default TimeSet; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Build-a-Pomodoro-Clock", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://freecodecamp-solutions.github.io/Build-a-Pomodoro-Clock/", 6 | "dependencies": { 7 | "gh-pages": "^2.2.0", 8 | "moment": "^2.29.1", 9 | "react": "^16.14.0", 10 | "react-dom": "^16.14.0", 11 | "react-fcctest": "^1.4.1", 12 | "react-scripts": "3.0.1", 13 | "use-timer": "^1.4.0" 14 | }, 15 | "scripts": { 16 | 17 | "predeploy": "npm run build", 18 | "deploy": "gh-pages -d build", 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject" 23 | }, 24 | "eslintConfig": { 25 | "extends": "react-app" 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | Pomodoro Clock 23 | 24 | 25 | 26 | 27 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react' 2 | import ReactFcctest from 'react-fcctest' 3 | import { useInterval } from '../hooks/useInterval' 4 | 5 | import TimeSet from './TimeSet' 6 | import Timer from './Timer' 7 | import Controls from './Controls' 8 | import alarm from '../sounds/beep.mp3' 9 | 10 | const App = () => { 11 | const [breakVal, setBreakVal] = useState(5) 12 | const [sessionVal, setSessionVal] = useState(25) 13 | const [mode, setMode] = useState('session') 14 | const [time, setTime] = useState(sessionVal * 60 * 1000) 15 | const [active, setActive] = useState(false) 16 | const beep = useRef() 17 | 18 | useInterval(() => setTime(time - 1000), active ? 1000 : null) 19 | 20 | useEffect(() => { 21 | setTime(sessionVal * 60 * 1000) 22 | }, [sessionVal]) 23 | 24 | useEffect(() => { 25 | if (time === 0 && mode === 'session') { 26 | beep.current.play() 27 | setMode('break') 28 | setTime(breakVal * 60 * 1000) 29 | } else if (time === 0 && mode === 'break') { 30 | beep.current.play() 31 | setMode('session') 32 | setTime(sessionVal * 60 * 1000) 33 | } 34 | }, [time, breakVal, sessionVal, mode]) 35 | 36 | const handleReset = () => { 37 | beep.current.pause() 38 | beep.current.currentTime = 0; 39 | setActive(false) 40 | setMode('session') 41 | setBreakVal(5) 42 | setSessionVal(25) 43 | setTime(25 * 60 * 1000) 44 | } 45 | 46 | return ( 47 |
48 |
49 |

Pomodoro Clock

50 |
51 |
52 |
53 | 54 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 65 |
67 | ) 68 | } 69 | 70 | export default App; 71 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=PT+Sans&display=swap'); 2 | *, 3 | *:before, 4 | *:after { 5 | box-sizing: border-box; 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | body { 11 | font-family: 'PT Sans', sans-serif; 12 | background-color: #eaf3f7; 13 | padding: 1rem; 14 | } 15 | 16 | .container { 17 | display: flex; 18 | flex-direction: column; 19 | align-items: center; 20 | height: 100vh; 21 | } 22 | 23 | header { 24 | align-self: flex-end; 25 | display: flex; 26 | justify-content: flex-end; 27 | } 28 | 29 | header h1 { 30 | text-transform: uppercase; 31 | color: #33c273; 32 | font-weight: 400; 33 | letter-spacing: 5px; 34 | } 35 | 36 | main { 37 | display: flex; 38 | flex-grow: 1; 39 | flex-direction: column; 40 | justify-content: center; 41 | align-items: center; 42 | width: 75vh; 43 | } 44 | 45 | .time-wrapper { 46 | background-color: #ffffff; 47 | width: 100%; 48 | padding: 1rem; 49 | box-shadow: 0 3px 6px #888888; 50 | } 51 | 52 | .time-wrapper h2 { 53 | text-align: right; 54 | font-size: 2rem; 55 | text-transform: uppercase; 56 | color: #7a7a7a; 57 | } 58 | 59 | .time-wrapper h3 { 60 | text-align: center; 61 | font-size: 7rem; 62 | color: #6dffac; 63 | font-weight: 400; 64 | } 65 | 66 | .controls-wrapper { 67 | width: 100%; 68 | display: flex; 69 | justify-content: flex-end; 70 | } 71 | 72 | .controls-wrapper button { 73 | font-size: 3rem; 74 | display: block; 75 | border: none; 76 | background-color: transparent; 77 | color: #7a7a7a; 78 | } 79 | 80 | .timeset-wrapper { 81 | margin: 1rem; 82 | width: 100%; 83 | display: flex; 84 | justify-content: space-between; 85 | } 86 | 87 | .timeset-wrapper .control { 88 | display: flex; 89 | flex-direction: column; 90 | justify-content: center; 91 | } 92 | 93 | .timeset-wrapper .control h2 { 94 | font-weight: 400; 95 | font-size: 1.5rem; 96 | color: #7a7a7a; 97 | margin-bottom: 1rem; 98 | } 99 | 100 | .timeset-wrapper .control h3 { 101 | text-align: center; 102 | font-weight: 400; 103 | font-size: 2rem; 104 | color: #7a7a7a; 105 | } 106 | 107 | .timeset-wrapper .control button { 108 | border: none; 109 | background-color: transparent; 110 | color: #33c273; 111 | font-size: 2rem; 112 | font-weight: 700; 113 | } 114 | 115 | @media only screen and (max-width: 600px) { 116 | main { 117 | width: 100%; 118 | } 119 | } 120 | --------------------------------------------------------------------------------