Skip to content

Commit

Permalink
Mark item as purchased & update DB (#31)
Browse files Browse the repository at this point in the history
* updated the updateItem func in api to take parameters & use firebase func

* skeleton of updateItem if purchased

* updateItem changed to increment purchases and update dateLastPurchased

* update the checkbox input to have my dynamic prop values

* added toast and the updateItem call using the inputs checked status to update the purchase info

Co-authored-by: Maha Ahmed <[email protected]>

* removed the <ul> tags list items don't have <li>

* added a 24 hrs later check function in date utils

* added useEffect to determine if 24hrs has passed

* added toast that let's user know know item already marked as purchased in last 24 hrs

Co-authored-by: Maha Ahmed <[email protected]>

* refactoring conditonal check of purchase date to render a marked purchase

* refactored the dependency in useEffect for better accuracy

* update to use Pick Utility Type, added a try...catch block, directly set updates fields

* correct the math of the 24hr check and update func name

* moved the setChecked for marking as purchased into success toast, removed redundant checks for under 24hr purchase error

* update the shopping list api doc for the updateItem change

* updated purchaseDate with more concise logic, added disabled to checkbox, removed toast.error for recheck in 24hr

* updated ListItem to use tagged unions

Co-authored-by: Maha Ahmed <[email protected]>

* fixed the merge conflict issue

Co-authored-by: Maha Ahmed <[email protected]>

* updateItem rethrows error it catches so that toast of ListItem can trigger the error correctly

---------

Co-authored-by: Maha <[email protected]>
Co-authored-by: Maha Ahmed <[email protected]>
  • Loading branch information
3 people authored Sep 8, 2024
1 parent 6dcf2e7 commit 7bacfe6
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 42 deletions.
5 changes: 4 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ export function App() {

{/* protected routes */}
<Route element={<ProtectRoute user={user} redirectPath="/" />}>
<Route path="/list" element={<List data={data} />} />
<Route
path="/list"
element={<List data={data} listPath={listPath} />}
/>
<Route
path="/manage-list"
element={<ManageList listPath={listPath} />}
Expand Down
7 changes: 6 additions & 1 deletion src/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ This function takes user-provided data and uses it to create a new item in the F

### `updateItem`

🚧 To be completed! 🚧
This function takes user-provided data and uses it to update an exiting item in the Firestore database.

| Parameter | Type | Description |
| ---------- | -------- | --------------------------------------------------------------- |
| `listPath` | `string` | The Firestore path of the list to which the item will be added. |
| `item` | `string` | The item object. |

### `deleteItem`

Expand Down
43 changes: 28 additions & 15 deletions src/api/firebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,23 +234,36 @@ export async function addItem(
) {
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!
dateLastPurchased: null,
dateNextPurchased: getFutureDate(daysUntilNextPurchase),
name,
totalPurchases: 0,
});
try {
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!
dateLastPurchased: null,
dateNextPurchased: getFutureDate(daysUntilNextPurchase),
name,
totalPurchases: 0,
});
} catch (error) {
console.error("Error adding an item", error);
throw error;
}
}

export async function updateItem() {
/**
* TODO: Fill this out so that it uses the correct Firestore function
* to update an existing item. You'll need to figure out what arguments
* this function must accept!
*/
export async function updateItem(listPath: string, item: ListItem) {
const itemDocRef = doc(db, listPath, "items", item.id);

const updates: Pick<ListItem, "totalPurchases" | "dateLastPurchased"> = {
totalPurchases: item.totalPurchases + 1,
dateLastPurchased: Timestamp.fromDate(new Date()),
};

try {
await updateDoc(itemDocRef, updates);
} catch (error) {
console.error("Error updating document", error);
throw error;
}
}

export async function deleteItem() {
Expand Down
74 changes: 69 additions & 5 deletions src/components/ListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,72 @@
import * as api from '../api';
import './ListItem.css';
import "./ListItem.css";
import { updateItem, ListItem } from "../api";
import { useState } from "react";
import toast from "react-hot-toast";
import { moreThan24HoursPassed } from "../utils";

type Props = Pick<api.ListItem, 'name'>;
interface Props {
item: ListItem;
listPath: string | null;
}
interface None {
kind: "none";
}

interface Set {
kind: "set";
value: boolean;
}

export function ListItemCheckBox({ item, listPath }: Props) {
const [updatedCheckState, setUpdatedCheckState] = useState<None | Set>({
kind: "none",
});

const isChecked =
updatedCheckState.kind === "set"
? updatedCheckState.value
: item.dateLastPurchased
? !moreThan24HoursPassed(item.dateLastPurchased.toDate())
: false;

const handleCheckChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const newCheckedState = e.target.checked;

// Temporarily store the updated check state
setUpdatedCheckState({ kind: "set", value: newCheckedState });

if (!listPath) {
toast.error("Error: listPath is missing or invalid.");
return;
}

try {
await toast.promise(updateItem(listPath, item), {
loading: `Marking ${item.name} as purchased!`,
success: `${item.name} is now marked as purchased!`,
error: `${item.name} failed to add to your list of purchases. Please try again!`,
});
} finally {
// reset local state
setUpdatedCheckState({ kind: "none" });
}
};

export function ListItem({ name }: Props) {
return <li className="ListItem">{name}</li>;
return (
<div className="ListItem">
<label htmlFor={`checkbox-${item.id}`}>
<input
type="checkbox"
id={`checkbox-${item.id}`}
aria-label={`Mark ${item.name} as purchased.`}
value={item.id}
checked={isChecked}
onChange={handleCheckChange}
aria-checked={isChecked}
disabled={isChecked}
/>
{item.name}
</label>
</div>
);
}
9 changes: 9 additions & 0 deletions src/utils/dates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ const ONE_DAY_IN_MILLISECONDS = 86400000;
export function getFutureDate(offset: number) {
return new Date(Date.now() + offset * ONE_DAY_IN_MILLISECONDS);
}

export function moreThan24HoursPassed(purchaseDate: Date): boolean {
const currentTime = new Date();

const timeElapsedInMilliseconds =
currentTime.getTime() - purchaseDate.getTime();

return timeElapsedInMilliseconds >= ONE_DAY_IN_MILLISECONDS;
}
43 changes: 23 additions & 20 deletions src/views/authenticated/List.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { useState, useMemo } from "react";
import { ListItem as ListItemComponent } from "../../components/ListItem";
import { FilterListInput as FilterListComponent } from "../../components/FilterListInput";
import { ListItemCheckBox } from "../../components/ListItem";
import { FilterListInput } from "../../components/FilterListInput";
import { ListItem } from "../../api";
import { useNavigate } from "react-router-dom";

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

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

const [searchTerm, setSearchTerm] = useState<string>("");

const filteredListItems = useMemo(() => {
Expand Down Expand Up @@ -50,24 +50,27 @@ export function List({ data: unfilteredListItems }: Props) {
<p>
Hello from the <code>/list</code> page!
</p>
<section>
<FilterListComponent
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
/>
<h3>Want to add more items to your list?</h3>
<button
onClick={() => navigate("/manage-list")}
aria-label="Navigate to add more items to your list"
>
{"Add items"}
</button>
</section>
<ul>

<div>
<section>
{unfilteredListItems.length > 0 && (
<FilterListInput
searchTerm={searchTerm}
setSearchTerm={setSearchTerm}
/>
)}
<h3>Want to add more items to your list?</h3>
<button
onClick={() => navigate("/manage-list")}
aria-label="Navigate to add more items to your list"
>
{"Add items"}
</button>
</section>
{filteredListItems.map((item) => (
<ListItemComponent key={item.id} name={item.name} />
<ListItemCheckBox key={item.id} item={item} listPath={listPath} />
))}
</ul>
</div>
</>
);
}

0 comments on commit 7bacfe6

Please sign in to comment.