From 0f224287fd1634c5f309739fe93753072e501abd Mon Sep 17 00:00:00 2001 From: Indigo Tang Date: Sun, 6 Jul 2025 13:25:55 +0000 Subject: [PATCH] Implement my toys edit toys and rental toys function to sqlite database --- src/app/actions/toy.ts | 75 ++++++++++++++++++++++++++++++ src/components/toys/AddToyForm.tsx | 69 ++++++++++++++++++--------- src/data/operations.ts | 45 +++++++++++++++++- 3 files changed, 166 insertions(+), 23 deletions(-) create mode 100644 src/app/actions/toy.ts diff --git a/src/app/actions/toy.ts b/src/app/actions/toy.ts new file mode 100644 index 0000000..f8ce1f8 --- /dev/null +++ b/src/app/actions/toy.ts @@ -0,0 +1,75 @@ + +'use server'; + +import { revalidatePath } from 'next/cache'; +import { createToy, updateToy, getToyById } from '@/data/operations'; +import type { Toy } from '@/types'; + +// This type represents the data coming from the form. +export type ToyFormData = Omit & { + name: string; + description: string; + category: string; + images: string[]; + unavailableRanges: { startDate: string; endDate: string }[]; + pricePerDay?: number; + location?: string; +}; + +interface FormResult { + success: boolean; + message: string; + toy?: Toy; +} + +export async function createOrUpdateToy( + formData: ToyFormData & { ownerId: string }, + toyId: string | null // null for create, string for update +): Promise { + + // Basic validation + if (!formData.name || !formData.description || !formData.category || !formData.ownerId) { + return { success: false, message: 'Missing required fields.' }; + } + + try { + let savedToy: Toy; + + if (toyId) { + // Update existing toy + const existingToy = getToyById(toyId); + if (!existingToy) { + return { success: false, message: 'Toy not found.' }; + } + if (existingToy.ownerId !== formData.ownerId) { + return { success: false, message: 'Unauthorized action.' }; // Security check + } + + savedToy = updateToy({ + id: toyId, + ...formData, + }); + + } else { + // Create new toy + savedToy = createToy(formData); + } + + revalidatePath('/dashboard/my-toys'); + revalidatePath(`/toys/${savedToy.id}`); + revalidatePath('/'); + revalidatePath(`/[locale]/toys/${savedToy.id}`, 'page'); + revalidatePath(`/[locale]/dashboard/my-toys`, 'page'); + + + return { + success: true, + message: toyId ? 'Toy updated successfully!' : 'Toy created successfully!', + toy: savedToy, + }; + + } catch (error) { + console.error('Error in createOrUpdateToy:', error); + return { success: false, message: 'An unexpected error occurred.' }; + } +} diff --git a/src/components/toys/AddToyForm.tsx b/src/components/toys/AddToyForm.tsx index d2201a3..6db88d9 100644 --- a/src/components/toys/AddToyForm.tsx +++ b/src/components/toys/AddToyForm.tsx @@ -1,7 +1,7 @@ 'use client'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; @@ -11,8 +11,11 @@ import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { useToast } from '@/hooks/use-toast'; import { ToyBrick, Save, PlusCircle, Trash2 } from 'lucide-react'; -import type { Toy } from '@/types'; +import type { Toy, User } from '@/types'; import { useI18n, useCurrentLocale } from '@/locales/client'; +import { createOrUpdateToy, type ToyFormData } from '@/app/actions/toy'; +import { getUserByEmail } from '@/app/actions/user'; + const toyCategoryDefinitions = [ { key: 'educational', value: 'Educational' }, @@ -30,7 +33,7 @@ const toyCategoryDefinitions = [ ]; interface AddToyFormProps { - initialData?: Partial; + initialData?: Toy; isEditMode?: boolean; } @@ -45,10 +48,22 @@ export default function AddToyForm({ initialData, isEditMode = false }: AddToyFo const [category, setCategory] = useState(initialData?.category || ''); const [pricePerDay, setPricePerDay] = useState(initialData?.pricePerDay?.toString() || '0'); const [location, setLocation] = useState(initialData?.location || ''); - const [images, setImages] = useState(initialData?.images || ['']); - // unavailableRanges will be initialized as empty or from initialData, but not editable in this form version. + const [images, setImages] = useState(initialData?.images?.length ? initialData.images : ['']); const [unavailableRanges, setUnavailableRanges] = useState(initialData?.unavailableRanges || []); const [isLoading, setIsLoading] = useState(false); + const [currentUser, setCurrentUser] = useState(null); + + useEffect(() => { + const fetchUser = async () => { + const userEmail = localStorage.getItem('userEmail'); + if (userEmail) { + const user = await getUserByEmail(userEmail); + setCurrentUser(user); + } + }; + fetchUser(); + }, []); + const handleImageChange = (index: number, value: string) => { const newImages = [...images]; @@ -74,28 +89,38 @@ export default function AddToyForm({ initialData, isEditMode = false }: AddToyFo setIsLoading(false); return; } + + if (!currentUser) { + toast({ title: "Authentication Error", description: "Could not identify current user. Please log in again.", variant: "destructive" }); + setIsLoading(false); + return; + } - const toyData: Partial = { - name, description, category, + const toyFormData: ToyFormData & { ownerId: string } = { + name, + description, + category, pricePerDay: parseFloat(pricePerDay) || 0, location, images: images.filter(img => img.trim() !== ''), - unavailableRanges: initialData?.unavailableRanges || [], // Preserve existing, or empty for new + unavailableRanges, + ownerId: currentUser.id, }; - if (isEditMode && initialData?.id) { - toyData.id = initialData.id; - } - - - console.log("Submitting toy data:", toyData); - await new Promise(resolve => setTimeout(resolve, 1500)); - - toast({ - title: isEditMode ? t('add_toy_form.edit_title_toast') : t('add_toy_form.add_title_toast'), - description: t('add_toy_form.success_description_toast', { toyName: name, action: isEditMode ? t('add_toy_form.updated_action_toast') : t('add_toy_form.listed_action_toast')}) - }); - router.push(`/${locale}/dashboard/my-toys`); + + const result = await createOrUpdateToy(toyFormData, isEditMode && initialData?.id ? initialData.id : null); + setIsLoading(false); + + if (result.success && result.toy) { + toast({ + title: isEditMode ? t('add_toy_form.edit_title_toast') : t('add_toy_form.add_title_toast'), + description: t('add_toy_form.success_description_toast', { toyName: result.toy.name, action: isEditMode ? t('add_toy_form.updated_action_toast') : t('add_toy_form.listed_action_toast')}) + }); + router.push(`/${locale}/dashboard/my-toys`); + router.refresh(); + } else { + toast({ title: "Error", description: result.message, variant: "destructive" }); + } }; return ( @@ -174,7 +199,7 @@ export default function AddToyForm({ initialData, isEditMode = false }: AddToyFo -