Skip to content

Commit 855a7c2

Browse files
authored
Merge pull request #34 from the-collab-lab/ma-fz/duplicate-or-emptyitem
Issue # 10 Prevent user from adding duplicate or empty item to the list
2 parents 5ea939b + f823aeb commit 855a7c2

File tree

4 files changed

+56
-20
lines changed

4 files changed

+56
-20
lines changed

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export function App() {
7070
/>
7171
<Route
7272
path="/manage-list"
73-
element={<ManageList listPath={listPath} />}
73+
element={<ManageList listPath={listPath} data={data || []} />}
7474
/>
7575
</Route>
7676

src/components/forms/AddItemForm.tsx

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { ChangeEvent, FormEvent, useState } from "react";
2-
import { addItem } from "../../api";
3-
import { validateTrimmedString } from "../../utils";
2+
import { addItem, ListItem } from "../../api";
3+
import { validateItemName } from "../../utils";
44
import toast from "react-hot-toast";
55

66
import { useNavigate } from "react-router-dom";
77

88
interface Props {
99
listPath: string | null;
10+
data: ListItem[];
1011
}
1112

1213
enum PurchaseTime {
@@ -21,7 +22,7 @@ const purchaseTimelines = {
2122
[PurchaseTime.notSoon]: 30,
2223
};
2324

24-
export function AddItemForm({ listPath }: Props) {
25+
export function AddItemForm({ listPath, data: unfilteredListItems }: Props) {
2526
const navigate = useNavigate();
2627

2728
const [itemName, setItemName] = useState("");
@@ -42,12 +43,16 @@ export function AddItemForm({ listPath }: Props) {
4243
listPath: string,
4344
) => {
4445
e.preventDefault();
45-
const trimmedItemName = validateTrimmedString(itemName);
4646

47-
if (!trimmedItemName) {
48-
toast.error(
49-
"Item name cannot be empty or just spaces. Please enter a valid name.",
50-
);
47+
// Validate the item name input
48+
const validationErrorMessage = validateItemName(
49+
itemName,
50+
unfilteredListItems,
51+
);
52+
53+
// If there's a validation error, show the error and return early
54+
if (validationErrorMessage) {
55+
toast.error(validationErrorMessage);
5156
return;
5257
}
5358

@@ -62,13 +67,13 @@ export function AddItemForm({ listPath }: Props) {
6267

6368
try {
6469
await toast.promise(
65-
addItem(listPath, trimmedItemName, daysUntilNextPurchase),
70+
addItem(listPath, itemName, daysUntilNextPurchase), // saving original input
6671
{
6772
loading: "Adding item to list.",
6873
success: () => {
6974
setItemName("");
7075
setItemNextPurchaseTimeline(PurchaseTime.soon);
71-
return `${itemName} successfully added to your list!`;
76+
return `${itemName} successfully added to your list!`; // showing original input
7277
},
7378
error: () => {
7479
return `${itemName} failed to add to your list. Please try again!`;
@@ -79,7 +84,6 @@ export function AddItemForm({ listPath }: Props) {
7984
console.error("Failed to add item:", error);
8085
}
8186
};
82-
8387
const navigateToListPage = () => {
8488
navigate("/list");
8589
};
@@ -98,7 +102,6 @@ export function AddItemForm({ listPath }: Props) {
98102
name="item"
99103
value={itemName}
100104
onChange={handleItemNameTextChange}
101-
required
102105
aria-label="Enter the item name"
103106
aria-required
104107
/>

src/utils/validateTrimmedString.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,41 @@
1-
// makes sure the string passed into the function isn't an empty string
2-
export function validateTrimmedString(input: string) {
3-
const trimmedInput = input.trim();
1+
import { ListItem } from "../api";
42

3+
// Validates the item name input for a shopping list
4+
export function validateItemName(
5+
input: string,
6+
existingItems: ListItem[],
7+
): string | null {
8+
const trimmedInput = input.trim(); //removes leading and trailing whitespaces
9+
10+
// Condition 1: Check if the input is empty
511
if (trimmedInput.length === 0) {
6-
return null;
12+
return "Item cannot be empty";
13+
}
14+
15+
//Remove punctuation marks and normalize input
16+
const punctuationRegex = /[^\p{L}]/gu;
17+
18+
const normalizedInputName = trimmedInput
19+
.replace(punctuationRegex, "")
20+
.toLowerCase();
21+
22+
//Create a list of normalized existing item names
23+
const normalizedExistingItemNames = existingItems.map((existingItem) => {
24+
return existingItem.name.replace(punctuationRegex, "").toLowerCase();
25+
});
26+
27+
// Condition 2: Check if the normalized input matches any existing item
28+
const isDuplicateItem = (normalizedInputName: string): boolean => {
29+
return normalizedExistingItemNames.some(
30+
(item) => item === normalizedInputName,
31+
);
32+
};
33+
34+
//return error if the item already exists
35+
if (isDuplicateItem(normalizedInputName)) {
36+
return ` ${normalizedInputName} already exists in the list`;
737
}
838

9-
return trimmedInput;
39+
// Return null if no errors are found (input is valid)
40+
return null;
1041
}

src/views/authenticated/ManageList.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { AddItemForm } from "../../components/forms/AddItemForm";
22
import ShareListForm from "../../components/forms/ShareListForm";
3+
import { ListItem } from "../../api";
34

45
interface Props {
6+
data: ListItem[];
57
listPath: string | null;
68
}
79

8-
export function ManageList({ listPath }: Props) {
10+
export function ManageList({ listPath, data }: Props) {
911
return (
1012
<div>
1113
<p>
1214
Hello from the <code>/manage-list</code> page!
1315
</p>
14-
<AddItemForm listPath={listPath} />
16+
<AddItemForm listPath={listPath} data={data || []} />
1517
<ShareListForm listPath={listPath} />
1618
</div>
1719
);

0 commit comments

Comments
 (0)