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';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { ToyBrick, Save, PlusCircle, Trash2 } from 'lucide-react';
|
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 { useI18n, useCurrentLocale } from '@/locales/client';
|
||||||
|
import { createOrUpdateToy, type ToyFormData } from '@/app/actions/toy';
|
||||||
|
import { getUserByEmail } from '@/app/actions/user';
|
||||||
|
|
||||||
|
|
||||||
const toyCategoryDefinitions = [
|
const toyCategoryDefinitions = [
|
||||||
{ key: 'educational', value: 'Educational' },
|
{ key: 'educational', value: 'Educational' },
|
||||||
|
|
@ -30,7 +33,7 @@ const toyCategoryDefinitions = [
|
||||||
];
|
];
|
||||||
|
|
||||||
interface AddToyFormProps {
|
interface AddToyFormProps {
|
||||||
initialData?: Partial<Toy>;
|
initialData?: Toy;
|
||||||
isEditMode?: boolean;
|
isEditMode?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,10 +48,22 @@ export default function AddToyForm({ initialData, isEditMode = false }: AddToyFo
|
||||||
const [category, setCategory] = useState(initialData?.category || '');
|
const [category, setCategory] = useState(initialData?.category || '');
|
||||||
const [pricePerDay, setPricePerDay] = useState(initialData?.pricePerDay?.toString() || '0');
|
const [pricePerDay, setPricePerDay] = useState(initialData?.pricePerDay?.toString() || '0');
|
||||||
const [location, setLocation] = useState(initialData?.location || '');
|
const [location, setLocation] = useState(initialData?.location || '');
|
||||||
const [images, setImages] = useState<string[]>(initialData?.images || ['']);
|
const [images, setImages] = useState<string[]>(initialData?.images?.length ? initialData.images : ['']);
|
||||||
// unavailableRanges will be initialized as empty or from initialData, but not editable in this form version.
|
|
||||||
const [unavailableRanges, setUnavailableRanges] = useState<Toy['unavailableRanges']>(initialData?.unavailableRanges || []);
|
const [unavailableRanges, setUnavailableRanges] = useState<Toy['unavailableRanges']>(initialData?.unavailableRanges || []);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
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 handleImageChange = (index: number, value: string) => {
|
||||||
const newImages = [...images];
|
const newImages = [...images];
|
||||||
|
|
@ -74,28 +89,38 @@ export default function AddToyForm({ initialData, isEditMode = false }: AddToyFo
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
return;
|
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<Toy> = {
|
const toyFormData: ToyFormData & { ownerId: string } = {
|
||||||
name, description, category,
|
name,
|
||||||
|
description,
|
||||||
|
category,
|
||||||
pricePerDay: parseFloat(pricePerDay) || 0,
|
pricePerDay: parseFloat(pricePerDay) || 0,
|
||||||
location,
|
location,
|
||||||
images: images.filter(img => img.trim() !== ''),
|
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));
|
|
||||||
|
|
||||||
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`);
|
|
||||||
setIsLoading(false);
|
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 (
|
return (
|
||||||
|
|
@ -174,7 +199,7 @@ export default function AddToyForm({ initialData, isEditMode = false }: AddToyFo
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CardFooter className="p-0 pt-6">
|
<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')) : (
|
{isLoading ? (isEditMode ? t('add_toy_form.saving_button') : t('add_toy_form.listing_button')) : (
|
||||||
<>
|
<>
|
||||||
<Save className="mr-2 h-5 w-5" />
|
<Save className="mr-2 h-5 w-5" />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
|
||||||
import db from '@/lib/db';
|
import db from '@/lib/db';
|
||||||
import type { Toy, User } from '@/types';
|
import type { Toy, User } from '@/types';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
|
||||||
// Helper to parse toy data from DB, converting JSON strings back to objects
|
// Helper to parse toy data from DB, converting JSON strings back to objects
|
||||||
const parseToy = (toyData: any): Toy => {
|
const parseToy = (toyData: any): Toy => {
|
||||||
|
|
@ -49,7 +50,49 @@ export function getToysByOwner(ownerId: string): Toy[] {
|
||||||
return toys.map(parseToy);
|
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 {
|
export function getOwnerProfile(ownerId: string): User | undefined {
|
||||||
return getUserById(ownerId);
|
return getUserById(ownerId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue