Implement my toys edit toys and rental toys function to sqlite database
This commit is contained in:
parent
1e994e8a4c
commit
0f224287fd
|
|
@ -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<Toy, 'id' | 'name' | 'description' | 'category' | 'images' | 'unavailableRanges' | 'pricePerDay' | 'location' | 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'> & {
|
||||
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<FormResult> {
|
||||
|
||||
// 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.' };
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Toy>;
|
||||
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<string[]>(initialData?.images || ['']);
|
||||
// unavailableRanges will be initialized as empty or from initialData, but not editable in this form version.
|
||||
const [images, setImages] = useState<string[]>(initialData?.images?.length ? initialData.images : ['']);
|
||||
const [unavailableRanges, setUnavailableRanges] = useState<Toy['unavailableRanges']>(initialData?.unavailableRanges || []);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [currentUser, setCurrentUser] = useState<User | null>(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];
|
||||
|
|
@ -75,27 +90,37 @@ export default function AddToyForm({ initialData, isEditMode = false }: AddToyFo
|
|||
return;
|
||||
}
|
||||
|
||||
const toyData: Partial<Toy> = {
|
||||
name, description, category,
|
||||
if (!currentUser) {
|
||||
toast({ title: "Authentication Error", description: "Could not identify current user. Please log in again.", variant: "destructive" });
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const result = await createOrUpdateToy(toyFormData, isEditMode && initialData?.id ? initialData.id : null);
|
||||
|
||||
console.log("Submitting toy data:", toyData);
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
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: name, action: isEditMode ? t('add_toy_form.updated_action_toast') : t('add_toy_form.listed_action_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`);
|
||||
setIsLoading(false);
|
||||
router.refresh();
|
||||
} else {
|
||||
toast({ title: "Error", description: result.message, variant: "destructive" });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -174,7 +199,7 @@ export default function AddToyForm({ initialData, isEditMode = false }: AddToyFo
|
|||
</div>
|
||||
|
||||
<CardFooter className="p-0 pt-6">
|
||||
<Button type="submit" className="w-full" size="lg" disabled={isLoading}>
|
||||
<Button type="submit" className="w-full" size="lg" disabled={isLoading || !currentUser}>
|
||||
{isLoading ? (isEditMode ? t('add_toy_form.saving_button') : t('add_toy_form.listing_button')) : (
|
||||
<>
|
||||
<Save className="mr-2 h-5 w-5" />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
import db from '@/lib/db';
|
||||
import type { Toy, User } from '@/types';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
// Helper to parse toy data from DB, converting JSON strings back to objects
|
||||
const parseToy = (toyData: any): Toy => {
|
||||
|
|
@ -49,7 +50,49 @@ export function getToysByOwner(ownerId: string): Toy[] {
|
|||
return toys.map(parseToy);
|
||||
}
|
||||
|
||||
// This now fetches the full user profile from the database
|
||||
export function createToy(toyData: Omit<Toy, 'id' | 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>): Toy {
|
||||
const id = `toy-${randomUUID()}`;
|
||||
const stmt = db.prepare(
|
||||
`INSERT INTO toys (id, name, description, category, images, unavailableRanges, ownerId, pricePerDay, location)
|
||||
VALUES (@id, @name, @description, @category, @images, @unavailableRanges, @ownerId, @pricePerDay, @location)`
|
||||
);
|
||||
|
||||
stmt.run({
|
||||
...toyData,
|
||||
id,
|
||||
images: JSON.stringify(toyData.images),
|
||||
unavailableRanges: JSON.stringify(toyData.unavailableRanges),
|
||||
});
|
||||
|
||||
return getToyById(id)!;
|
||||
}
|
||||
|
||||
export function updateToy(toyData: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>): Toy {
|
||||
const stmt = db.prepare(
|
||||
`UPDATE toys SET
|
||||
name = @name,
|
||||
description = @description,
|
||||
category = @category,
|
||||
images = @images,
|
||||
unavailableRanges = @unavailableRanges,
|
||||
pricePerDay = @pricePerDay,
|
||||
location = @location
|
||||
WHERE id = @id AND ownerId = @ownerId` // Security check
|
||||
);
|
||||
|
||||
const result = stmt.run({
|
||||
...toyData,
|
||||
images: JSON.stringify(toyData.images),
|
||||
unavailableRanges: JSON.stringify(toyData.unavailableRanges),
|
||||
});
|
||||
|
||||
if (result.changes === 0) {
|
||||
throw new Error("Toy not found or user not authorized to update.");
|
||||
}
|
||||
|
||||
return getToyById(toyData.id)!;
|
||||
}
|
||||
|
||||
export function getOwnerProfile(ownerId: string): User | undefined {
|
||||
return getUserById(ownerId);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue