Skip to content

Commit 5a465cc

Browse files
committed
app: pomodoro app
1 parent a295cc6 commit 5a465cc

File tree

4 files changed

+176
-0
lines changed

4 files changed

+176
-0
lines changed

public/apps/apps.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,13 @@
259259
"title": "Stopwatch",
260260
"description": "A simple stopwatch with lap functionality.",
261261
"icon": "TimerIcon"
262+
},
263+
{
264+
"slug": "pomodoro-timer",
265+
"to": "/apps/pomodoro-timer",
266+
"title": "Pomodoro Timer",
267+
"description": "A simple and customizable Pomodoro timer to boost your productivity.",
268+
"icon": "TimerIcon"
262269
}
263270
]
264271
}

public/sounds/notification.mp3

83.5 KB
Binary file not shown.

src/components/AnimatedRoutes.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import TicTacToePage from '../pages/apps/TicTacToePage'; // Import TicTacToePage
5252
import ConnectFourPage from '../pages/apps/ConnectFourPage'; // Import ConnectFourPage
5353
import ImageCompressorPage from '../pages/apps/ImageCompressorPage'; // Import ImageCompressorPage
5454
import StopwatchAppPage from '../pages/StopwatchAppPage'; // Import StopwatchAppPage
55+
import PomodoroTimerPage from '../pages/apps/PomodoroTimerPage';
5556
import SettingsPage from '../pages/SettingsPage';
5657

5758
import UsefulLinksPage from '../pages/UsefulLinksPage';
@@ -529,6 +530,10 @@ function AnimatedRoutes() {
529530
path="/apps::sw"
530531
element={<Navigate to="/apps/stopwatch" replace />}
531532
/>
533+
<Route
534+
path="/apps::pomodoro"
535+
element={<Navigate to="/apps/pomodoro-timer" replace />}
536+
/>
532537
{/* End of hardcoded redirects */}
533538
<Route
534539
path="/apps/ip"
@@ -1007,6 +1012,20 @@ function AnimatedRoutes() {
10071012
</motion.div>
10081013
}
10091014
/>
1015+
<Route
1016+
path="/apps/pomodoro-timer"
1017+
element={
1018+
<motion.div
1019+
initial="initial"
1020+
animate="in"
1021+
exit="out"
1022+
variants={pageVariants}
1023+
transition={pageTransition}
1024+
>
1025+
<PomodoroTimerPage />
1026+
</motion.div>
1027+
}
1028+
/>
10101029
{/* D&D specific 404 page */}
10111030
<Route
10121031
path="/stories/*"
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import React, { useState, useEffect, useRef } from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { ArrowLeftIcon, PlayIcon, PauseIcon, ArrowCounterClockwiseIcon } from '@phosphor-icons/react';
4+
import useSeo from '../../hooks/useSeo';
5+
import { useToast } from '../../hooks/useToast';
6+
7+
const PomodoroTimerPage = () => {
8+
useSeo({
9+
title: 'Pomodoro Timer | Fezcodex',
10+
description: 'A simple and customizable Pomodoro timer to boost your productivity.',
11+
keywords: ['Fezcodex', 'pomodoro', 'timer', 'productivity'],
12+
ogTitle: 'Pomodoro Timer | Fezcodex',
13+
ogDescription: 'A simple and customizable Pomodoro timer to boost your productivity.',
14+
ogImage: 'https://fezcode.github.io/logo512.png',
15+
twitterCard: 'summary_large_image',
16+
twitterTitle: 'Pomodoro Timer | Fezcodex',
17+
twitterDescription: 'A simple and customizable Pomodoro timer to boost your productivity.',
18+
twitterImage: 'https://fezcode.github.io/logo512.png',
19+
});
20+
21+
const { addToast } = useToast();
22+
const [minutes, setMinutes] = useState(25);
23+
const [seconds, setSeconds] = useState(0);
24+
const [isActive, setIsActive] = useState(false);
25+
const [mode, setMode] = useState('work'); // 'work', 'shortBreak', 'longBreak'
26+
const [pomodoroCount, setPomodoroCount] = useState(0);
27+
const audioRef = useRef(null);
28+
29+
useEffect(() => {
30+
let interval = null;
31+
32+
if (isActive) {
33+
interval = setInterval(() => {
34+
if (seconds === 0) {
35+
if (minutes === 0) {
36+
if (audioRef.current) {
37+
audioRef.current.play();
38+
}
39+
40+
if (mode === 'work') {
41+
const newPomodoroCount = pomodoroCount + 1;
42+
setPomodoroCount(newPomodoroCount);
43+
addToast({ title: 'Pomodoro', message: "Work session finished! It's time for a break." });
44+
if (newPomodoroCount % 4 === 0) {
45+
selectMode('longBreak');
46+
} else {
47+
selectMode('shortBreak');
48+
}
49+
} else {
50+
addToast({ title: 'Pomodoro', message: "Break's over! Time to get back to work." });
51+
selectMode('work');
52+
}
53+
} else {
54+
setMinutes(minutes - 1);
55+
setSeconds(59);
56+
}
57+
} else {
58+
setSeconds(seconds - 1);
59+
}
60+
}, 1000);
61+
} else {
62+
clearInterval(interval);
63+
}
64+
65+
return () => clearInterval(interval);
66+
}, [isActive, seconds, minutes, mode, pomodoroCount, addToast]);
67+
68+
const toggleTimer = () => {
69+
setIsActive(!isActive);
70+
};
71+
72+
const resetTimer = () => {
73+
setIsActive(false);
74+
if (mode === 'work') {
75+
setMinutes(25);
76+
} else if (mode === 'shortBreak') {
77+
setMinutes(5);
78+
} else {
79+
setMinutes(15);
80+
}
81+
setSeconds(0);
82+
};
83+
84+
const selectMode = (newMode) => {
85+
setMode(newMode);
86+
setIsActive(false);
87+
switch (newMode) {
88+
case 'work':
89+
setMinutes(25);
90+
break;
91+
case 'shortBreak':
92+
setMinutes(5);
93+
break;
94+
case 'longBreak':
95+
setMinutes(15);
96+
break;
97+
default:
98+
setMinutes(25);
99+
}
100+
setSeconds(0);
101+
};
102+
103+
const formatTime = (time) => (time < 10 ? `0${time}` : time);
104+
105+
return (
106+
<div className="py-16 sm:py-24">
107+
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-gray-300">
108+
<Link to="/apps" className="text-article hover:underline flex items-center justify-center gap-2 text-lg mb-4">
109+
<ArrowLeftIcon size={24} /> Back to Apps
110+
</Link>
111+
<h1 className="text-4xl font-bold tracking-tight sm:text-6xl mb-4 flex items-center">
112+
<span className="codex-color">fc</span>
113+
<span className="separator-color">::</span>
114+
<span className="apps-color">apps</span>
115+
<span className="separator-color">::</span>
116+
<span className="single-app-color">pomodoro</span>
117+
</h1>
118+
<hr className="border-gray-700" />
119+
<div className="flex justify-center items-center mt-16">
120+
<div className="bg-app-alpha-10 border-app-alpha-50 text-app hover:bg-app/15 group border rounded-lg shadow-2xl p-6 flex flex-col justify-between relative transform transition-all duration-300 ease-in-out scale-105 overflow-hidden h-full w-full max-w-md">
121+
<div className="absolute top-0 left-0 w-full h-full opacity-10" style={{ backgroundImage: 'radial-gradient(circle, white 1px, transparent 1px)', backgroundSize: '10px 10px' }}></div>
122+
<div className="relative z-10 p-4 text-center">
123+
<h1 className="text-3xl font-arvo font-normal mb-4 text-app">Pomodoro Timer</h1>
124+
<hr className="border-gray-700 mb-6" />
125+
<div className="flex justify-center gap-4 mb-6">
126+
<button onClick={() => selectMode('work')} className={`px-4 py-2 rounded-md font-semibold ${mode === 'work' ? 'bg-app text-white' : 'bg-tb text-app border-app-alpha-50 hover:bg-app/15 border'}`}>Pomodoro</button>
127+
<button onClick={() => selectMode('shortBreak')} className={`px-4 py-2 rounded-md font-semibold ${mode === 'shortBreak' ? 'bg-app text-white' : 'bg-tb text-app border-app-alpha-50 hover:bg-app/15 border'}`}>Short Break</button>
128+
<button onClick={() => selectMode('longBreak')} className={`px-4 py-2 rounded-md font-semibold ${mode === 'longBreak' ? 'bg-app text-white' : 'bg-tb text-app border-app-alpha-50 hover:bg-app/15 border'}`}>Long Break</button>
129+
</div>
130+
<div className="text-8xl font-mono font-bold text-app-light mb-8">
131+
{formatTime(minutes)}:{formatTime(seconds)}
132+
</div>
133+
<div className="flex justify-center gap-6">
134+
<button onClick={toggleTimer} className="p-4 rounded-full bg-tb text-app border-app-alpha-50 hover:bg-app/15 border">
135+
{isActive ? <PauseIcon size={32} /> : <PlayIcon size={32} />}
136+
</button>
137+
<button onClick={resetTimer} className="p-4 rounded-full bg-tb text-app border-app-alpha-50 hover:bg-app/15 border">
138+
<ArrowCounterClockwiseIcon size={32} />
139+
</button>
140+
</div>
141+
</div>
142+
</div>
143+
</div>
144+
</div>
145+
<audio ref={audioRef} src="/sounds/notification.mp3" />
146+
</div>
147+
);
148+
};
149+
150+
export default PomodoroTimerPage;

0 commit comments

Comments
 (0)