diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 89d4ca253..1fe000ee3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25831,7 +25831,7 @@ "@types/warning": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" + "integrity": "sha512-t/Tvs5qR47OLOr+4E9ckN8AmP2Tf16gWq+/qA4iUGS/OOyHVO8wv2vjJuX8SNOUTJyWb+2t7wJm6cXILFnOROA==" }, "@types/webpack": { "version": "4.41.31", @@ -31536,7 +31536,7 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" }, "isexe": { "version": "2.0.0", @@ -36818,7 +36818,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" }, "strip-ansi": { "version": "3.0.1", @@ -38807,6 +38807,13 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" }, + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "optional": true, + "peer": true + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -39482,7 +39489,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" }, "binary-extensions": { "version": "1.13.1", diff --git a/frontend/src/components/Notification/Message.tsx b/frontend/src/components/Notification/Message.tsx new file mode 100644 index 000000000..4415d3162 --- /dev/null +++ b/frontend/src/components/Notification/Message.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import styles from './notification.module.css'; +import moment from 'moment'; +import { useState } from 'react'; +import { notificationBadge } from '../../icons/other'; + +type Message = { + key: number; + time: Date; + title: string; + body: string; + read: boolean; + markAsRead: (key: number) => void; +}; + +/** + * Truncates a string to a maximum length of 30 characters. + * If the string is longer than 30 characters, it appends '...' to the end. + * + * @param {string} str - The string to be truncated. + * @returns {string} - The truncated string. If the original string is 30 characters or fewer, it returns the original string. + */ +function truncate(str: string) { + if (str.length <= 30) { + return str; + } + return `${str.slice(0, 30)}...`; +} + +const DisplayMessage = ({ + key, + time, + title, + body, + read, + markAsRead, +}: Message) => { + const [trunc, setTrunc] = useState(body.length > 30); + const [viewed, setViewed] = useState(read); + const onViewClick = () => { + setViewed(true); + markAsRead(key); + setTrunc((prev) => !prev); + }; + return ( +
+ {viewed ? null : ( + notification badge + )} +
+
+ {title.slice(0, 1)} +
+
+
+

{moment(time).format('MMMM Do')}

+

{trunc ? truncate(body) : body}

+
+
+ {trunc ? 'View More' : 'Less'} +
+
+ ); +}; + +export default DisplayMessage; diff --git a/frontend/src/components/Notification/Notification.tsx b/frontend/src/components/Notification/Notification.tsx index 0387e6006..09d1fcda0 100644 --- a/frontend/src/components/Notification/Notification.tsx +++ b/frontend/src/components/Notification/Notification.tsx @@ -7,11 +7,14 @@ import styles from './notification.module.css'; import 'reactjs-popup/dist/index.css'; import { notificationBadge, notificationBell } from '../../icons/other'; import { Ride } from '../../types'; +import DisplayMessage from './Message'; type Message = { + key: number; time: Date; title: string; body: string; + read: boolean; }; type NotificationData = { @@ -21,27 +24,29 @@ type NotificationData = { sentTime: string; }; -const truncate = (str: string, num: number) => { - if (str.length <= num) { - return str; - } - return `${str.slice(0, num)}...`; -}; - const Notification = () => { const [newMessages, setNewMessages] = useState([]); const [messages, setMessages] = useState([]); const [notify, setNotify] = useState(false); const popupId = useId(); + const markAsRead = (key: number) => { + setMessages((prevMessages) => + prevMessages.map((message) => + message.key === key ? { ...message, read: true } : message + ) + ); + }; useEffect(() => { navigator.serviceWorker.addEventListener('message', (event) => { const { body, ride, sentTime, title }: NotificationData = event.data; const newMsg = { + key: newMessages.length + 1, time: new Date(sentTime), title, body, day: ride.startTime, + read: false, }; setNewMessages([newMsg, ...newMessages]); setNotify(true); @@ -49,19 +54,8 @@ const Notification = () => { }, []); const mapMessages = (msgs: Message[]) => - msgs.map(({ time, title, body }, i) => ( -
-
-
- C -
-
-
-

{moment(time).format('MMMM Do')}

-

{body}

-
-
View
-
+ msgs.map((message) => ( + )); useEffect(() => { diff --git a/frontend/src/components/Notification/notification.module.css b/frontend/src/components/Notification/notification.module.css index aac4c55a3..121be2936 100644 --- a/frontend/src/components/Notification/notification.module.css +++ b/frontend/src/components/Notification/notification.module.css @@ -10,8 +10,18 @@ } .badge { - width: 44px; - height: 44px; + position: absolute; + right: 6px; + top: 3px; + width: 10px; + height: 10px; +} + +.messageBadge { + position: absolute; + left: 15px; + width: 10px; + height: 10px; } .body { @@ -66,6 +76,7 @@ } .link { + cursor: pointer; font-size: 10px; color: #0084f4; } diff --git a/frontend/src/components/Sidebar/Sidebar.tsx b/frontend/src/components/Sidebar/Sidebar.tsx index a0f01d533..705d1a99e 100644 --- a/frontend/src/components/Sidebar/Sidebar.tsx +++ b/frontend/src/components/Sidebar/Sidebar.tsx @@ -89,6 +89,8 @@ const Sidebar = ({ type, children }: SidebarProps) => { key={path} onClick={() => setSelected(path)} className={styles.icon} + aria-current={path === selected ? 'page' : undefined} + aria-label={caption} to={path} >
{ : styles.circle } > - - {''} + {caption}
{caption}