diff --git a/package-lock.json b/package-lock.json index 7606c72..42a07c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "eslint-plugin-react": "^7.35.0", "husky": "^9.1.4", "jsdom": "^24.1.1", - "lint-staged": "^15.2.8", + "lint-staged": "^15.2.9", "prettier": "^3.3.3", "typescript-eslint": "^8.2.0", "vite": "^5.3.5", @@ -7955,7 +7955,6 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" diff --git a/package.json b/package.json index f8906fe..7e3d6a8 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "eslint-plugin-react": "^7.35.0", "husky": "^9.1.4", "jsdom": "^24.1.1", - "lint-staged": "^15.2.8", + "lint-staged": "^15.2.9", "prettier": "^3.3.3", "typescript-eslint": "^8.2.0", "vite": "^5.3.5", diff --git a/src/App.tsx b/src/App.tsx index 5f5f04d..fc236ea 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,6 @@ import { Home, Layout, List, ManageList, PageNotFound } from "./views"; import { useFindUser, useShoppingListData, useShoppingLists } from "./api"; import { useStateWithStorage } from "./utils"; - import { ProtectRoute } from "./components"; /** @@ -15,6 +14,7 @@ import { ProtectRoute } from "./components"; * react-hot-toast to work anywhere in the app by just * importing toast as done in useAuth. */ + import { Toaster } from "react-hot-toast"; export function App() { diff --git a/src/api/firebase.ts b/src/api/firebase.ts index 7baaa7a..e293e21 100644 --- a/src/api/firebase.ts +++ b/src/api/firebase.ts @@ -212,7 +212,9 @@ export async function shareList( const recipientDoc = await getDoc(doc(usersCollectionRef, recipientEmail)); // If the recipient user doesn't exist, we can't share the list. if (!recipientDoc.exists()) { - return; + throw new Error( + "Unable to share list. Please verify correct email address.", + ); } // Add the list to the recipient user's sharedLists array. const listDocumentRef = doc(db, listPath); diff --git a/src/components/FilterListInput.tsx b/src/components/FilterListInput.tsx new file mode 100644 index 0000000..6ebe5ef --- /dev/null +++ b/src/components/FilterListInput.tsx @@ -0,0 +1,35 @@ +import { ChangeEvent, FormEvent } from "react"; + +interface FilterListProps { + searchTerm: string; + setSearchTerm: (term: string) => void; +} + +export function FilterListInput({ + searchTerm, + setSearchTerm, +}: FilterListProps) { + const handleChange = (e: ChangeEvent) => { + setSearchTerm(e.target.value); + }; + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + }; + + return ( +
+ +
+ ); +} diff --git a/src/components/forms/ShareListForm.tsx b/src/components/forms/ShareListForm.tsx new file mode 100644 index 0000000..c4c2ae0 --- /dev/null +++ b/src/components/forms/ShareListForm.tsx @@ -0,0 +1,74 @@ +import { ChangeEvent, FormEvent, useState } from "react"; +import { shareList } from "../../api/firebase"; + +import toast from "react-hot-toast"; + +import { useAuth } from "../../api/useAuth"; + +import { User } from "../../api/firebase"; + +interface Props { + listPath: string | null; +} + +const ShareListForm = ({ listPath }: Props) => { + const { user: currentUser } = useAuth(); + + const [emailName, setEmailName] = useState(""); + + const handleEmailNameChange = (e: ChangeEvent) => { + setEmailName(e.target.value); + }; + + const handleInvite = async ( + e: FormEvent, + listPath: string | null, + ) => { + console.log("Button clicked! Inviting user!"); + e.preventDefault(); + + if (!listPath) { + return; + } + + try { + await toast.promise(shareList(listPath, currentUser as User, emailName), { + loading: "sharing list with existing user", + success: () => { + setEmailName(""); + return `Successfully invited ${emailName} to your list!`; + }, + error: () => { + return `Oops! Failed to invite ${emailName} to your list. Please verify correct email!`; + }, + }); + } catch (error) { + console.error("Oops! Failed to invite user:", error); + } + }; + + return ( +
handleInvite(e, listPath)}> + +
+ +
+ ); +}; + +export default ShareListForm; diff --git a/src/views/List.tsx b/src/views/List.tsx index bfd5a8b..b81d596 100644 --- a/src/views/List.tsx +++ b/src/views/List.tsx @@ -1,23 +1,38 @@ -import React from 'react'; -import { ListItem as ListItemComponent } from '../components/ListItem'; -import { ListItem } from '../api'; +import { useState, useMemo } from "react"; +import { ListItem as ListItemComponent } from "../components/ListItem"; +import { FilterListInput as FilterListComponent } from "../components/FilterListInput"; +import { ListItem } from "../api"; interface Props { data: ListItem[]; } -export function List({ data }: Props) { - const hasItem = data.length !== 0; +export function List({ data: unfilteredListItems }: Props) { + const [searchTerm, setSearchTerm] = useState(""); + + const filteredListItems = useMemo(() => { + return unfilteredListItems.filter((item) => + item.name.toLowerCase().includes(searchTerm.toLowerCase()), + ); + }, [searchTerm, unfilteredListItems]); + return ( <>

Hello from the /list page!

+ + {unfilteredListItems.length > 0 && ( + + )} +
    - {hasItem && - data.map((item) => ( - - ))} + {filteredListItems.map((item) => ( + + ))}
); diff --git a/src/views/ManageList.tsx b/src/views/ManageList.tsx index f83f81c..1d80b48 100644 --- a/src/views/ManageList.tsx +++ b/src/views/ManageList.tsx @@ -3,6 +3,8 @@ import { addItem } from "../api/firebase"; import { validateTrimmedString } from "../utils"; import toast from "react-hot-toast"; +import ShareListForm from "../components/forms/ShareListForm"; + interface Props { listPath: string | null; } @@ -154,6 +156,7 @@ export function ManageList({ listPath }: Props) { )} + ); }