Compare commits

..

No commits in common. "0d49a916c7b9f4ebb7c5b9e4517262ff0ac9009f" and "1e994e8a4c407a8458f8065ef939295e27896ca8" have entirely different histories.

27 changed files with 357 additions and 566 deletions

View File

@ -1,103 +0,0 @@
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import ToyList from '@/components/toys/ToyList';
import { getToysByOwner, getOwnerProfile, getAllToys } from '@/data/operations';
import { getI18n, getStaticParams as getLocaleStaticParams } from '@/locales/server';
import { Home, UserCircle } from 'lucide-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { ToyBrick } from 'lucide-react';
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
interface OwnerToysPageProps {
params: { ownerId: string; locale: string };
}
export default async function OwnerToysPage({ params }: OwnerToysPageProps) {
const t = await getI18n();
const ownerId = Number(params.ownerId);
const ownerToys = getToysByOwner(ownerId);
const ownerProfile = getOwnerProfile(ownerId);
const ownerNameFromToys = ownerToys.length > 0 ? ownerToys[0].ownerName : undefined;
let displayOwnerName = ownerProfile?.name || ownerNameFromToys || t('owner_toys.unknown_owner');
const pageTitle = displayOwnerName !== t('owner_toys.unknown_owner')
? t('owner_toys.title_specific', { ownerName: displayOwnerName })
: t('owner_toys.title_generic');
return (
<div className="space-y-8">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<h1 className="text-3xl font-bold font-headline text-primary">{pageTitle}</h1>
<Link href={`/${params.locale}/`} passHref>
<Button variant="outline" className="w-full sm:w-auto">
<Home className="mr-2 h-4 w-4" />
{t('owner_toys.back_to_home')}
</Button>
</Link>
</div>
{ownerProfile && (
<Card className="mb-8 shadow-lg border-accent/30">
<CardHeader>
<div className="flex flex-col sm:flex-row items-center gap-4">
<Avatar className="h-20 w-20 sm:h-24 sm:w-24 border-2 border-accent">
<AvatarImage src={ownerProfile.avatarUrl} alt={displayOwnerName} data-ai-hint="owner avatar" />
<AvatarFallback className="text-3xl bg-muted">
{displayOwnerName ? displayOwnerName.split(' ').map(n => n[0]).join('').toUpperCase() : <UserCircle />}
</AvatarFallback>
</Avatar>
<div className="text-center sm:text-left">
<CardTitle className="text-2xl font-headline text-accent">
{t('owner_toys.about_owner', { ownerName: displayOwnerName })}
</CardTitle>
{ownerNameFromToys && ownerNameFromToys !== displayOwnerName && (
<CardDescription>{t('toy_details.owner')}: {ownerNameFromToys}</CardDescription>
)}
</div>
</div>
</CardHeader>
<CardContent>
<p className="text-foreground/80 leading-relaxed">{ownerProfile.bio}</p>
</CardContent>
</Card>
)}
{ownerToys.length > 0 ? (
<ToyList toys={ownerToys} t={t} />
) : (
<Card className="text-center py-12 shadow-md">
<CardHeader>
<ToyBrick className="h-16 w-16 mx-auto text-muted-foreground mb-4" />
<CardTitle>
{displayOwnerName !== t('owner_toys.unknown_owner')
? t('owner_toys.no_toys_listed_by', { ownerName: displayOwnerName })
: t('owner_toys.owner_not_found')}
</CardTitle>
{displayOwnerName !== t('owner_toys.unknown_owner') && <CardDescription>{t('home.explore_toys')}</CardDescription>}
</CardHeader>
<CardContent>
<Link href={`/${params.locale}/`} passHref>
<Button size="lg">
{t('home.explore_toys')}
</Button>
</Link>
</CardContent>
</Card>
)}
</div>
);
}
export async function generateStaticParams() {
const localeParams = getLocaleStaticParams();
const allToys = getAllToys();
const ownerIds = Array.from(new Set(allToys.map(toy => toy.ownerId)));
const ownerParams = ownerIds.map(id => ({ ownerId: String(id) }));
return localeParams.flatMap(lang =>
ownerParams.map(owner => ({ ...lang, ...owner }))
);
}

View File

@ -13,8 +13,7 @@ interface EditUserPageProps {
export default async function EditUserPage({ params }: EditUserPageProps) {
const t = await getI18n();
const userId = Number(params.id);
const userData = getUserById(userId);
const userData = getUserById(params.id);
if (!userData) {
return (

View File

@ -53,7 +53,7 @@ export default function AdminUserManagementPage() {
}, [t, toast]);
const handleDeleteUser = async (userId: number) => {
const handleDeleteUser = async (userId: string) => {
setIsDeleting(true);
const result = await deleteUser(userId);
if (result.success) {

View File

@ -17,20 +17,20 @@ import { ArrowLeft, Send, Loader2, AlertTriangle, ToyBrick } from 'lucide-react'
import { useToast } from '@/hooks/use-toast';
import { format } from 'date-fns';
// Assume current user is 'user1' (ID: 1) for mock purposes
const currentUserId = 1;
const currentUserProfiles: Record<number, { name: string; avatarInitial: string; avatarUrl?: string }> = {
1: { name: 'Alice Wonderland', avatarInitial: 'AW', avatarUrl: 'https://placehold.co/40x40.png?text=AW' }, // Logged in user
2: { name: 'Bob The Builder', avatarInitial: 'BT', avatarUrl: 'https://placehold.co/40x40.png?text=BT' },
3: { name: 'Carol Danvers', avatarInitial: 'CD', avatarUrl: 'https://placehold.co/40x40.png?text=CD' },
4: { name: 'Charlie Brown', avatarInitial: 'CB', avatarUrl: 'https://placehold.co/40x40.png?text=CB' },
5: { name: 'Diana Prince', avatarInitial: 'DP', avatarUrl: 'https://placehold.co/40x40.png?text=DP' },
6: { name: 'Edward Nigma', avatarInitial: 'EN', avatarUrl: 'https://placehold.co/40x40.png?text=EN' },
// Assume current user is 'user1' for mock purposes
const currentUserId = 'user1';
const currentUserProfiles: Record<string, { name: string; avatarInitial: string; avatarUrl?: string }> = {
'user1': { name: 'Alice Wonderland', avatarInitial: 'AW', avatarUrl: 'https://placehold.co/40x40.png?text=AW' }, // Logged in user
'user2': { name: 'Bob The Builder', avatarInitial: 'BT', avatarUrl: 'https://placehold.co/40x40.png?text=BT' },
'user3': { name: 'Carol Danvers', avatarInitial: 'CD', avatarUrl: 'https://placehold.co/40x40.png?text=CD' },
'user4': { name: 'Charlie Brown', avatarInitial: 'CB', avatarUrl: 'https://placehold.co/40x40.png?text=CB' },
'user5': { name: 'Diana Prince', avatarInitial: 'DP', avatarUrl: 'https://placehold.co/40x40.png?text=DP' },
'user6': { name: 'Edward Nigma', avatarInitial: 'EN', avatarUrl: 'https://placehold.co/40x40.png?text=EN' },
};
// Helper function to get the other participant in a conversation
const getOtherParticipant = (request: RentalRequest, currentUserId: number) => {
const getOtherParticipant = (request: RentalRequest, currentUserId: string) => {
if (request.toy.ownerId === currentUserId) {
return { id: request.requesterId, name: request.requesterName };
}
@ -238,3 +238,5 @@ export default function MessageDetailPage({ params }: { params: { id: string } }
</div>
);
}

View File

@ -9,10 +9,10 @@ import Link from "next/link";
import { MessageSquareQuote, ToyBrick } from "lucide-react";
import { format } from 'date-fns';
const currentUserId = 1; // Assume this is the logged-in user's ID
const currentUserId = 'user1'; // Assume this is the logged-in user's ID
// Helper function to get the other participant in a conversation
const getOtherParticipant = (request: RentalRequest, currentUserId: number) => {
const getOtherParticipant = (request: RentalRequest, currentUserId: string) => {
if (request.toy.ownerId === currentUserId) {
return { id: request.requesterId, name: request.requesterName };
}

View File

@ -2,19 +2,30 @@
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { getToysByOwner } from "@/data/operations";
import { mockRentalHistory } from "@/lib/mockData";
import Link from "next/link";
import { PlusCircle, Edit3, Trash2, Eye, ToyBrick as ToyBrickIcon } from "lucide-react";
import { PlusCircle, Edit3, Trash2, Eye, ToyBrick as ToyBrickIcon, BarChartHorizontalBig } from "lucide-react";
import Image from "next/image";
import type { Toy } from "@/types";
import { Badge } from "@/components/ui/badge";
import { getI18n } from "@/locales/server";
const currentUserId = 1;
const currentUserId = 'user1';
export default async function MyToysPage() {
const t = await getI18n();
const userToys = getToysByOwner(currentUserId);
const getRentalCountForToy = (toyId: string): number => {
// NOTE: This part still uses mock data and will need to be migrated.
return mockRentalHistory.filter(entry => entry.toy.id === toyId && entry.toy.ownerId === currentUserId).length;
};
const userToysWithRentalCount = userToys.map(toy => ({
...toy,
rentalCount: getRentalCountForToy(toy.id),
}));
return (
<div className="space-y-8">
<div className="flex justify-between items-center">
@ -30,7 +41,7 @@ export default async function MyToysPage() {
</Link>
</div>
{userToys.length === 0 ? (
{userToysWithRentalCount.length === 0 ? (
<Card className="text-center py-12 shadow-md">
<CardHeader>
<ToyBrickIcon className="h-16 w-16 mx-auto text-muted-foreground mb-4" />
@ -48,8 +59,8 @@ export default async function MyToysPage() {
</Card>
) : (
<div className="space-y-6">
{userToys.map(toy => (
<ListedToyItem key={toy.id} toy={toy} t={t} />
{userToysWithRentalCount.map(toy => (
<ListedToyItem key={toy.id} toy={toy} t={t} rentalCount={toy.rentalCount} />
))}
</div>
)}
@ -60,9 +71,10 @@ export default async function MyToysPage() {
interface ListedToyItemProps {
toy: Toy & {dataAiHint?: string};
t: (key: string, params?: Record<string, string | number>) => string;
rentalCount: number;
}
function ListedToyItem({ toy, t }: ListedToyItemProps) {
function ListedToyItem({ toy, t, rentalCount }: ListedToyItemProps) {
const placeholderHint = toy.dataAiHint || toy.category.toLowerCase() || "toy";
return (
<Card className="overflow-hidden shadow-lg hover:shadow-xl transition-shadow duration-300">
@ -105,6 +117,14 @@ function ListedToyItem({ toy, t }: ListedToyItemProps) {
<span className="font-semibold">{t('toy_details.price')}: </span>
{toy.pricePerDay !== undefined ? (toy.pricePerDay > 0 ? `$${toy.pricePerDay}${t('toy_details.price_per_day')}` : t('toy_details.price_free')) : 'Not set'}
</div>
{rentalCount > 0 && (
<div className="flex items-center text-accent">
<BarChartHorizontalBig className="mr-1.5 h-4 w-4" />
{rentalCount === 1
? t('dashboard.my_toys.rental_count_one')
: t('dashboard.my_toys.rental_count_many', { count: rentalCount })}
</div>
)}
</div>
</div>
</div>

View File

@ -3,20 +3,15 @@ import { Button } from "@/components/ui/button";
import Link from "next/link";
import { ToyBrick, PlusCircle, ListOrdered, User, ShoppingBag } from "lucide-react";
import { getI18n } from "@/locales/server";
import { getToysByOwner } from "@/data/operations";
const currentUserId = 1; // Mock logged-in user ID
const userStats = {
listedToys: 3,
activeRentals: 1,
pendingRequests: 2,
};
export default async function DashboardOverviewPage() {
const t = await getI18n();
const userToys = getToysByOwner(currentUserId);
const userStats = {
listedToys: userToys.length, // Real data from DB
activeRentals: 1, // Mock data
pendingRequests: 2, // Mock data
};
return (
<div className="space-y-8">
<Card className="shadow-lg">

View File

@ -11,7 +11,7 @@ import type { Locale } from "@/locales/server";
import { Badge } from "@/components/ui/badge";
import { format } from 'date-fns';
const currentUserId = 1; // Assume this is the logged-in user's ID
const currentUserId = 'user1'; // Assume this is the logged-in user's ID
export default async function RentalHistoryPage({ params }: { params: { locale: Locale } }) {
const t = await getI18n();
@ -112,3 +112,4 @@ function RentalHistoryItemCard({ item, t, locale }: RentalHistoryItemCardProps)
</Card>
);
}

View File

@ -4,53 +4,55 @@ import { ListOrdered, Check, X, MessageSquareText } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import Image from "next/image";
import type { Toy, RentalRequest } from "@/types";
import { getAllToys } from "@/data/operations";
import type { Toy } from "@/types";
import { mockToys } from "@/lib/mockData";
import { Badge } from "@/components/ui/badge";
import { getI18n } from "@/locales/server";
// This is still mock data. In a real app, this would come from the database.
function getMockRequests(): RentalRequest[] {
const allToys = getAllToys();
const myToys = allToys.filter(t => t.ownerId === 1);
interface RentalRequest {
id: string;
toy: Toy;
requesterName: string;
requesterId: string;
requestedDates: string; // e.g., "Aug 5, 2024 - Aug 10, 2024"
status: 'pending' | 'approved' | 'declined';
message?: string;
dataAiHint?: string;
}
if (myToys.length === 0) return [];
const rentalRequests: RentalRequest[] = [
const rentalRequests: RentalRequest[] = [
{
id: 'req1',
toy: myToys[0],
toy: mockToys[0],
requesterName: 'Charlie Brown',
requesterId: 4,
requesterId: 'user4',
requestedDates: 'August 10, 2024 - August 17, 2024',
status: 'pending',
message: 'My son would love to play with these for his birthday week! We are very careful with toys and will ensure it is returned in perfect condition. Could we possibly pick it up on the 9th evening?',
dataAiHint: myToys[0]?.category.toLowerCase(),
dataAiHint: mockToys[0]?.category.toLowerCase(),
},
{
id: 'req2',
toy: myToys.length > 1 ? myToys[1] : myToys[0],
toy: mockToys[3],
requesterName: 'Diana Prince',
requesterId: 5,
requesterId: 'user5',
requestedDates: 'September 1, 2024 - September 5, 2024',
status: 'approved',
dataAiHint: (myToys.length > 1 ? myToys[1] : myToys[0])?.category.toLowerCase(),
dataAiHint: mockToys[3]?.category.toLowerCase(),
},
{
id: 'req3',
toy: myToys[0],
toy: mockToys[0],
requesterName: 'Edward Nigma',
requesterId: 6,
requesterId: 'user6',
requestedDates: 'July 20, 2024 - July 22, 2024',
status: 'declined',
message: 'Looking for a weekend rental.',
dataAiHint: myToys[0]?.category.toLowerCase(),
dataAiHint: mockToys[0]?.category.toLowerCase(),
},
];
return rentalRequests;
}
];
const currentUserToyRequests = getMockRequests();
const currentUserToyRequests = rentalRequests.filter(req => req.toy.ownerId === 'user1');
export default async function RentalRequestsPage() {
@ -165,3 +167,4 @@ function RequestItemCard({ request, t }: RequestItemCardProps) {
</Card>
);
}

View File

@ -3,12 +3,15 @@ import Image from 'next/image';
import { getToyById } from '@/data/operations';
import type { Toy } from '@/types';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import { ArrowLeft, DollarSign, MapPin } from 'lucide-react';
import Link from 'next/link';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { getI18n, getStaticParams as getLocaleStaticParams } from '@/locales/server';
import type { Locale } from '@/locales/server';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { addDays, parseISO } from 'date-fns';
import { getAllToys } from '@/data/operations';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { ShoppingBag } from 'lucide-react';
@ -38,6 +41,12 @@ export default async function ToyPage({ params }: ToyPageProps) {
const placeholderHint = toy.category.toLowerCase() || "toy detail";
const disabledDates = toy.unavailableRanges.map(range => {
const from = parseISO(range.startDate);
const to = parseISO(range.endDate);
return { from, to };
});
return (
<div className="container mx-auto py-8 px-4">
<Link href={`/${params.locale}/`} className="inline-flex items-center text-primary hover:underline mb-6 group">
@ -123,6 +132,23 @@ export default async function ToyPage({ params }: ToyPageProps) {
<Separator />
<Card className="shadow-md">
<CardHeader>
<CardTitle className="text-xl font-headline text-primary">{t('toy_details.availability_calendar_title')}</CardTitle>
</CardHeader>
<CardContent className="flex justify-center">
<Calendar
mode="single"
disabled={disabledDates}
month={new Date()}
className="rounded-md border"
/>
</CardContent>
<p className="text-xs text-muted-foreground mt-0 pb-4 text-center">
{t('toy_details.calendar_note')}
</p>
</Card>
<Button size="lg" className="w-full mt-6 transition-transform transform hover:scale-105">
<ShoppingBag className="mr-2 h-5 w-5" /> {t('toy_details.request_to_rent')}
</Button>

View File

@ -1,76 +0,0 @@
'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.
// Simplified for clarity and maintainability.
export type ToyFormData = {
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: number },
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);
}
// Revalidate paths to show the new data.
// This tells Next.js to re-fetch the data on the next request for these paths across all locales.
revalidatePath('/dashboard/my-toys');
revalidatePath(`/toys/${savedToy.id}`);
revalidatePath('/');
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.' };
}
}

View File

@ -3,6 +3,7 @@
import db from '@/lib/db';
import type { User } from '@/types';
import { randomUUID } from 'crypto';
import { revalidatePath } from 'next/cache';
import { getAllUsers as dbGetAllUsers, getUserById } from '@/data/operations';
@ -25,21 +26,21 @@ export async function registerUser(data: { name: string; nickname?: string; emai
return { success: false, message: 'An account with this email already exists.' };
}
const stmt = db.prepare(
'INSERT INTO users (name, nickname, email, role, avatarUrl, bio) VALUES (@name, @nickname, @email, @role, @avatarUrl, @bio)'
);
const info = stmt.run({
const newUser: User = {
id: `user-${randomUUID()}`,
name,
nickname: nickname ?? null,
nickname,
email,
role: role,
avatarUrl: '',
bio: ''
});
};
const newUserId = info.lastInsertRowid as number;
const newUser = getUserById(newUserId);
const stmt = db.prepare(
'INSERT INTO users (id, name, nickname, email, role, avatarUrl, bio) VALUES (@id, @name, @nickname, @email, @role, @avatarUrl, @bio)'
);
stmt.run(newUser);
revalidatePath('/admin/users');
@ -95,7 +96,7 @@ interface DeleteUserResult {
message: string;
}
export async function deleteUser(id: number): Promise<DeleteUserResult> {
export async function deleteUser(id: string): Promise<DeleteUserResult> {
try {
const user = getUserById(id);
if (user && (user.email === 'user@example.com' || user.email === 'admin@example.com')) {
@ -142,7 +143,7 @@ export async function getUserByEmail(email: string): Promise<User | null> {
}
interface UpdateProfileData {
id: number;
id: string;
name: string;
nickname?: string;
avatarUrl?: string;

View File

@ -1,6 +1,5 @@
import AddToyForm from '@/components/toys/AddToyForm';
import { getToyById } from '@/data/operations';
import { mockToys } from '@/lib/mockData';
import type { Toy } from '@/types';
import { Button } from '@/components/ui/button';
import Link from 'next/link';
@ -10,8 +9,14 @@ interface EditToyPageProps {
params: { id: string };
}
// Server Component to fetch toy data (mocked for now)
async function getToyForEdit(id: string): Promise<Partial<Toy> | undefined> {
await new Promise(resolve => setTimeout(resolve, 100)); // Simulate fetch
return mockToys.find(toy => toy.id === id);
}
export default async function EditToyPage({ params }: EditToyPageProps) {
const toyData = getToyById(params.id);
const toyData = await getToyForEdit(params.id);
if (!toyData) {
return (

View File

@ -1,18 +1,20 @@
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { getToysByOwner } from "@/data/operations";
import ToyCard from "@/components/toys/ToyCard";
import { mockToys } from "@/lib/mockData"; // Using all toys for now, filter by ownerId in real app
import Link from "next/link";
import { PlusCircle, Edit3, Trash2, Eye, ToyBrick as ToyBrickIcon } from "lucide-react";
import { PlusCircle, Edit3, Trash2, Eye } from "lucide-react";
import Image from "next/image";
import type { Toy } from "@/types";
import { Badge } from "@/components/ui/badge";
const currentUserId = 1;
// Assume this is the logged-in user's ID
const currentUserId = 'user1';
export default async function MyToysPage() {
const userToys = getToysByOwner(currentUserId);
// Filter toys by current user
const userToys = mockToys.filter(toy => toy.ownerId === currentUserId);
export default function MyToysPage() {
return (
<div className="space-y-8">
<div className="flex justify-between items-center">
@ -31,7 +33,7 @@ export default async function MyToysPage() {
{userToys.length === 0 ? (
<Card className="text-center py-12 shadow-md">
<CardHeader>
<ToyBrickIcon className="h-16 w-16 mx-auto text-muted-foreground mb-4" />
<ToyBrick className="h-16 w-16 mx-auto text-muted-foreground mb-4" />
<CardTitle>No Toys Listed Yet</CardTitle>
<CardDescription>Share your first toy and spread the joy!</CardDescription>
</CardHeader>
@ -97,14 +99,14 @@ function ListedToyItem({ toy }: ListedToyItemProps) {
</div>
</div>
<p className="text-muted-foreground mt-2 text-sm line-clamp-2">{toy.description}</p>
<div className="mt-4 text-sm space-y-1">
<div>
<div className="mt-4 text-sm">
<span className="font-semibold">Price: </span>
{toy.pricePerDay !== undefined ? (toy.pricePerDay > 0 ? `$${toy.pricePerDay}/day` : 'Free') : 'Not set'}
</div>
</div>
{/* Could add more stats like number of rentals, views etc. here */}
</div>
</div>
</Card>
);
}

View File

@ -1,21 +1,16 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { ToyBrick, PlusCircle, ListOrdered, User, ShoppingBag } from "lucide-react";
import { getToysByOwner } from "@/data/operations";
const currentUserId = 1; // Mock logged-in user ID
export default async function DashboardOverviewPage() {
const userToys = getToysByOwner(currentUserId);
const userStats = {
listedToys: userToys.length, // Real data from DB
activeRentals: 1, // Mock data
pendingRequests: 2, // Mock data
};
// Mock data for dashboard overview
const userStats = {
listedToys: 3,
activeRentals: 1, // Toys I'm renting
pendingRequests: 2, // Requests for my toys
};
export default function DashboardOverviewPage() {
return (
<div className="space-y-8">
<Card className="shadow-lg">

View File

@ -1,24 +1,20 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { ShoppingBag, ToyBrick } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import Image from "next/image";
import type { Toy } from "@/types";
import { getAllToys } from "@/data/operations";
import { mockToys } from "@/lib/mockData"; // Using all toys for now
// Mock data: toys rented by the current user
// In a real app, this would come from a database query based on rental records
const rentedToys: (Toy & { rentalEndDate?: string, dataAiHint?: string })[] = [
{ ...mockToys[1], rentalEndDate: "2024-08-15", dataAiHint: mockToys[1]?.category.toLowerCase() }, // Remote Control Car
{ ...mockToys[4], rentalEndDate: "2024-09-01", dataAiHint: mockToys[4]?.category.toLowerCase() }, // Beginner Guitar
];
export default function MyRentalsPage() {
const allToys = getAllToys();
// Mock data: toys rented by the current user
// In a real app, this would come from a database query based on rental records
const rentedToys: (Toy & { rentalEndDate?: string })[] = [];
if (allToys.length > 1) {
rentedToys.push({ ...allToys[1], rentalEndDate: "2024-12-15" });
}
if (allToys.length > 4) {
rentedToys.push({ ...allToys[4], rentalEndDate: "2025-01-01" });
}
return (
<div className="space-y-8">
<div>
@ -54,7 +50,7 @@ export default function MyRentalsPage() {
}
interface RentalItemCardProps {
toy: Toy & { rentalEndDate?: string };
toy: Toy & { rentalEndDate?: string, dataAiHint?: string };
}
function RentalItemCard({ toy }: RentalItemCardProps) {

View File

@ -1,55 +1,58 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { ListOrdered, Check, X } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import Image from "next/image";
import type { Toy, RentalRequest } from "@/types";
import { getAllToys } from "@/data/operations";
import type { Toy } from "@/types";
import { mockToys } from "@/lib/mockData";
import { Badge } from "@/components/ui/badge";
// This is still mock data. In a real app, this would come from the database.
function getMockRequests(): RentalRequest[] {
const allToys = getAllToys();
const myToys = allToys.filter(t => t.ownerId === 1);
interface RentalRequest {
id: string;
toy: Toy;
requesterName: string;
requesterId: string;
requestedDates: string; // e.g., "Aug 5, 2024 - Aug 10, 2024"
status: 'pending' | 'approved' | 'declined';
message?: string;
dataAiHint?: string;
}
if (myToys.length === 0) return [];
const rentalRequests: RentalRequest[] = [
// Mock data: rental requests for the current user's toys
const rentalRequests: RentalRequest[] = [
{
id: 'req1',
toy: myToys[0],
toy: mockToys[0], // Colorful Building Blocks Set (owned by user1)
requesterName: 'Charlie Brown',
requesterId: 4,
requesterId: 'user4',
requestedDates: 'August 10, 2024 - August 17, 2024',
status: 'pending',
message: 'My son would love to play with these for his birthday week!',
dataAiHint: myToys[0]?.category.toLowerCase(),
dataAiHint: mockToys[0]?.category.toLowerCase(),
},
{
id: 'req2',
toy: myToys.length > 1 ? myToys[1] : myToys[0],
toy: mockToys[3], // Plush Teddy Bear (owned by user1)
requesterName: 'Diana Prince',
requesterId: 5,
requesterId: 'user5',
requestedDates: 'September 1, 2024 - September 5, 2024',
status: 'approved',
dataAiHint: (myToys.length > 1 ? myToys[1] : myToys[0])?.category.toLowerCase(),
dataAiHint: mockToys[3]?.category.toLowerCase(),
},
{
id: 'req3',
toy: myToys[0],
toy: mockToys[0],
requesterName: 'Edward Nigma',
requesterId: 6,
requesterId: 'user6',
requestedDates: 'July 20, 2024 - July 22, 2024',
status: 'declined',
message: 'Looking for a weekend rental.',
dataAiHint: myToys[0]?.category.toLowerCase(),
dataAiHint: mockToys[0]?.category.toLowerCase(),
},
];
return rentalRequests;
}
];
const currentUserToyRequests = getMockRequests();
// Assuming current user is user1 for whom these requests are relevant
const currentUserToyRequests = rentalRequests.filter(req => req.toy.ownerId === 'user1');
export default function RentalRequestsPage() {

View File

@ -1,28 +1,10 @@
import ToyList from '@/components/toys/ToyList';
import { getAllToys } from '@/data/operations';
import { mockToys } from '@/lib/mockData';
import { Button } from '@/components/ui/button';
import Link from 'next/link';
import { PlusCircle } from 'lucide-react';
export default async function HomePage() {
const toys = getAllToys();
const t = (key: string, params?: any) => {
// Basic mock t function for non-localized pages
const keyParts = key.split('.');
let text = keyParts.pop() || key;
text = text.replace(/_/g, ' ');
text = text.charAt(0).toUpperCase() + text.slice(1);
if (params) {
Object.keys(params).forEach(pKey => {
text = text.replace(`{${pKey}}`, params[pKey]);
})
}
return text;
};
export default function HomePage() {
return (
<div className="space-y-8">
<section className="text-center py-12 bg-gradient-to-r from-primary/10 via-background to-accent/10 rounded-lg shadow">
@ -51,7 +33,7 @@ export default async function HomePage() {
<h2 className="text-3xl font-bold font-headline text-center mb-8 text-primary">
Available Toys
</h2>
<ToyList toys={toys} t={t} />
<ToyList toys={mockToys.map(toy => ({...toy, dataAiHint: toy.category.toLowerCase()}))} />
</section>
</div>
);

View File

@ -1,21 +1,26 @@
import Image from 'next/image';
import { getToyById, getAllToys } from '@/data/operations';
import { mockToys } from '@/lib/mockData';
import type { Toy } from '@/types';
import { Button } from '@/components/ui/button';
import { ArrowLeft, DollarSign, MapPin, ShoppingBag, UserCircle2 } from 'lucide-react';
import AvailabilityCalendar from '@/components/toys/AvailabilityCalendar';
import { ArrowLeft, CalendarDays, DollarSign, MapPin, ShoppingBag, UserCircle2 } from 'lucide-react';
import Link from 'next/link';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
interface ToyPageProps {
params: { id: string };
}
// Server Component to fetch toy data (mocked for now)
async function getToyById(id: string): Promise<Toy | undefined> {
// In a real app, this would fetch from a database
await new Promise(resolve => setTimeout(resolve, 200)); // Simulate network delay
return mockToys.find(toy => toy.id === id);
}
export default async function ToyPage({ params }: ToyPageProps) {
const toy = getToyById(params.id);
const toy = await getToyById(params.id);
if (!toy) {
return (
@ -42,6 +47,7 @@ export default async function ToyPage({ params }: ToyPageProps) {
</Link>
<div className="grid md:grid-cols-2 gap-8 lg:gap-12 items-start">
{/* Image Gallery Section */}
<div className="space-y-4">
<div className="aspect-video relative w-full rounded-lg overflow-hidden shadow-lg">
<Image
@ -70,6 +76,7 @@ export default async function ToyPage({ params }: ToyPageProps) {
)}
</div>
{/* Toy Details Section */}
<div className="space-y-6">
<Badge variant="secondary" className="text-sm">{toy.category}</Badge>
<h1 className="text-4xl font-bold font-headline text-primary">{toy.name}</h1>
@ -81,18 +88,11 @@ export default async function ToyPage({ params }: ToyPageProps) {
<Separator />
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm">
<div className="flex items-center gap-2">
<Link href={`/owner/${toy.ownerId}/toys`} className="flex-shrink-0">
<Avatar className="h-8 w-8">
<AvatarImage src={toy.ownerAvatarUrl} alt={toy.ownerName} data-ai-hint="owner avatar" />
<AvatarFallback>{toy.ownerName.split(' ').map(n => n[0]).join('').toUpperCase()}</AvatarFallback>
</Avatar>
</Link>
<div className="flex items-center">
<UserCircle2 className="h-5 w-5 mr-2 text-accent" />
<div>
<span className="font-medium text-muted-foreground">Owner: </span>
<Link href={`/owner/${toy.ownerId}/toys`} className="text-foreground hover:underline">
{toy.ownerName}
</Link>
<span className="text-foreground">{toy.ownerName}</span>
</div>
</div>
{toy.location && (
@ -109,9 +109,7 @@ export default async function ToyPage({ params }: ToyPageProps) {
<DollarSign className="h-5 w-5 mr-2 text-accent" />
<div>
<span className="font-medium text-muted-foreground">Price: </span>
<span className="text-foreground font-semibold">
{toy.pricePerDay > 0 ? `$${toy.pricePerDay}/day` : 'Free'}
</span>
<span className="text-foreground font-semibold">{toy.pricePerDay > 0 ? `$${toy.pricePerDay}/day` : 'Free'}</span>
</div>
</div>
)}
@ -119,6 +117,8 @@ export default async function ToyPage({ params }: ToyPageProps) {
<Separator />
<AvailabilityCalendar availability={toy.availability} />
<Button size="lg" className="w-full mt-6 transition-transform transform hover:scale-105">
<ShoppingBag className="mr-2 h-5 w-5" /> Request to Rent
</Button>
@ -130,8 +130,7 @@ export default async function ToyPage({ params }: ToyPageProps) {
// Generate static paths for all toys
export async function generateStaticParams() {
const toys = getAllToys();
return toys.map((toy) => ({
return mockToys.map((toy) => ({
id: toy.id,
}));
}

View File

@ -1,7 +1,7 @@
'use client';
import { useState, useEffect } from 'react';
import { useState } 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,11 +11,8 @@ 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, User } from '@/types';
import type { Toy } 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' },
@ -33,7 +30,7 @@ const toyCategoryDefinitions = [
];
interface AddToyFormProps {
initialData?: Toy;
initialData?: Partial<Toy>;
isEditMode?: boolean;
}
@ -48,22 +45,10 @@ 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?.length ? initialData.images : ['']);
const [images, setImages] = useState<string[]>(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 [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];
@ -90,37 +75,27 @@ export default function AddToyForm({ initialData, isEditMode = false }: AddToyFo
return;
}
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: number } = {
name,
description,
category,
const toyData: Partial<Toy> = {
name, description, category,
pricePerDay: parseFloat(pricePerDay) || 0,
location,
images: images.filter(img => img.trim() !== ''),
unavailableRanges,
ownerId: currentUser.id,
unavailableRanges: initialData?.unavailableRanges || [], // Preserve existing, or empty for new
};
if (isEditMode && initialData?.id) {
toyData.id = initialData.id;
}
const result = await createOrUpdateToy(toyFormData, isEditMode && initialData?.id ? initialData.id : null);
setIsLoading(false);
console.log("Submitting toy data:", toyData);
await new Promise(resolve => setTimeout(resolve, 1500));
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')})
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`);
router.refresh();
} else {
toast({ title: "Error", description: result.message, variant: "destructive" });
}
setIsLoading(false);
};
return (
@ -199,7 +174,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 || !currentUser}>
<Button type="submit" className="w-full" size="lg" disabled={isLoading}>
{isLoading ? (isEditMode ? t('add_toy_form.saving_button') : t('add_toy_form.listing_button')) : (
<>
<Save className="mr-2 h-5 w-5" />

View File

@ -1,7 +1,6 @@
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 => {
@ -39,7 +38,7 @@ export function getToyById(id: string): Toy | undefined {
return toy ? parseToy(toy) : undefined;
}
export function getToysByOwner(ownerId: number): Toy[] {
export function getToysByOwner(ownerId: string): Toy[] {
const stmt = db.prepare(`
SELECT t.*, u.name as ownerName, u.avatarUrl as ownerAvatarUrl
FROM toys t
@ -50,50 +49,8 @@ export function getToysByOwner(ownerId: number): Toy[] {
return toys.map(parseToy);
}
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: number): User | undefined {
// This now fetches the full user profile from the database
export function getOwnerProfile(ownerId: string): User | undefined {
return getUserById(ownerId);
}
@ -104,7 +61,7 @@ export function getAllUsers(): User[] {
return stmt.all() as User[];
}
export function getUserById(id: number): User | undefined {
export function getUserById(id: string): User | undefined {
const stmt = db.prepare('SELECT * FROM users WHERE id = ?');
return stmt.get(id) as User | undefined;
}

View File

@ -15,16 +15,12 @@ db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
function initDb() {
// Check if the users table exists. If it does, we assume the DB is initialized.
const table = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = 'users'").get();
console.log("Initializing database schema if needed...");
if (!table) {
console.log("Database not found. Initializing and seeding...");
// Create Users Table
// Create Users Table if it doesn't exist
db.exec(`
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
nickname TEXT,
email TEXT NOT NULL UNIQUE,
@ -34,38 +30,50 @@ function initDb() {
);
`);
// Create Toys Table
// Migration: Add nickname column to users table if it doesn't exist
try {
const columns = db.prepare("PRAGMA table_info(users)").all();
if (!columns.some((col: any) => col.name === 'nickname')) {
console.log("Adding 'nickname' column to 'users' table...");
db.exec('ALTER TABLE users ADD COLUMN nickname TEXT');
}
} catch (error) {
console.error("Error during 'users' table migration:", error);
}
// Create Toys Table if it doesn't exist
db.exec(`
CREATE TABLE toys (
CREATE TABLE IF NOT EXISTS toys (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL,
category TEXT NOT NULL,
images TEXT,
unavailableRanges TEXT,
ownerId INTEGER NOT NULL,
ownerId TEXT NOT NULL,
pricePerDay REAL,
location TEXT,
FOREIGN KEY (ownerId) REFERENCES users(id) ON DELETE CASCADE
);
`);
console.log("Seeding initial data...");
console.log("Seeding initial data if missing...");
// Prepare insert statements
// Use INSERT OR IGNORE to only add data if the primary key doesn't exist.
// This prevents errors on subsequent runs and adds missing users/toys without overwriting.
const insertUser = db.prepare(`
INSERT INTO users (id, name, nickname, email, role, avatarUrl, bio)
INSERT OR IGNORE INTO users (id, name, nickname, email, role, avatarUrl, bio)
VALUES (@id, @name, @nickname, @email, @role, @avatarUrl, @bio)
`);
const insertToy = db.prepare(`
INSERT INTO toys (id, name, description, category, images, unavailableRanges, ownerId, pricePerDay, location)
INSERT OR IGNORE INTO toys (id, name, description, category, images, unavailableRanges, ownerId, pricePerDay, location)
VALUES (@id, @name, @description, @category, @images, @unavailableRanges, @ownerId, @pricePerDay, @location)
`);
// Use a transaction for efficiency
const seedData = db.transaction(() => {
for (const user of rawUsers) {
const insertManyUsers = db.transaction((users: User[]) => {
for (const user of users) {
insertUser.run({
id: user.id,
name: user.name,
@ -76,7 +84,10 @@ function initDb() {
bio: user.bio ?? null
});
}
for (const toy of rawToys) {
});
const insertManyToys = db.transaction((toys) => {
for (const toy of toys) {
insertToy.run({
...toy,
images: JSON.stringify(toy.images),
@ -85,12 +96,10 @@ function initDb() {
}
});
seedData();
insertManyUsers(rawUsers);
insertManyToys(rawToys);
console.log("Database initialization and seeding complete.");
} else {
console.log("Database already initialized.");
}
}
// Initialize and export db

View File

@ -5,13 +5,13 @@ import { addDays, formatISO, subDays } from 'date-fns';
const today = new Date();
export const rawUsers: User[] = [
{ id: 1, name: 'Alice Wonderland', nickname: 'Alice', email: 'user@example.com', role: 'Admin', avatarUrl: 'https://placehold.co/100x100.png?text=AW', bio: "Lover of imaginative play and sharing joy. I have a collection of classic storybooks and dress-up costumes that my kids have outgrown but still have lots of life left in them!" },
{ id: 2, name: 'Bob The Builder', nickname: 'Bob', email: 'user2@example.com', role: 'User', avatarUrl: 'https://placehold.co/100x100.png?text=BT', bio: "Can we fix it? Yes, we can! Sharing my collection of construction toys, tools, and playsets. Always happy to help another budding builder." },
{ id: 3, name: 'Carol Danvers', nickname: 'Captain Marvel', email: 'user3@example.com', role: 'User', avatarUrl: 'https://placehold.co/100x100.png?text=CD', bio: "Higher, further, faster. Sharing toys that inspire adventure, courage, and exploration. My collection includes superhero action figures and space-themed playsets." },
{ id: 4, name: 'Charlie Brown', nickname: 'Chuck', email: 'user4@example.com', role: 'User', avatarUrl: '', bio: '' },
{ id: 5, name: 'Diana Prince', nickname: 'Wonder Woman', email: 'user5@example.com', role: 'User', avatarUrl: '', bio: '' },
{ id: 6, name: 'Edward Nigma', nickname: 'Riddler', email: 'user6@example.com', role: 'User', avatarUrl: '', bio: '' },
{ id: 7, name: 'Main Admin', nickname: 'Head Honcho', email: 'admin@example.com', role: 'Admin', avatarUrl: 'https://placehold.co/100x100.png?text=ADM', bio: 'Keeping the toy box tidy.' },
{ id: 'user1', name: 'Alice Wonderland', nickname: 'Alice', email: 'user@example.com', role: 'Admin', avatarUrl: 'https://placehold.co/100x100.png?text=AW', bio: "Lover of imaginative play and sharing joy. I have a collection of classic storybooks and dress-up costumes that my kids have outgrown but still have lots of life left in them!" },
{ id: 'user2', name: 'Bob The Builder', nickname: 'Bob', email: 'user2@example.com', role: 'User', avatarUrl: 'https://placehold.co/100x100.png?text=BT', bio: "Can we fix it? Yes, we can! Sharing my collection of construction toys, tools, and playsets. Always happy to help another budding builder." },
{ id: 'user3', name: 'Carol Danvers', nickname: 'Captain Marvel', email: 'user3@example.com', role: 'User', avatarUrl: 'https://placehold.co/100x100.png?text=CD', bio: "Higher, further, faster. Sharing toys that inspire adventure, courage, and exploration. My collection includes superhero action figures and space-themed playsets." },
{ id: 'user4', name: 'Charlie Brown', nickname: 'Chuck', email: 'user4@example.com', role: 'User', avatarUrl: '', bio: '' },
{ id: 'user5', name: 'Diana Prince', nickname: 'Wonder Woman', email: 'user5@example.com', role: 'User', avatarUrl: '', bio: '' },
{ id: 'user6', name: 'Edward Nigma', nickname: 'Riddler', email: 'user6@example.com', role: 'User', avatarUrl: '', bio: '' },
{ id: 'admin-main', name: 'Main Admin', nickname: 'Head Honcho', email: 'admin@example.com', role: 'Admin', avatarUrl: 'https://placehold.co/100x100.png?text=ADM', bio: 'Keeping the toy box tidy.' },
];
export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[] = [
@ -25,7 +25,7 @@ export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[]
{ startDate: formatISO(addDays(today, 5), { representation: 'date' }), endDate: formatISO(addDays(today, 7), { representation: 'date' }) },
{ startDate: formatISO(addDays(today, 15), { representation: 'date' }), endDate: formatISO(addDays(today, 16), { representation: 'date' }) },
],
ownerId: 1,
ownerId: 'user1',
pricePerDay: 5,
location: 'Springfield Gardens',
},
@ -38,7 +38,7 @@ export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[]
unavailableRanges: [
{ startDate: formatISO(addDays(today, 10), { representation: 'date' }), endDate: formatISO(addDays(today, 12), { representation: 'date' }) },
],
ownerId: 2,
ownerId: 'user2',
pricePerDay: 8,
location: 'Willow Creek',
},
@ -49,7 +49,7 @@ export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[]
category: 'Electronics',
images: ['https://placehold.co/600x400.png?text=Kids+Tablet', 'https://placehold.co/600x400.png?text=Tablet+Screen'],
unavailableRanges: [],
ownerId: 3,
ownerId: 'user3',
pricePerDay: 7,
location: 'Metro City',
},
@ -62,7 +62,7 @@ export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[]
unavailableRanges: [
{ startDate: formatISO(addDays(today, 20), { representation: 'date' }), endDate: formatISO(addDays(today, 25), { representation: 'date' }) },
],
ownerId: 1,
ownerId: 'user1',
pricePerDay: 3,
location: 'Springfield Gardens',
},
@ -73,7 +73,7 @@ export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[]
category: 'Musical',
images: ['https://placehold.co/600x400.png?text=Kids+Guitar'],
unavailableRanges: [],
ownerId: 2,
ownerId: 'user2',
pricePerDay: 10,
location: 'Willow Creek',
},
@ -84,7 +84,7 @@ export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[]
category: 'Outdoor',
images: ['https://placehold.co/600x400.png?text=Sports+Kit'],
unavailableRanges: [],
ownerId: 3,
ownerId: 'user3',
pricePerDay: 6,
location: 'Metro City',
}

View File

@ -1,7 +1,7 @@
export interface MessageEntry {
id: string;
senderId: number;
senderId: string;
senderName: string;
text: string;
timestamp: string; // ISO date string
@ -15,7 +15,7 @@ export interface Toy {
images: string[];
unavailableRanges: { startDate: string; endDate: string }[];
ownerName: string;
ownerId: number;
ownerId: string;
ownerAvatarUrl?: string;
pricePerDay?: number;
location?: string;
@ -23,7 +23,7 @@ export interface Toy {
}
export interface User {
id: number;
id: string;
name: string; // This is now Full Name
nickname?: string;
email: string;
@ -40,7 +40,7 @@ export interface DailyAvailability {
export interface RentalHistoryEntry {
id:string;
userId: number;
userId: string;
toy: Toy;
rentalStartDate: string;
rentalEndDate: string;
@ -53,7 +53,7 @@ export interface RentalRequest {
id: string;
toy: Toy;
requesterName: string;
requesterId: number;
requesterId: string;
requestedDates: string;
status: 'pending' | 'approved' | 'declined';
message?: string;

Binary file not shown.

Binary file not shown.

Binary file not shown.