Skip to content

Commit

Permalink
Merge pull request #34 from the-collab-lab/ma-fz/duplicate-or-emptyitem
Browse files Browse the repository at this point in the history
Issue # 10 Prevent user from adding duplicate or empty item to the list
  • Loading branch information
eternalmaha authored Sep 15, 2024
2 parents 5ea939b + f823aeb commit 855a7c2
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 20 deletions.
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export function App() {
/>
<Route
path="/manage-list"
element={<ManageList listPath={listPath} />}
element={<ManageList listPath={listPath} data={data || []} />}
/>
</Route>

Expand Down
27 changes: 15 additions & 12 deletions src/components/forms/AddItemForm.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { ChangeEvent, FormEvent, useState } from "react";
import { addItem } from "../../api";
import { validateTrimmedString } from "../../utils";
import { addItem, ListItem } from "../../api";
import { validateItemName } from "../../utils";
import toast from "react-hot-toast";

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

interface Props {
listPath: string | null;
data: ListItem[];
}

enum PurchaseTime {
Expand All @@ -21,7 +22,7 @@ const purchaseTimelines = {
[PurchaseTime.notSoon]: 30,
};

export function AddItemForm({ listPath }: Props) {
export function AddItemForm({ listPath, data: unfilteredListItems }: Props) {
const navigate = useNavigate();

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

if (!trimmedItemName) {
toast.error(
"Item name cannot be empty or just spaces. Please enter a valid name.",
);
// Validate the item name input
const validationErrorMessage = validateItemName(
itemName,
unfilteredListItems,
);

// If there's a validation error, show the error and return early
if (validationErrorMessage) {
toast.error(validationErrorMessage);
return;
}

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

try {
await toast.promise(
addItem(listPath, trimmedItemName, daysUntilNextPurchase),
addItem(listPath, itemName, daysUntilNextPurchase), // saving original input
{
loading: "Adding item to list.",
success: () => {
setItemName("");
setItemNextPurchaseTimeline(PurchaseTime.soon);
return `${itemName} successfully added to your list!`;
return `${itemName} successfully added to your list!`; // showing original input
},
error: () => {
return `${itemName} failed to add to your list. Please try again!`;
Expand All @@ -79,7 +84,6 @@ export function AddItemForm({ listPath }: Props) {
console.error("Failed to add item:", error);
}
};

const navigateToListPage = () => {
navigate("/list");
};
Expand All @@ -98,7 +102,6 @@ export function AddItemForm({ listPath }: Props) {
name="item"
value={itemName}
onChange={handleItemNameTextChange}
required
aria-label="Enter the item name"
aria-required
/>
Expand Down
41 changes: 36 additions & 5 deletions src/utils/validateTrimmedString.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
// makes sure the string passed into the function isn't an empty string
export function validateTrimmedString(input: string) {
const trimmedInput = input.trim();
import { ListItem } from "../api";

// Validates the item name input for a shopping list
export function validateItemName(
input: string,
existingItems: ListItem[],
): string | null {
const trimmedInput = input.trim(); //removes leading and trailing whitespaces

// Condition 1: Check if the input is empty
if (trimmedInput.length === 0) {
return null;
return "Item cannot be empty";
}

//Remove punctuation marks and normalize input
const punctuationRegex = /[^\p{L}]/gu;

const normalizedInputName = trimmedInput
.replace(punctuationRegex, "")
.toLowerCase();

//Create a list of normalized existing item names
const normalizedExistingItemNames = existingItems.map((existingItem) => {
return existingItem.name.replace(punctuationRegex, "").toLowerCase();
});

// Condition 2: Check if the normalized input matches any existing item
const isDuplicateItem = (normalizedInputName: string): boolean => {
return normalizedExistingItemNames.some(
(item) => item === normalizedInputName,
);
};

//return error if the item already exists
if (isDuplicateItem(normalizedInputName)) {
return ` ${normalizedInputName} already exists in the list`;
}

return trimmedInput;
// Return null if no errors are found (input is valid)
return null;
}
6 changes: 4 additions & 2 deletions src/views/authenticated/ManageList.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { AddItemForm } from "../../components/forms/AddItemForm";
import ShareListForm from "../../components/forms/ShareListForm";
import { ListItem } from "../../api";

interface Props {
data: ListItem[];
listPath: string | null;
}

export function ManageList({ listPath }: Props) {
export function ManageList({ listPath, data }: Props) {
return (
<div>
<p>
Hello from the <code>/manage-list</code> page!
</p>
<AddItemForm listPath={listPath} />
<AddItemForm listPath={listPath} data={data || []} />
<ShareListForm listPath={listPath} />
</div>
);
Expand Down

0 comments on commit 855a7c2

Please sign in to comment.