Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relocate Add Item Functionality to Modal on List Page #37

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
767 changes: 766 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
"npm": ">=8.19.0"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-slot": "^1.1.0",
"@the-collab-lab/shopping-list-utils": "^2.2.0",
"class-variance-authority": "^0.7.0",
Expand All @@ -18,7 +20,8 @@
"react-hot-toast": "^2.4.1",
"react-router-dom": "^6.26.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8"
},
"devDependencies": {
"@nabla/vite-plugin-eslint": "^2.0.4",
Expand Down
6 changes: 5 additions & 1 deletion src/api/firebase.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,10 @@ export async function shareList(listPath, currentUserId, recipientEmail) {
* @param {string} itemData.itemName The name of the item.
* @param {number} itemData.daysUntilNextPurchase The number of days until the user thinks they'll need to buy the item again.
*/
export async function addItem(listPath, { itemName, daysUntilNextPurchase }) {
export async function addItem(
listPath,
{ itemName, itemQuantity, daysUntilNextPurchase },
) {
const listCollectionRef = collection(db, listPath, 'items');
return addDoc(listCollectionRef, {
dateCreated: new Date(),
Expand All @@ -187,6 +190,7 @@ export async function addItem(listPath, { itemName, daysUntilNextPurchase }) {
dateNextPurchased: getFutureDate(daysUntilNextPurchase),
dayInterval: daysUntilNextPurchase,
name: itemName,
quantity: itemQuantity,
totalPurchases: 0,
checked: false,
});
Expand Down
14 changes: 0 additions & 14 deletions src/components/ListItem.css

This file was deleted.

40 changes: 26 additions & 14 deletions src/components/ListItem.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import './ListItem.css';
import { updateItem, deleteItem } from '../api';
import { useEffect } from 'react';
import { ONE_DAY_IN_MILLISECONDS } from '../utils/dates';
import toast from 'react-hot-toast';
import { Button } from './ui/button';

export function ListItem({
listPath,
name,
id,
quantity,
isChecked,
dateLastPurchased,
totalPurchases,
Expand Down Expand Up @@ -54,20 +55,31 @@ export function ListItem({
}, []);

return (
<li className="ListItem">
<input
type="checkbox"
id={id}
onChange={handleOnChange}
checked={isChecked}
disabled={isChecked}
/>
<label htmlFor={`${id}`}>{name}</label>
<li className="flex justify-between">
<div className="flex items-center gap-2">
<input
type="checkbox"
id={id}
onChange={handleOnChange}
checked={isChecked}
disabled={isChecked}
/>
<div className="flex items-center gap-2">
<label htmlFor={`${id}`} className="font-medium text-lg">
{quantity}
</label>
<label htmlFor={`${id}`} className="font-medium text-lg">
{name}
</label>
</div>
</div>
{/* Add CSS to dynamically change bg-color for badges? */}
<p className="TimeBadge">{indicator}</p>
<button type="button" id={id} onClick={handleDelete}>
Delete
</button>
<div className="flex items-center gap-4">
<p className="">{indicator}</p>
<Button type="button" id={id} onClick={handleDelete}>
Delete
</Button>
</div>
</li>
);
}
101 changes: 81 additions & 20 deletions src/components/ManageListForms/AddItemForm.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
import { useState } from 'react';
import { addItem } from '../../api/firebase';
import toast from 'react-hot-toast';
import { Button } from '../ui/button';
import { Input } from '../ui/input';
import {
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue,
} from '../ui/select';

export default function AddItemForm({ listPath, data }) {
export default function AddItemForm({ listPath, data, handleOpenModal }) {
const [formData, setFormData] = useState({
itemName: '',
itemQuantity: 1,
daysUntilNextPurchase: '',
});

const resetItemName = () => {
setFormData((prevFormData) => ({
...prevFormData,
itemName: '',
}));
};

const handleSubmit = async (event) => {
event.preventDefault();

Expand All @@ -17,12 +34,20 @@ export default function AddItemForm({ listPath, data }) {

const match = data.find((item) => item.name === formattedItemName);

if (formattedItemName.length === 0) {
toast.error(`No numbers or special characters.`);
resetItemName();
return;
}

try {
if (!match) {
if (!match && formattedItemName.length >= 1) {
handleOpenModal();
await addItem(listPath, { ...formData, itemName: formattedItemName });
toast.success(`${formattedItemName} was added to your list`);
} else {
toast.error(`${formattedItemName} is already on your list`);
resetItemName();
return;
}
} catch (error) {
Expand All @@ -35,36 +60,72 @@ export default function AddItemForm({ listPath, data }) {
setFormData((prevFormData) => ({ ...prevFormData, [name]: value }));
};

const handleSelectChange = (value) => {
setFormData((prevFormData) => ({
...prevFormData,
daysUntilNextPurchase: value,
}));
};

return (
<form onSubmit={(event) => handleSubmit(event)}>
<div className="item-name">
<label htmlFor="itemName">Enter the item name: </label>
<input
<form
className="flex flex-col items-center gap-8 max-w-[400px] text-black"
onSubmit={(event) => handleSubmit(event)}
>
<div className="flex flex-col gap-2 w-full">
<label htmlFor="itemName" className="text-md font-medium">
Item name{' '}
</label>
<Input
type="text"
name="itemName"
id="itemName"
placeholder="Enter item name"
required
value={formData.itemName}
onChange={handleChange}
/>
</div>
<div className="buy-again">
<label htmlFor="daysUntilNextPurchase">
When do you think you will need to purchase this item again?
<div className="flex flex-col gap-2 w-full">
<label htmlFor="daysUntilNextPurchase" className="text-md font-medium">
How soon would you like to buy this again?
</label>
<select
name="daysUntilNextPurchase"
id="daysUntilNextPurchase"
value={formData.daysUntilNextPurchase}
onChange={handleChange}
<Select onValueChange={handleSelectChange}>
<SelectTrigger>
<SelectValue placeholder="Select Time" />
</SelectTrigger>
<SelectContent>
<SelectItem value="7">Soon (7 days)</SelectItem>
<SelectItem value="14">Kind of soon (14 days)</SelectItem>
<SelectItem value="30">Not soon (30 days)</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex flex-col items-start gap-2 w-full">
<label htmlFor="quantity" className="text-md font-medium">
Quantity
</label>
<div className="flex items-start">
<Input
type="number"
name="itemQuantity"
id="quantity"
required
value={formData.itemQuantity}
onChange={handleChange}
min={1}
max={100}
/>
</div>
</div>
<div className="flex w-full">
<Button
type="submit"
className="bg-pink text-white rounded-xl w-full hover:bg-pink hover:bg-opacity-75 text-sm"
>
<option value="">Select Time</option>
<option value="7">Soon (7 days)</option>
<option value="14">Kind of soon (14 days)</option>
<option value="30">Not soon (30 days)</option>
</select>
Add Item
</Button>
</div>
<button type="submit">Add Item</button>
</form>
);
}
16 changes: 5 additions & 11 deletions src/components/SearchBar.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Input } from './ui/input';

export function SearchBar({ allData, setDisplayData, setSearch, search }) {
const handleInputChange = (e) => {
const searchTerm = e.target.value;
Expand All @@ -9,24 +11,16 @@ export function SearchBar({ allData, setDisplayData, setSearch, search }) {

setDisplayData(filteredData);
};
const handleClear = () => {
setDisplayData(allData);
setSearch('');
};

return (
<form>
<label htmlFor="item-filter">Search for an item here: </label>
<input
<form className="text-black">
<Input
type="text"
id="item-filter"
value={search}
placeholder="Search here"
placeholder="Search..."
onChange={handleInputChange}
/>
<button type="button" onClick={handleClear}>
Clear Input
</button>
</form>
);
}
101 changes: 101 additions & 0 deletions src/components/ui/dialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';

import { cn } from '@/lib/utils';

const Dialog = DialogPrimitive.Root;

const DialogTrigger = DialogPrimitive.Trigger;

const DialogPortal = DialogPrimitive.Portal;

const DialogClose = DialogPrimitive.Close;

const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className,
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;

const DialogContent = React.forwardRef(
({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-slate-200 bg-white py-7 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] rounded-xl dark:border-slate-800 dark:bg-slate-950',
'flex flex-col items-center justify-center',
className,
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-0 focus:ring-slate-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 data-[state=open]:text-slate-500 dark:ring-offset-slate-950 dark:focus:ring-slate-300 dark:data-[state=open]:bg-slate-800 dark:data-[state=open]:text-slate-400">
<X className="h-7 w-7 text-grey" />
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
),
);
DialogContent.displayName = DialogPrimitive.Content.displayName;

const DialogHeader = ({ className, ...props }) => (
<div
className={cn('flex flex-col space-y-1.5 sm:text-left', className)}
{...props}
/>
);
DialogHeader.displayName = 'DialogHeader';

const DialogFooter = ({ className, ...props }) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className,
)}
{...props}
/>
);
DialogFooter.displayName = 'DialogFooter';

const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
'text-lg font-semibold leading-none tracking-tight',
className,
)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;

const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn('text-lg text-slate-500 dark:text-slate-400', className)}
{...props}
Hudamabkhoot marked this conversation as resolved.
Show resolved Hide resolved
/>
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;

export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
};
Loading
Loading