Skip to content

Commit

Permalink
add cookies to store a user's customization preferences
Browse files Browse the repository at this point in the history
  • Loading branch information
marinoffDev committed Oct 13, 2024
1 parent c12d91a commit 8a217f7
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 68 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Future plans for enhancements and development:
- [x] Add more notification sound options
- [x] Add open graph meta tags
- [x] Add option to enable browser notifications
- [ ] Add cookies to store a user's customization preferences
- [x] Add cookies to store a user's customization preferences
- [ ] Add option to start subsequent timers automatically
- [ ] Add SSG build step before deployment
- [ ] Add toggle for "super dark mode" while a timer is active
Expand Down
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@radix-ui/react-tabs": "^1.0.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"js-cookie": "^3.0.5",
"lucide-react": "^0.378.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
123 changes: 62 additions & 61 deletions src/components/Customize.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useState, useEffect } from "react";
import {
Dialog,
DialogContent,
Expand All @@ -20,40 +20,38 @@ import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faGear } from "@fortawesome/free-solid-svg-icons";
import TimerSlider from "@/components/ui/TimerSlider";
import { defaultSettings } from "@/lib/defaultSettings"
import { notificationSounds } from "@/lib/notificationSounds"
import TimerSlider from "@/components/ui/TimerSlider";

export default function Customize({ timerSettings, onSaveTimerSettings }) {
const [open, setOpen] = useState(false);
const [settings, setSettings] = useState({
pomodoro: timerSettings.pomodoro / 60,
shortBreak: timerSettings.shortBreak / 60,
longBreak: timerSettings.longBreak / 60,
sessionRounds: timerSettings.sessionRounds,
notificationSound: timerSettings.notificationSound
});

const [settings, setSettings] = useState(timerSettings);

useEffect(() => {
setSettings({
setSettings((prevSettings) => ({
...prevSettings,
pomodoro: timerSettings.pomodoro / 60,
shortBreak: timerSettings.shortBreak / 60,
longBreak: timerSettings.longBreak / 60,
sessionRounds: timerSettings.sessionRounds,
notificationSound: timerSettings.notificationSound
});
notificationSound: timerSettings.notificationSound,
}));
}, [timerSettings]);

// Handle changes in settings
const handleChange = (type, value) => {
setSettings((prev) => ({ ...prev, [type]: value }));
setSettings((prevSettings) => ({ ...prevSettings, [type]: value }));
};

// Handle notification sound change and play preview sound
const handleSoundChange = (value) => {
setSettings((prev) => ({ ...prev, notificationSound: value }));
setSettings((prevSettings) => ({ ...prevSettings, notificationSound: value }));
const sound = new Audio(value);
sound.play();
};

// When resetting to default, always fallback to the hardcoded default values
const handleReset = () => {
setSettings({
pomodoro: defaultSettings.pomodoro / 60,
Expand All @@ -64,6 +62,7 @@ export default function Customize({ timerSettings, onSaveTimerSettings }) {
});
};

// Only save user preferences if they actually click the Save Changes button
const handleSave = () => {
onSaveTimerSettings({
pomodoro: settings.pomodoro * 60,
Expand All @@ -77,7 +76,7 @@ export default function Customize({ timerSettings, onSaveTimerSettings }) {

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild={true}>
<DialogTrigger asChild>
<Button variant="ghost">
<FontAwesomeIcon icon={faGear} size="lg" className="mr-2" />Customize
</Button>
Expand All @@ -87,51 +86,53 @@ export default function Customize({ timerSettings, onSaveTimerSettings }) {
<DialogTitle className="mb-2">Settings</DialogTitle>
<Separator />
</DialogHeader>
<DialogDescription>
<TimerSlider
label="Pomodoro"
unit="minute"
value={settings.pomodoro}
min={1}
max={60}
onChange={(value) => handleChange("pomodoro", value)}
/>
<TimerSlider
label="Short Break"
unit="minute"
value={settings.shortBreak}
min={1}
max={30}
onChange={(value) => handleChange("shortBreak", value)}
/>
<TimerSlider
label="Long Break"
unit="minute"
value={settings.longBreak}
min={1}
max={60}
onChange={(value) => handleChange("longBreak", value)}
/>
<TimerSlider
label="Session Rounds"
unit="round"
value={settings.sessionRounds}
min={1}
max={10}
onChange={(value) => handleChange("sessionRounds", value)}
/>
<div className="mt-5 flex items-center gap-4">
<Label className="text-nowrap">Notification Sound</Label>
<Select className="w-full" onValueChange={handleSoundChange} value={settings.notificationSound}>
<SelectTrigger>
<SelectValue>{Object.keys(notificationSounds).find(key => notificationSounds[key] === settings.notificationSound) || "Choose a sound..."}</SelectValue>
</SelectTrigger>
<SelectContent>
{Object.entries(notificationSounds).map(([key, value]) => (
<SelectItem key={value} value={value}>{key}</SelectItem>
))}
</SelectContent>
</Select>
<DialogDescription asChild>
<div>
<TimerSlider
label="Pomodoro"
unit="minute"
value={settings.pomodoro}
min={1}
max={60}
onChange={(value) => handleChange("pomodoro", value)}
/>
<TimerSlider
label="Short Break"
unit="minute"
value={settings.shortBreak}
min={1}
max={30}
onChange={(value) => handleChange("shortBreak", value)}
/>
<TimerSlider
label="Long Break"
unit="minute"
value={settings.longBreak}
min={1}
max={60}
onChange={(value) => handleChange("longBreak", value)}
/>
<TimerSlider
label="Session Rounds"
unit="round"
value={settings.sessionRounds}
min={1}
max={10}
onChange={(value) => handleChange("sessionRounds", value)}
/>
<div className="mt-5 flex items-center gap-4">
<Label className="text-nowrap">Notification Sound</Label>
<Select className="w-full" onValueChange={handleSoundChange} value={settings.notificationSound}>
<SelectTrigger>
<SelectValue>{Object.keys(notificationSounds).find(key => notificationSounds[key] === settings.notificationSound) || "Choose a sound..."}</SelectValue>
</SelectTrigger>
<SelectContent>
{Object.entries(notificationSounds).map(([key, value]) => (
<SelectItem key={value} value={value}>{key}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</DialogDescription>
<DialogFooter className="mt-4 flex justify-center gap-2">
Expand Down
41 changes: 37 additions & 4 deletions src/routes/App.jsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,61 @@
import { useRef, useState } from "react";
import { useRef, useState, useEffect } from "react";
import Navbar from "@/components/Navbar.jsx";
import Timer from "@/components/Timer.jsx";
import Footer from "@/components/Footer.jsx";
import HowToUse from "@/components/HowToUse.jsx";
import { defaultSettings } from "@/lib/defaultSettings"
import Cookies from "js-cookie";

function App() {
const refs = {
navbar: useRef(),
howToUse: useRef(),
};

const [timerSettings, setTimerSettings] = useState(defaultSettings);

useEffect(() => {
const loadSettingsFromCookies = () => {
const pomodoroCookie = Cookies.get("pomodoro");
const shortBreakCookie = Cookies.get("shortBreak");
const longBreakCookie = Cookies.get("longBreak");
const sessionRoundsCookie = Cookies.get("sessionRounds");
const notificationSoundCookie = Cookies.get("notificationSound");

if (pomodoroCookie && shortBreakCookie && longBreakCookie && sessionRoundsCookie && notificationSoundCookie) {
setTimerSettings((prevSettings) => ({
...prevSettings,
pomodoro: Number(pomodoroCookie),
shortBreak: Number(shortBreakCookie),
longBreak: Number(longBreakCookie),
sessionRounds: Number(sessionRoundsCookie),
notificationSound: notificationSoundCookie,
}));
}
};

loadSettingsFromCookies();
}, []);

const handleSaveTimerSettings = (newSettings) => {
setTimerSettings(newSettings);
Cookies.set("pomodoro", newSettings.pomodoro, { expires: 365 });
Cookies.set("shortBreak", newSettings.shortBreak, { expires: 365 });
Cookies.set("longBreak", newSettings.longBreak, { expires: 365 });
Cookies.set("sessionRounds", newSettings.sessionRounds, { expires: 365 });
Cookies.set("notificationSound", newSettings.notificationSound, { expires: 365 });
};

const handleScrollToSection = (section) => {
refs[section]?.current?.scrollIntoView({ behavior: "smooth" });
};

const [timerSettings, setTimerSettings] = useState(defaultSettings);

return (
<>
<Navbar
ref={refs.navbar}
scrollToHowToUse={() => handleScrollToSection("howToUse")}
onSaveTimerSettings={(newSettings) => setTimerSettings(newSettings)}
onSaveTimerSettings={handleSaveTimerSettings}
timerSettings={timerSettings}
/>
<main>
Expand Down
4 changes: 2 additions & 2 deletions src/routes/Privacy.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function Privacy() {
navigateToRoute={"/Focus-Fox/Terms/"}
navigateToRouteLabel={"Terms and Conditions"}
pageTitle={"Privacy Policy"}
effectiveDateText={"October 5th 2024"}
effectiveDateText={"October 13th 2024"}
>
<p className="py-2">
Focus Fox ({`"we", "us", or "our"`}) provides a web-based Pomodoro technique timer at <Link to="/Focus-Fox/" className="underline">https://marinoffdev.github.io/Focus-Fox/</Link> (the {`"Service"`}). This Privacy Policy outlines what data is collected, how it is used, and your choices regarding that data.
Expand All @@ -19,7 +19,7 @@ export default function Privacy() {
</p>
<h2 className="text-3xl font-bold pt-4">Cookies and Preferences</h2>
<p className="py-2">
{`Focus Fox currently does not use cookies. However, in the future, we plan to implement cookies to store your Pomodoro timer settings, theme preferences, and notification sound choices. These cookies will be used solely to improve your experience by saving your preferences, ensuring you don't have to reconfigure settings on each visit.`}
{`Focus Fox employs browser cookies that are purely functional, enabling the site to remember your customized timer settings, theme, and notification preferences. These cookies do not track your browsing activity, are not used for advertising purposes, and do not collect any personal information.`}
</p>
<h2 className="text-3xl font-bold pt-4">External Links and Services</h2>
<p className="py-2">
Expand Down

0 comments on commit 8a217f7

Please sign in to comment.