diff --git a/package-lock.json b/package-lock.json index 30af3dc..914ffa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.3.0", - "react-router-dom": "^6.26.0" + "react-router-dom": "^6.26.0", + "react-toastify": "^10.0.5" }, "devDependencies": { "@nabla/vite-plugin-eslint": "^2.0.4", @@ -5001,6 +5002,14 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -8980,6 +8989,18 @@ "react-dom": ">=16.8" } }, + "node_modules/react-toastify": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz", + "integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index 1d10348..d6316a9 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.3.0", - "react-router-dom": "^6.26.0" + "react-router-dom": "^6.26.0", + "react-toastify": "^10.0.5" }, "devDependencies": { "@nabla/vite-plugin-eslint": "^2.0.4", diff --git a/src/api/firebase.js b/src/api/firebase.js index 7fd8e7f..3644b57 100644 --- a/src/api/firebase.js +++ b/src/api/firebase.js @@ -173,7 +173,7 @@ export async function shareList(listPath, currentUserId, recipientEmail) { sharedLists: arrayUnion(listDocumentRef), }); return Promise.resolve( - `You have successfully shared your list to ${recipientEmail}`, + `You have successfully shared your list to: \n${recipientEmail}`, ); } diff --git a/src/components/ListItem.jsx b/src/components/ListItem.jsx index 8121825..468023e 100644 --- a/src/components/ListItem.jsx +++ b/src/components/ListItem.jsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { useToggle } from '@uidotdev/usehooks'; import { Toggle } from './Toggle.jsx'; +import { notify } from '../utils/notifications'; import './ListItem.css'; import { updateItem, deleteItem } from '../api/firebase.js'; import { FaTrashAlt } from 'react-icons/fa'; @@ -15,7 +16,6 @@ export function ListItem({ purchaseInterval, dateCreated, sortCriteria, - setMessage, }) { const [purchased, setPurchased] = useToggle(false); const [isDisabled, setIsDisabled] = useState(false); @@ -55,7 +55,7 @@ export function ListItem({ dateCreated, }); console.log(`${name} updated successfully`); - alert(`${name} is purchased successfully`); + notify(`${name} has been purchased successfully!`, 'success'); setIsDisabled(true); } catch (error) { console.error('Error updating item:', error); @@ -64,7 +64,6 @@ export function ListItem({ } }; - // handleDelete Function const handleDelete = async () => { const deleteConfirm = window.confirm( `Are you sure you want to delete ${name}?`, @@ -73,7 +72,7 @@ export function ListItem({ if (deleteConfirm) { try { await deleteItem(listPath, itemId); - setMessage(`${name} has been deleted successfully!`); + notify(`${name} has been deleted successfully!`, 'success'); } catch (error) { console.log(`Error:`, error); } @@ -95,15 +94,11 @@ export function ListItem({ {name}
{sortCriteria.tag}
- {/* - Delete - */} diff --git a/src/index.css b/src/index.css index 346a5a6..327d388 100644 --- a/src/index.css +++ b/src/index.css @@ -18,7 +18,6 @@ --color-border: hsla(220, 13%, 32%, 1); --color-error: var(--color-red); --color-text: var(--color-white); - /** * Set the value of 1rem to 10px to make relative units * easier to work with. @@ -103,3 +102,7 @@ code { :root.theme-light code { --color-bg: var(--color-gray-light); } + +.share-div { + padding-bottom: 100px; +} diff --git a/src/utils/notifications.js b/src/utils/notifications.js new file mode 100644 index 0000000..ecef3e4 --- /dev/null +++ b/src/utils/notifications.js @@ -0,0 +1,18 @@ +import { toast } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; + +/** + * calls a toast notification to pop up + * @param {string} text The text that should appear in the notification + * @param {string} type The styling for the notification: "info", "success", "error", "warning", "default" + */ +export const notify = (text, type) => + toast(text, { + role: 'alert', + autoClose: 3000, + type, + draggable: true, + closeOnClick: true, + pauseOnHover: true, + hideProgressBar: true, + }); diff --git a/src/views/Home.jsx b/src/views/Home.jsx index 39675e4..06f9894 100644 --- a/src/views/Home.jsx +++ b/src/views/Home.jsx @@ -1,17 +1,18 @@ -import './Home.css'; +import { ToastContainer } from 'react-toastify'; +import { FaPlusSquare, FaShareAlt } from 'react-icons/fa'; +import { FaAngleRight, FaAngleDown } from 'react-icons/fa6'; +import { NavLink } from 'react-router-dom'; import { useState } from 'react'; +import { notify } from '../utils/notifications'; import { createList } from '../api'; -import { FaAngleRight, FaAngleDown } from 'react-icons/fa6'; import { Disclosure } from './Disclosure'; import { List } from './List'; import { IconButton } from '../components/IconButton'; -import { FaPlusSquare, FaShareAlt } from 'react-icons/fa'; -import { NavLink } from 'react-router-dom'; +import './Home.css'; export function Home({ data, lists, listPath, setListPath, user }) { const userId = user?.uid; const userEmail = user?.email; - const [listName, setListName] = useState(''); const handleChange = (event) => { @@ -23,19 +24,21 @@ export function Home({ data, lists, listPath, setListPath, user }) { if (listName) { try { const newListPath = await createList(userId, userEmail, listName); - alert('List is sucessfully created'); + notify('List is sucessfully created', 'success'); setListPath(newListPath); } catch { - alert('There was an error adding your list to db'); + notify('There was an error adding your list', 'error'); } } else { - alert('Please enter a valid name'); + notify('Please enter a valid name', 'warning'); } + setListName(''); }; return (
+
{ setSearchInput(e.target.value); - setMessage(''); }; const clearSearchInput = () => { @@ -23,14 +21,6 @@ export function List({ data, listPath }) { : item; }); - useEffect(() => { - if (message !== '') { - setInterval(() => { - setMessage(''); - }, 5000); - } - }, [message]); - return ( <>
@@ -60,7 +50,6 @@ export function List({ data, listPath }) { dateLastPurchased={item.dateLastPurchased} purchaseInterval={item.purchaseInterval} dateCreated={item.dateCreated} - setMessage={setMessage} sortCriteria={item.sortCriteria} /> ))} @@ -68,8 +57,6 @@ export function List({ data, listPath }) { ) : (

No items to display

)} -
- {message} ); } diff --git a/src/views/ManageList.jsx b/src/views/ManageList.jsx index 6222b23..4ca7af6 100644 --- a/src/views/ManageList.jsx +++ b/src/views/ManageList.jsx @@ -1,14 +1,15 @@ +import { ToastContainer } from 'react-toastify'; import { useState, useMemo } from 'react'; import { addItem, shareList } from '../api/firebase'; import { FaPlusSquare } from 'react-icons/fa'; import { IconButton } from '../components/IconButton'; import { FaEnvelope } from 'react-icons/fa6'; +import { notify } from '../utils/notifications'; export function ManageList({ listPath, user, data }) { const currentUserId = user?.uid; const [itemName, setItemName] = useState(''); const [daysUntilNextPurchase, setDaysUntilNextPurchase] = useState(7); - const [message, setMessage] = useState(''); const [recipientEmail, setRecipientEmail] = useState(''); const messages = { @@ -34,14 +35,14 @@ export function ManageList({ listPath, user, data }) { const normalizedItemName = normalizeString(itemName.trim()); if (!normalizedItemName) { - setMessage('empty'); + notify(messages['empty'], 'warning'); return; } const itemMatch = normalizedData.includes(normalizedItemName); if (itemMatch) { - setMessage('duplicate'); + notify(messages['duplicate'], 'warning'); return; } @@ -50,12 +51,12 @@ export function ManageList({ listPath, user, data }) { itemName: normalizedItemName, daysUntilNextPurchase, }); - setMessage('added'); setItemName(''); setDaysUntilNextPurchase(7); + notify(messages['added'], 'success'); } catch (error) { console.error('Error adding item:', error); - setMessage('failed'); + notify(messages['failed'], 'error'); } }; @@ -63,16 +64,17 @@ export function ManageList({ listPath, user, data }) { event.preventDefault(); shareList(listPath, currentUserId, recipientEmail) .then((result) => { - alert(result); + notify(result, 'success'); setRecipientEmail(''); }) .catch((error) => { - alert(error); + notify(error, 'error'); }); }; return (
+

Manage Your Shopping List for {extractedListName}

@@ -123,14 +125,8 @@ export function ManageList({ listPath, user, data }) {
-

- {message && ( -

- {messages[message] || ''} -

- )} -
+