Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c72947c
Dodanie pliku Powiadomienia.tsx i edytowanie pliku footer_Ustawienia.tsx
pavlovskakristina Mar 19, 2026
9218af1
Dodanie panelu powiadomień i poprawki w ustawieniach
pavlovskakristina Mar 20, 2026
9f1f8cc
feat: Dodanie przycisku powrotu do ustawień
pavlovskakristina Mar 27, 2026
21cde4f
Merge branch 'dev' into feature/app-setting-panel--notifications
pavlovskakristina Mar 27, 2026
9558b72
feat: dodanie trwałego zapisu ustawień powiadomień w cookies
pavlovskakristina Mar 29, 2026
351fe2c
feat: useNotifications - blokowanie powiadomien na podstawie cookies
pavlovskakristina Mar 29, 2026
06d6d59
feat: Powiadomienia - aktualizacja tytulow powiadomien
pavlovskakristina Mar 29, 2026
7c9675c
feat: Utworzenie pliku z konfiguracja i mapowaniem notificationConfig.ts
pavlovskakristina Apr 1, 2026
e909566
feat: integracja wspólnej konfiguracji powiadomień w hooku i widoku
pavlovskakristina Apr 1, 2026
4a8ca41
refactor: ujednolicenie wielkości liter (/powiadomienia)
pavlovskakristina Apr 16, 2026
21f5763
refactor: zastpienie biblioteki js-cookie natywnym rozwiazaniem
pavlovskakristina Apr 18, 2026
95e6da6
Merge remote-tracking branch 'origin/dev' into feature/app-setting-pa…
pavlovskakristina Apr 18, 2026
5405501
feat: dodanie mozliwosci sprawdzenia, czy cookie dzialaja zgodnie z z…
pavlovskakristina Apr 18, 2026
f10d0bb
Odinstalowanie js-cookie i ts-cookie
pavlovskakristina Apr 18, 2026
7b16626
fix: Naprawa przycisku 'zezwól na powiadomienia' - znika po kliknięciu
pavlovskakristina Apr 18, 2026
a8db91f
fix: Naprawa przycisku 'zezwól na powiadomienia' - hook
pavlovskakristina Apr 18, 2026
639598c
feat: Dodanie przy kazdym powiadomieniu body (pochodzenie)
pavlovskakristina Apr 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
"dependencies": {
"@radix-ui/react-slot": "^1.2.4",
"@tailwindcss/vite": "^4.1.18",
"@types/js-cookie": "^3.0.6",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"js-cookie": "^3.0.5",
"lucide-react": "^0.563.0",
"radix-ui": "^1.4.3",
"react": "^19.2.0",
Expand Down
25 changes: 25 additions & 0 deletions src/hooks/useNotifications.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import { useState } from "react";
import { NOTIFICATION_TITLE_TO_ID } from "../lib/notificationConfig";

// Interfejs dla powiadomienia
export interface AppNotification {
title: string;
options?: NotificationOptions;
}

const COOKIE_KEY = "user_notifications_preferences";

// COOKIES
function getCookie(key: string): string | undefined {
return document.cookie.split("; ")
.find(row => row.startsWith(key + "="))
?.split("=")[1];
}

export const useNotifications = () => {
const [permission, setPermission] =
useState<NotificationPermission>("default");
Expand All @@ -23,6 +33,21 @@ export const useNotifications = () => {
return;
}

// Sprawdenie czy ten typ powiadomienia jest włączony w COOKIES
const id = NOTIFICATION_TITLE_TO_ID[notification.title];
if (id) {
const saved = getCookie(COOKIE_KEY);
if (saved) {
try {
const prefs = JSON.parse(saved);
// powiadomienie zostaje wyłączone przez użytkowika - nie wysyla sie
if (prefs[id] === false) return;
} catch (error) {
console.error("Błąd podczas odczytywania cookies:", error);
}
}
}

try {
if ("serviceWorker" in navigator) {
const registration = await navigator.serviceWorker.ready;
Expand Down
17 changes: 17 additions & 0 deletions src/lib/notificationConfig.ts

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Z tego co rozumiem to jest tylko setting tego, co ma być wyświetlane w tej podstronie?
  2. Tworzysz swój interface AppNotificationConfig - ale już istnieje coś bardzo podobnego: src/hooks/useTimeNotification.ts
  3. To po prostu są na razie wyłączniki, które tylko zmieniają i przechowują stan :/

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hej
notificationConfig.ts to współdzielona konfiguracja dla całej aplikacji.
NOTIFICATIONS jest używane w Powiadomienia.tsx do renderowania listy ( czyli to miejsce tak naprawdę odpowiada za to, co widzi użytkownik w powiadomieniach). 
A NOTIFICATION_TITLE_TO_ID w useNotifications.ts do sprawdzania preferencji przed wysłaniem powiadomienia.
Gdybym dała to do useTimeNotification.ts to najprawdopodobniej pojawiłby się błąd. Bo useTimeNotification.ts odpowiada za obliczanie czasu, a nie za sprawdzanie preferencji użytkownika. (czyli plik Powiadomienia.tsx musiałby wtedy importować konfigurację z hooka timera)

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export interface AppNotificationConfig {
id: string;
title: string;
}

export const NOTIFICATIONS: AppNotificationConfig[] = [
{ id: "1", title: "Cykliczne powiadomienie ⏰" },
{ id: "2", title: "Czas na przerwę! ☕" },
{ id: "3", title: "Czas na ćwiczenie! 🔵" },
{ id: "4", title: "Zadbaj o nawodnienie! 💧" },
];

// Mapowanie tytułu na id – używane w useNotifications
export const NOTIFICATION_TITLE_TO_ID: Record<string, string> = Object.fromEntries(
NOTIFICATIONS.map((n) => [n.title, n.id])
);

150 changes: 150 additions & 0 deletions src/pages/Powiadomienia.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { Bell, SquareArrowLeft } from "lucide-react";
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { NOTIFICATIONS } from "../lib/notificationConfig";


interface UstawieniaPowiadomien {
id: string;
title: string;
active: boolean;
icon: React.ReactNode;
}

// COOKIES
const COOKIE_KEY = "user_notifications_preferences";

function getCookie(key: string): string | undefined {
return document.cookie.split("; ")
.find(row => row.startsWith(key + "="))
?.split("=")[1];
}

function setCookie(key: string, value: string, days: number): void {
const expires = new Date();
expires.setDate(expires.getDate() + days);
document.cookie = `${key}=${value}; expires=${expires.toUTCString()}; path=/`;
}


function Powiadomienia() {

// Sprawdzamy czy przeglądarka obsługuje powiadomienia
const [pushSupported, setPushSupported] = useState<boolean | null>(null);

useEffect(() => {
const supported = "Notification" in window
&& "serviceWorker" in navigator
&& "PushManager" in window;
setPushSupported(supported);
}, []);

// Stan przechowujący wybory użytkownika
const [settings, setSettings] = useState<UstawieniaPowiadomien[]>(() => {
const savedCookies = getCookie(COOKIE_KEY);

const defaultSettings = NOTIFICATIONS.map((n) => ({
...n,
active: true,
icon: <Bell size={17} />,
}));


if (savedCookies) {
try {
const parsed = JSON.parse(savedCookies);
// Łączymy ikony z zapisanymi stanami powiadomien
return defaultSettings.map(ds => ({
...ds,
active: parsed[ds.id] ?? ds.active
}));
} catch (error) {
// W przypadku wystąpienia błędu przy cookies, ustawiamy domyśne ustawienia
console.error("Błąd podczas odczytywania cookies:", error); // wyswietlenie bledu
return defaultSettings;
}
}
return defaultSettings;
});

// Funkcja przełączająca stan konkretnego powiadomienia i zapisująca nowy stan do COOKIES
const toggle = (id: string) => {
const newSettings = settings.map(item =>
item.id === id ? { ...item, active: !item.active } : item
);

setSettings(newSettings);


// Zapisywanie uproszczonego obiektu do COOKIES {id, active}
const configToSave = newSettings.reduce((acc, curr) => ({
...acc, [curr.id]: curr.active
}), {});

// Po 365 dniach COOKIES zostaje usuniete
setCookie(COOKIE_KEY, JSON.stringify(configToSave), 365);
};

return (
<div className="min-h-screen w-full bg-gray-100 flex flex-col items-center p-10 relative">

{/* Przycisk powrotu */}
<Link
to="/Ustawienia"
className="absolute left-6 top-10 p-3 bg-white text-black rounded-full shadow-lg active:scale-95 transition-all hover:bg-gray-50"
>
<SquareArrowLeft size={20} />
</Link>

{/* Nagłówek strony */}
<div className="w-full max-w-md flex items-center mb-8">
<h1 className="text-4xl font-bold text-gray-800">Powiadomienia</h1>
</div>

{/* Lista opcji do wyboru */}
<div className="w-full max-w-md space-y-3">
{settings.map((item) => (
<div
key={item.id}
onClick={() => toggle(item.id)}
className="flex items-center justify-between p-4 bg-white rounded-2xl shadow-sm cursor-pointer active:bg-gray-50 transition-colors"
>
{/* Ikonka i naglowek powiadomienia */}
<div className="flex items-center gap-4">
<div className="p-2 bg-blue-50 text-blue-600 rounded-lg">
{item.icon}
</div>

<span className="font-medium text-gray-600">
{item.title}</span>
</div>

{/* Toggle - przycisk switch */}
<div className={`w-12 h-6 rounded-full transition-colors relative ${item.active ? 'bg-green-500' : 'bg-gray-300'}`}>
<div className={`absolute top-1 w-4 h-4 bg-white rounded-full transition-all ${item.active ? 'left-7' : 'left-1'}`} />
</div>
</div>
))}
</div>

<div className="mt-auto mb-6 text-center px-4">

{pushSupported === false ? ( // przeglądarka nie obsługuje push
<p className="text-red-400 text-sm">
Twoja przeglądarka nie obsługuje powiadomień push.
</p>
)
: pushSupported === true && Notification.permission !== "granted" ? ( // przeglądarka obsługuje push
<button
onClick={() => Notification.requestPermission()}
className="px-4 py-2 bg-blue-500 text-white rounded-xl text-sm"
>
Włącz powiadomienia
</button>
) : null}
</div>
</div>
);
}

export default Powiadomienia
Loading