EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
Notifications.jsx
Go to the documentation of this file.
1import { useEffect, useState } from 'react';
2import MainLayout from '../components/Layout/MainLayout';
3import { apiFetch } from '../lib/api';
4
5function Notifications() {
6 const [notes, setNotes] = useState([]);
7 const [loading, setLoading] = useState(false);
8 const [error, setError] = useState('');
9
10 async function fetchNotes() {
11 setLoading(true);
12 setError('');
13 try {
14 const token = localStorage.getItem('token');
15 const { apiUrl } = await import('../lib/api');
16 const url = apiUrl('/notifications');
17 const res = await apiFetch(url.toString(), { headers: { Authorization: `Bearer ${token}` } });
18 if (!res.ok) throw new Error('Failed to load notifications');
19 const data = await res.json();
20 setNotes(data);
21 } catch (err) {
22 console.error(err);
23 setError(err.message);
24 } finally {
25 setLoading(false);
26 }
27 }
28
29 useEffect(() => { fetchNotes(); }, []);
30
31 const markRead = async (id) => {
32 try {
33 const token = localStorage.getItem('token');
34 const { apiUrl } = await import('../lib/api');
35 const url = apiUrl(`/notifications/${id}/mark-read`);
36 const res = await apiFetch(url.toString(), { method: 'PUT', headers: { Authorization: `Bearer ${token}` } });
37 if (!res.ok) throw new Error('Failed');
38 setNotes(n => n.map(x => x.notification_id === id ? { ...x, is_read: true } : x));
39 } catch (err) {
40 console.error(err);
41 }
42 };
43
44 const dismissNote = async (id) => {
45 if (!window.confirm('Dismiss this notification?')) return;
46 try {
47 const token = localStorage.getItem('token');
48 const { apiUrl } = await import('../lib/api');
49 const url = apiUrl(`/notifications/${id}`);
50 const res = await apiFetch(url.toString(), { method: 'DELETE', headers: { Authorization: `Bearer ${token}` } });
51 if (!res.ok) throw new Error('Failed to dismiss notification');
52 setNotes(n => n.filter(x => x.notification_id !== id));
53 } catch (err) {
54 console.error(err);
55 alert('Failed to dismiss notification');
56 }
57 };
58
59 return (
60 <MainLayout>
61 <div className="page-content">
62 <div className="page-header"><h2>Notifications</h2></div>
63 {loading && <div className="loading">Loading...</div>}
64 {error && <div className="error-message">{error}</div>}
65 <div className="notifications-list" style={{ maxWidth: 600, margin: '0 auto' }}>
66 {notes.length === 0 ? (
67 <div style={{ color: '#888', textAlign: 'center', marginTop: 32 }}>
68 <span className="material-symbols-outlined" style={{ fontSize: 48, opacity: 0.3 }}>notifications_off</span>
69 <div>No notifications</div>
70 </div>
71 ) : (
72 notes.map(n => (
73 <div key={n.notification_id} style={{
74 padding: 16,
75 borderBottom: '1px solid #eee',
76 background: n.is_read ? '#fafafa' : '#fff',
77 display: 'flex',
78 flexDirection: 'column',
79 gap: 6,
80 position: 'relative'
81 }}>
82 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
83 <div style={{ fontWeight: 'bold', fontSize: '1.1em' }}>{n.title}</div>
84 <div style={{ fontSize: '0.95em', color: '#888' }}>{new Date(n.created_at).toLocaleString()}</div>
85 </div>
86 <div style={{ marginTop: 2 }}>{n.message}</div>
87 <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
88 {!n.is_read && (
89 <button className="btn btn-sm" onClick={() => markRead(n.notification_id)}>
90 Mark read
91 </button>
92 )}
93 <button className="btn btn-sm btn-danger" onClick={() => dismissNote(n.notification_id)}>
94 Dismiss
95 </button>
96 </div>
97 </div>
98 ))
99 )}
100 </div>
101 </div>
102 </MainLayout>
103 );
104}
105
106export default Notifications;