├── 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 |
51 |
52 |
53 |
54 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
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 |
--------------------------------------------------------------------------------