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!
+
+
+
+
);
}