From da0009a5d4da5d55daf1da14b3e994103617612c Mon Sep 17 00:00:00 2001
From: Falak Zahra
Date: Wed, 28 Aug 2024 09:54:55 -0600
Subject: [PATCH 01/24] Filter the list based on user search input
---
src/components/FilterList.tsx | 32 ++++++++++++++++++++++++++++++++
src/views/List.tsx | 24 +++++++++++++++++++-----
2 files changed, 51 insertions(+), 5 deletions(-)
create mode 100644 src/components/FilterList.tsx
diff --git a/src/components/FilterList.tsx b/src/components/FilterList.tsx
new file mode 100644
index 0000000..cf4e15c
--- /dev/null
+++ b/src/components/FilterList.tsx
@@ -0,0 +1,32 @@
+import { ChangeEvent } from "react";
+import { ListItem } from "../api";
+
+interface FilterListProps {
+ searchTerm: string;
+ // setSearchTerm: React.Dispatch>
+ setSearchTerm: (term: string) => void;
+}
+
+export function FilterList({ searchTerm, setSearchTerm }: FilterListProps) {
+ const handleChange = (e: ChangeEvent) => {
+ setSearchTerm(e.target.value);
+ };
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/src/views/List.tsx b/src/views/List.tsx
index bfd5a8b..b0631e1 100644
--- a/src/views/List.tsx
+++ b/src/views/List.tsx
@@ -1,21 +1,35 @@
-import React from 'react';
-import { ListItem as ListItemComponent } from '../components/ListItem';
-import { ListItem } from '../api';
+import { useState } from "react";
+import { ListItem as ListItemComponent } from "../components/ListItem";
+import { FilterList as FilterListComponent } from "../components/FilterList";
+import { ListItem } from "../api";
interface Props {
data: ListItem[];
}
export function List({ data }: Props) {
- const hasItem = data.length !== 0;
+ const [searchTerm, setSearchTerm] = useState("");
+
+ const filteredData = data.filter((item) =>
+ item.name.toLowerCase().includes(searchTerm.toLowerCase()),
+ );
+ console.log(filteredData);
+
+ const hasItem = filteredData.length !== 0;
return (
<>
Hello from the /list
page!
+
+
+
{hasItem &&
- data.map((item) => (
+ filteredData.map((item) => (
))}
From ce8ab9307ea8c1036c5777b4b6f24f428ed1ac7f Mon Sep 17 00:00:00 2001
From: Falak Zahra
Date: Wed, 28 Aug 2024 09:56:34 -0600
Subject: [PATCH 02/24] Filter the list based on user search input
---
package-lock.json | 2 +-
package.json | 2 +-
src/App.tsx | 2 --
3 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 45aa58c..3ba2965 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",
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 6a7e0f0..f0df29a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,5 +1,3 @@
-import React from "react";
-
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { Home, Layout, List, ManageList } from "./views";
From 89c30b47ba11dc2f0f785de6e7206b78903146aa Mon Sep 17 00:00:00 2001
From: Falak Zahra
Date: Wed, 28 Aug 2024 10:03:28 -0600
Subject: [PATCH 03/24] Remove unneccesary console logs and comments
---
src/components/FilterList.tsx | 4 +---
src/views/List.tsx | 1 -
2 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/components/FilterList.tsx b/src/components/FilterList.tsx
index cf4e15c..a2e142a 100644
--- a/src/components/FilterList.tsx
+++ b/src/components/FilterList.tsx
@@ -3,7 +3,6 @@ import { ListItem } from "../api";
interface FilterListProps {
searchTerm: string;
- // setSearchTerm: React.Dispatch>
setSearchTerm: (term: string) => void;
}
@@ -15,13 +14,12 @@ export function FilterList({ searchTerm, setSearchTerm }: FilterListProps) {
<>
-
>
);
From 8d96ad316cc8f27ecbe968472fc664d9e483c167 Mon Sep 17 00:00:00 2001
From: RossaMania
Date: Sat, 31 Aug 2024 16:09:48 -0500
Subject: [PATCH 12/24] refactor: The `ShareListForm` component is introduced
to handle the form submission and uses the `shareList` function from the
Firebase API to add the existing user to the list. This feature enhances
collaboration and sharing within the application.
---
src/components/forms/ShareListForm.tsx | 83 +++++++++++
src/views/ManageList.tsx | 188 ++++++++-----------------
2 files changed, 142 insertions(+), 129 deletions(-)
create mode 100644 src/components/forms/ShareListForm.tsx
diff --git a/src/components/forms/ShareListForm.tsx b/src/components/forms/ShareListForm.tsx
new file mode 100644
index 0000000..1235f2a
--- /dev/null
+++ b/src/components/forms/ShareListForm.tsx
@@ -0,0 +1,83 @@
+import { ChangeEvent, FormEvent, useState } from "react";
+import { shareList } from "../../api/firebase";
+import { validateTrimmedString } from "../../utils";
+
+import toast from "react-hot-toast";
+
+import { useAuth } from "../../api/useAuth";
+
+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;
+ }
+
+ if (!currentUser) {
+ return;
+ }
+
+ const trimmedEmailName = validateTrimmedString(emailName);
+
+ if (!trimmedEmailName) {
+ return;
+ }
+
+ try {
+ await toast.promise(shareList(listPath, currentUser, trimmedEmailName), {
+ 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 (
+
+ );
+};
+
+export default ShareListForm;
diff --git a/src/views/ManageList.tsx b/src/views/ManageList.tsx
index 2a02da6..3e61b53 100644
--- a/src/views/ManageList.tsx
+++ b/src/views/ManageList.tsx
@@ -1,9 +1,9 @@
import { ChangeEvent, FormEvent, useState } from "react";
-import { addItem, shareList } from "../api/firebase";
+import { addItem } from "../api/firebase";
import { validateTrimmedString } from "../utils";
import toast from "react-hot-toast";
-import { useAuth } from "../api/useAuth";
+import ShareListForm from "../components/forms/ShareListForm";
interface Props {
listPath: string | null;
@@ -22,13 +22,10 @@ const purchaseTimelines = {
};
export function ManageList({ listPath }: Props) {
- const { user: currentUser } = useAuth();
-
const [itemName, setItemName] = useState("");
const [itemNextPurchaseTimeline, setItemNextPurchaseTimeline] = useState(
PurchaseTime.soon,
);
- const [emailName, setEmailName] = useState("");
const handleItemNameTextChange = (e: ChangeEvent) => {
setItemName(e.target.value);
@@ -38,10 +35,6 @@ export function ManageList({ listPath }: Props) {
setItemNextPurchaseTimeline(changed);
};
- const handleEmailNameChange = (e: ChangeEvent) => {
- setEmailName(e.target.value);
- };
-
const handleSubmit = async (
e: FormEvent,
listPath: string,
@@ -85,143 +78,80 @@ export function ManageList({ listPath }: Props) {
}
};
- const handleInvite = async (
- e: FormEvent,
- listPath: string,
- ) => {
- console.log("Button clicked! Inviting user!");
- e.preventDefault();
-
- const trimmedEmailName = validateTrimmedString(emailName);
-
- if (!trimmedEmailName) {
- toast.error(
- "Oops! Email cannot be empty or just spaces. Please enter a valid email.",
- );
- return;
- }
-
- if (currentUser === null) {
- toast.error(`You are not logged in! Cannot invite.`);
-
- return;
- }
-
- try {
- await toast.promise(shareList(listPath, currentUser, trimmedEmailName), {
- 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 (
);
}
From 391e36e70970a39e8e9f2fe25b760215537e971e Mon Sep 17 00:00:00 2001
From: Maha
Date: Sat, 31 Aug 2024 19:15:21 -0400
Subject: [PATCH 13/24] removed trimmedEmailName utility function as its not
needed due to input type email. Corrected aria-label for handleInvite submit
button
---
src/components/forms/ShareListForm.tsx | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/src/components/forms/ShareListForm.tsx b/src/components/forms/ShareListForm.tsx
index 1235f2a..19ed389 100644
--- a/src/components/forms/ShareListForm.tsx
+++ b/src/components/forms/ShareListForm.tsx
@@ -1,6 +1,5 @@
import { ChangeEvent, FormEvent, useState } from "react";
import { shareList } from "../../api/firebase";
-import { validateTrimmedString } from "../../utils";
import toast from "react-hot-toast";
@@ -34,14 +33,8 @@ const ShareListForm = ({ listPath }: Props) => {
return;
}
- const trimmedEmailName = validateTrimmedString(emailName);
-
- if (!trimmedEmailName) {
- return;
- }
-
try {
- await toast.promise(shareList(listPath, currentUser, trimmedEmailName), {
+ await toast.promise(shareList(listPath, currentUser, emailName), {
loading: "sharing list with existing user",
success: () => {
setEmailName("");
@@ -68,12 +61,12 @@ const ShareListForm = ({ listPath }: Props) => {
placeholder="Enter e-mail address. . ."
onChange={handleEmailNameChange}
required
- aria-label="Enter the user name to share list"
+ aria-label="Enter the user email address to share list"
aria-required
/>
-
- {filteredListItems.length > 0 && (
+ {unfilteredListItems.length > 0 && (
Date: Sun, 1 Sep 2024 00:58:19 -0400
Subject: [PATCH 21/24] adding useMemo to list filtering to better performance
by reducing unnecessary re-renders
---
src/views/List.tsx | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/views/List.tsx b/src/views/List.tsx
index 4fa3a47..b81d596 100644
--- a/src/views/List.tsx
+++ b/src/views/List.tsx
@@ -1,4 +1,4 @@
-import { useState } from "react";
+import { useState, useMemo } from "react";
import { ListItem as ListItemComponent } from "../components/ListItem";
import { FilterListInput as FilterListComponent } from "../components/FilterListInput";
import { ListItem } from "../api";
@@ -10,9 +10,11 @@ interface Props {
export function List({ data: unfilteredListItems }: Props) {
const [searchTerm, setSearchTerm] = useState("");
- const filteredListItems = unfilteredListItems.filter((item) =>
- item.name.toLowerCase().includes(searchTerm.toLowerCase()),
- );
+ const filteredListItems = useMemo(() => {
+ return unfilteredListItems.filter((item) =>
+ item.name.toLowerCase().includes(searchTerm.toLowerCase()),
+ );
+ }, [searchTerm, unfilteredListItems]);
return (
<>
From 8d403d91a29533f52f0757deebe03a39efea5fb4 Mon Sep 17 00:00:00 2001
From: Falak Zahra
Date: Sat, 31 Aug 2024 23:24:02 -0600
Subject: [PATCH 22/24] Remove unfilteredListItems from the dependency array
for useMemo
---
src/views/List.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/views/List.tsx b/src/views/List.tsx
index b81d596..f9cd476 100644
--- a/src/views/List.tsx
+++ b/src/views/List.tsx
@@ -14,7 +14,7 @@ export function List({ data: unfilteredListItems }: Props) {
return unfilteredListItems.filter((item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
- }, [searchTerm, unfilteredListItems]);
+ }, [searchTerm]);
return (
<>
From 959c10a7dfa6118a766b81fe0aeb354fdbcb20c2 Mon Sep 17 00:00:00 2001
From: Falak Zahra
Date: Sat, 31 Aug 2024 23:32:22 -0600
Subject: [PATCH 23/24] Add unfilteredListItems back to the dependency array
for useMemo
---
src/views/List.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/views/List.tsx b/src/views/List.tsx
index f9cd476..b81d596 100644
--- a/src/views/List.tsx
+++ b/src/views/List.tsx
@@ -14,7 +14,7 @@ export function List({ data: unfilteredListItems }: Props) {
return unfilteredListItems.filter((item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
- }, [searchTerm]);
+ }, [searchTerm, unfilteredListItems]);
return (
<>
From 243b675f32baf3efcb2736740bbe1d9465ba8c96 Mon Sep 17 00:00:00 2001
From: Falak Zahra
Date: Sat, 31 Aug 2024 23:44:48 -0600
Subject: [PATCH 24/24] Prevent form refresh if user presses Enter key during
filtering process
---
src/components/FilterListInput.tsx | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/components/FilterListInput.tsx b/src/components/FilterListInput.tsx
index ca24b62..6ebe5ef 100644
--- a/src/components/FilterListInput.tsx
+++ b/src/components/FilterListInput.tsx
@@ -1,4 +1,4 @@
-import { ChangeEvent } from "react";
+import { ChangeEvent, FormEvent } from "react";
interface FilterListProps {
searchTerm: string;
@@ -13,8 +13,12 @@ export function FilterListInput({
setSearchTerm(e.target.value);
};
+ const handleSubmit = (e: FormEvent) => {
+ e.preventDefault();
+ };
+
return (
-