diff --git a/package-lock.json b/package-lock.json index 5671142..8a84077 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "firebase": "^10.12.5", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-hot-toast": "^2.4.1", "react-router-dom": "^6.26.0" }, "devDependencies": { @@ -4916,6 +4917,13 @@ "dev": true, "license": "MIT" }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT", + "peer": true + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -6248,6 +6256,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -7299,9 +7316,9 @@ "license": "MIT" }, "node_modules/lint-staged": { - "version": "15.2.8", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.8.tgz", - "integrity": "sha512-PUWFf2zQzsd9EFU+kM1d7UP+AZDbKFKuj+9JNVTBkhUFhbg4MAt6WfyMMwBfM4lYqd4D2Jwac5iuTu9rVj4zCQ==", + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.9.tgz", + "integrity": "sha512-BZAt8Lk3sEnxw7tfxM7jeZlPRuT4M68O0/CwZhhaw6eeWu0Lz5eERE3m386InivXB64fp/mDID452h48tvKlRQ==", "dev": true, "dependencies": { "chalk": "~5.3.0", @@ -8311,6 +8328,22 @@ "react": "^18.3.1" } }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "license": "MIT", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/package.json b/package.json index 1f74c3e..d5128ae 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "firebase": "^10.12.5", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-hot-toast": "^2.4.1", "react-router-dom": "^6.26.0" }, "devDependencies": { diff --git a/src/App.jsx b/src/App.jsx index b4b5227..b0f2f17 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -50,7 +50,10 @@ export function App() { element={} /> } /> - } /> + } + /> diff --git a/src/api/firebase.js b/src/api/firebase.js index 60cbb99..5cf611b 100644 --- a/src/api/firebase.js +++ b/src/api/firebase.js @@ -2,6 +2,7 @@ import { arrayUnion, getDoc, setDoc, + addDoc, collection, doc, onSnapshot, @@ -162,10 +163,9 @@ export async function shareList(listPath, currentUserId, recipientEmail) { * @param {number} itemData.daysUntilNextPurchase The number of days until the user thinks they'll need to buy the item again. */ export async function addItem(listPath, { itemName, daysUntilNextPurchase }) { - const listCollectionRef = collection(db, listPath, "items"); - // TODO: Replace this call to console.log with the appropriate - // Firebase function, so this information is sent to your database! - return console.log(listCollectionRef, { + const listCollectionRef = collection(db, listPath, 'items'); + + await addDoc(listCollectionRef, { dateCreated: new Date(), // NOTE: This is null because the item has just been created. // We'll use updateItem to put a Date here when the item is purchased! diff --git a/src/utils/index.js b/src/utils/index.js index 9243f7a..1761f66 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,2 +1,3 @@ -export * from "./dates"; -export * from "./hooks"; +export * from './dates'; +export * from './hooks'; +export * from './validateTrimmedString'; diff --git a/src/utils/validateTrimmedString.js b/src/utils/validateTrimmedString.js new file mode 100644 index 0000000..1366eb0 --- /dev/null +++ b/src/utils/validateTrimmedString.js @@ -0,0 +1,10 @@ +// makes sure the string passed into the function isn't an empty string +export function validateTrimmedString(input) { + const trimmedInput = input.trim(); + + if (trimmedInput.length === 0) { + return null; + } + + return trimmedInput; +} diff --git a/src/views/ManageList.jsx b/src/views/ManageList.jsx index e1c5fde..a5530d1 100644 --- a/src/views/ManageList.jsx +++ b/src/views/ManageList.jsx @@ -1,7 +1,143 @@ -export function ManageList() { +import { useState } from 'react'; +import { addItem } from '../api/firebase'; +import { validateTrimmedString } from '../utils'; +import toast, { Toaster } from 'react-hot-toast'; + +const SOON = 'soon'; +const KIND_OF_SOON = 'kindOfSoon'; +const NOT_SOON = 'notSoon'; + +const purchaseTimelines = { + [SOON]: 7, + [KIND_OF_SOON]: 14, + [NOT_SOON]: 30, +}; + +export function ManageList({ listPath }) { + const [itemName, setItemName] = useState(''); + const [itemNextPurchaseTimeline, setItemNextPurchaseTimeline] = + useState(SOON); + + const handleItemNameTextChange = (e) => { + setItemName(e.target.value); + }; + + const handleNextPurchaseChange = (e) => { + setItemNextPurchaseTimeline(e.target.value); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + const trimmedItemName = validateTrimmedString(itemName); + + if (!trimmedItemName) { + toast.error( + 'Item name cannot be empty or just spaces. Please enter a valid name.', + ); + return; + } + + if (!(itemNextPurchaseTimeline in purchaseTimelines)) { + toast.error( + 'Selected purchase timeline is invalid. Please choose a valid option.', + ); + return; + } + + const daysUntilNextPurchase = purchaseTimelines[itemNextPurchaseTimeline]; + + try { + await toast.promise( + addItem(listPath, { + itemName: trimmedItemName, + daysUntilNextPurchase, + }), + { + pending: 'Adding item to list.', + success: () => { + setItemName(''); + setItemNextPurchaseTimeline(SOON); + return `${itemName} successfully added to your list!`; + }, + error: () => { + return `${itemName} failed to add to your list. Please try again!`; + }, + }, + ); + } catch (error) { + console.error('Failed to add item:', error); + } + }; + return ( -

- Hello from the /manage-list page! -

+
+

+ Hello from the /manage-list page! +

+
+ +
+
+ When to buy: + +
+ +
+ +
+ +
+ +
); }