From 3db4ec20661ebde98d37ef53f0623eb35ba3d306 Mon Sep 17 00:00:00 2001 From: Indigo Tang Date: Mon, 9 Jun 2025 02:49:59 +0000 Subject: [PATCH] Updated app --- .modified | 0 docs/blueprint.md | 19 ++ src/app/dashboard/layout.tsx | 43 ++++ src/app/dashboard/my-toys/add/page.tsx | 11 + src/app/dashboard/my-toys/edit/[id]/page.tsx | 45 ++++ src/app/dashboard/my-toys/page.tsx | 112 ++++++++++ src/app/dashboard/page.tsx | 106 +++++++++ src/app/dashboard/profile/page.tsx | 162 ++++++++++++++ src/app/dashboard/rentals/page.tsx | 90 ++++++++ src/app/dashboard/requests/page.tsx | 153 +++++++++++++ src/app/globals.css | 147 +++++++------ src/app/layout.tsx | 37 +++- src/app/login/page.tsx | 107 +++++++++ src/app/page.tsx | 41 +++- src/app/register/page.tsx | 127 +++++++++++ src/app/toys/[id]/page.tsx | 136 ++++++++++++ src/components/layout/DashboardSidebar.tsx | 83 +++++++ src/components/layout/Footer.tsx | 14 ++ src/components/layout/Header.tsx | 100 +++++++++ src/components/toys/AddToyForm.tsx | 216 +++++++++++++++++++ src/components/toys/AvailabilityCalendar.tsx | 54 +++++ src/components/toys/ToyCard.tsx | 60 ++++++ src/components/toys/ToyList.tsx | 20 ++ src/lib/mockData.ts | 93 ++++++++ src/types/index.ts | 35 +++ tailwind.config.ts | 16 +- 26 files changed, 1953 insertions(+), 74 deletions(-) create mode 100644 .modified create mode 100644 docs/blueprint.md create mode 100644 src/app/dashboard/layout.tsx create mode 100644 src/app/dashboard/my-toys/add/page.tsx create mode 100644 src/app/dashboard/my-toys/edit/[id]/page.tsx create mode 100644 src/app/dashboard/my-toys/page.tsx create mode 100644 src/app/dashboard/page.tsx create mode 100644 src/app/dashboard/profile/page.tsx create mode 100644 src/app/dashboard/rentals/page.tsx create mode 100644 src/app/dashboard/requests/page.tsx create mode 100644 src/app/login/page.tsx create mode 100644 src/app/register/page.tsx create mode 100644 src/app/toys/[id]/page.tsx create mode 100644 src/components/layout/DashboardSidebar.tsx create mode 100644 src/components/layout/Footer.tsx create mode 100644 src/components/layout/Header.tsx create mode 100644 src/components/toys/AddToyForm.tsx create mode 100644 src/components/toys/AvailabilityCalendar.tsx create mode 100644 src/components/toys/ToyCard.tsx create mode 100644 src/components/toys/ToyList.tsx create mode 100644 src/lib/mockData.ts create mode 100644 src/types/index.ts diff --git a/.modified b/.modified new file mode 100644 index 0000000..e69de29 diff --git a/docs/blueprint.md b/docs/blueprint.md new file mode 100644 index 0000000..0c6f5ad --- /dev/null +++ b/docs/blueprint.md @@ -0,0 +1,19 @@ +# **App Name**: ToyShare + +## Core Features: + +- Toy Showcase: Display available toys in a visually appealing gallery format, showing key details. +- User Authentication: Allow users to log in/register via the website +- Availability Calendar: Display a simple weekly availability calendar for each toy. +- Schedule Setup: Users can set their own rental schedule +- Image display: Display all images of toys available for rental. + +## Style Guidelines: + +- Primary color: Muted blue (#6699CC), evoking trust and calmness. +- Background color: Very light blue (#F0F8FF), creates a soft and clean backdrop. +- Accent color: Soft green (#8FBC8F), provides a contrasting, harmonious accent. +- Body and headline font: 'PT Sans', a humanist sans-serif for a modern yet approachable feel. +- Use simple, outlined icons to represent toy categories and functionalities. +- Clean, grid-based layout with ample white space to highlight toys and schedule. +- Subtle transitions and loading animations to provide a smooth user experience. \ No newline at end of file diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx new file mode 100644 index 0000000..8444f3b --- /dev/null +++ b/src/app/dashboard/layout.tsx @@ -0,0 +1,43 @@ +'use client'; // Required for checking auth state on client + +import DashboardSidebar from '@/components/layout/DashboardSidebar'; +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { Loader2 } from 'lucide-react'; + +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode; +}) { + const router = useRouter(); + const [isAuthenticating, setIsAuthenticating] = useState(true); + + useEffect(() => { + // Mock authentication check + const isAuthenticated = localStorage.getItem('isToyShareAuthenticated') === 'true'; + if (!isAuthenticated) { + router.replace('/login?redirect=/dashboard'); // Redirect to login if not authenticated + } else { + setIsAuthenticating(false); + } + }, [router]); + + if (isAuthenticating) { + return ( +
+ +

Loading Dashboard...

+
+ ); + } + + return ( +
{/* Adjust min-height based on header height */} + +
+ {children} +
+
+ ); +} diff --git a/src/app/dashboard/my-toys/add/page.tsx b/src/app/dashboard/my-toys/add/page.tsx new file mode 100644 index 0000000..2dc73b7 --- /dev/null +++ b/src/app/dashboard/my-toys/add/page.tsx @@ -0,0 +1,11 @@ +import AddToyForm from '@/components/toys/AddToyForm'; + +export default function AddNewToyPage() { + return ( +
+ {/* Page Title can be part of the AddToyForm or here */} + {/*

Share a New Toy

*/} + +
+ ); +} diff --git a/src/app/dashboard/my-toys/edit/[id]/page.tsx b/src/app/dashboard/my-toys/edit/[id]/page.tsx new file mode 100644 index 0000000..82461ce --- /dev/null +++ b/src/app/dashboard/my-toys/edit/[id]/page.tsx @@ -0,0 +1,45 @@ +import AddToyForm from '@/components/toys/AddToyForm'; +import { mockToys } from '@/lib/mockData'; +import type { Toy } from '@/types'; +import { Button } from '@/components/ui/button'; +import Link from 'next/link'; +import { ArrowLeft } from 'lucide-react'; + +interface EditToyPageProps { + params: { id: string }; +} + +// Server Component to fetch toy data (mocked for now) +async function getToyForEdit(id: string): Promise | 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 = await getToyForEdit(params.id); + + if (!toyData) { + return ( +
+

Toy Not Found

+

Cannot edit a toy that does not exist.

+ + + +
+ ); + } + + return ( +
+ + + Back to My Toys + + +
+ ); +} diff --git a/src/app/dashboard/my-toys/page.tsx b/src/app/dashboard/my-toys/page.tsx new file mode 100644 index 0000000..7801b32 --- /dev/null +++ b/src/app/dashboard/my-toys/page.tsx @@ -0,0 +1,112 @@ +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +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 } from "lucide-react"; +import Image from "next/image"; +import type { Toy } from "@/types"; +import { Badge } from "@/components/ui/badge"; + +// Assume this is the logged-in user's ID +const currentUserId = 'user1'; + +// Filter toys by current user +const userToys = mockToys.filter(toy => toy.ownerId === currentUserId); + +export default function MyToysPage() { + return ( +
+
+
+

My Listed Toys

+

Manage the toys you've shared with the community.

+
+ + + +
+ + {userToys.length === 0 ? ( + + + + No Toys Listed Yet + Share your first toy and spread the joy! + + + + + + + + ) : ( +
+ {userToys.map(toy => ( + + ))} +
+ )} +
+ ); +} + +interface ListedToyItemProps { + toy: Toy & {dataAiHint?: string}; +} + +function ListedToyItem({ toy }: ListedToyItemProps) { + const placeholderHint = toy.dataAiHint || toy.category.toLowerCase() || "toy"; + return ( + +
+
+ {toy.name} +
+
+
+
+ {toy.name} + {toy.category} +
+
+ + + + + + + +
+
+

{toy.description}

+
+ Price: + {toy.pricePerDay !== undefined ? (toy.pricePerDay > 0 ? `$${toy.pricePerDay}/day` : 'Free') : 'Not set'} +
+ {/* Could add more stats like number of rentals, views etc. here */} +
+
+
+ ); +} + diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..d21f2c6 --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,106 @@ +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"; + +// 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 ( +
+ + + Welcome to Your Dashboard! + Manage your toys, rentals, and account settings all in one place. + + + } + actionLink="/dashboard/my-toys" + actionLabel="View My Toys" + /> + } + actionLink="/dashboard/rentals" + actionLabel="View My Rentals" + /> + } + actionLink="/dashboard/requests" + actionLabel="Manage Requests" + /> + + + +
+ + + Quick Actions + + + + + + + + + + + + + + Tips for Sharers + + +
    +
  • Take clear photos of your toys from multiple angles.
  • +
  • Write detailed and accurate descriptions.
  • +
  • Keep your availability calendar up-to-date.
  • +
  • Respond promptly to rental requests.
  • +
+
+
+
+
+ ); +} + +interface DashboardStatCardProps { + title: string; + value: string; + icon: React.ReactNode; + actionLink: string; + actionLabel: string; +} + +function DashboardStatCard({ title, value, icon, actionLink, actionLabel }: DashboardStatCardProps) { + return ( + + + {title} + {icon} + + +
{value}
+ + {actionLabel} → + +
+
+ ); +} diff --git a/src/app/dashboard/profile/page.tsx b/src/app/dashboard/profile/page.tsx new file mode 100644 index 0000000..9d9f4bc --- /dev/null +++ b/src/app/dashboard/profile/page.tsx @@ -0,0 +1,162 @@ +'use client'; + +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { UserCircle2, Mail, Phone, MapPin, Save } from 'lucide-react'; +import { useToast } from '@/hooks/use-toast'; +import { Textarea } from '@/components/ui/textarea'; + +// Mock user data +const mockUserProfile = { + name: 'Alice Wonderland', + email: 'alice@example.com', // Usually not editable or needs verification + 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.", + phone: '555-123-4567', + location: 'Springfield Gardens, USA', +}; + +export default function ProfilePage() { + const { toast } = useToast(); + const [name, setName] = useState(mockUserProfile.name); + const [email, setEmail] = useState(mockUserProfile.email); // For display + const [avatarUrl, setAvatarUrl] = useState(mockUserProfile.avatarUrl); + const [bio, setBio] = useState(mockUserProfile.bio); + const [phone, setPhone] = useState(mockUserProfile.phone); + const [location, setLocation] = useState(mockUserProfile.location); + const [currentPassword, setCurrentPassword] = useState(''); + const [newPassword, setNewPassword] = useState(''); + const [confirmNewPassword, setConfirmNewPassword] = useState(''); + + const [isLoading, setIsLoading] = useState(false); + const [isPasswordLoading, setIsPasswordLoading] = useState(false); + + const handleProfileUpdate = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + // Mock API call + await new Promise(resolve => setTimeout(resolve, 1000)); + console.log("Updating profile:", { name, avatarUrl, bio, phone, location }); + toast({ title: "Profile Updated", description: "Your profile information has been saved." }); + setIsLoading(false); + }; + + const handlePasswordChange = async (e: React.FormEvent) => { + e.preventDefault(); + if (newPassword !== confirmNewPassword) { + toast({ title: "Password Mismatch", description: "New passwords do not match.", variant: "destructive" }); + return; + } + if (newPassword.length < 6) { + toast({ title: "Password Too Short", description: "Password must be at least 6 characters.", variant: "destructive" }); + return; + } + setIsPasswordLoading(true); + // Mock API call + await new Promise(resolve => setTimeout(resolve, 1000)); + console.log("Changing password"); + toast({ title: "Password Updated", description: "Your password has been changed successfully." }); + setCurrentPassword(''); + setNewPassword(''); + setConfirmNewPassword(''); + setIsPasswordLoading(false); + }; + + return ( +
+
+

Profile Settings

+

Manage your personal information and account settings.

+
+ + {/* Profile Information Card */} + + + Personal Information + Update your publicly visible profile information. + +
+ +
+ + + {name.split(' ').map(n => n[0]).join('').toUpperCase()} + +
+ + setAvatarUrl(e.target.value)} placeholder="https://example.com/avatar.png" disabled={isLoading} /> +
+
+ +
+
+ + setName(e.target.value)} placeholder="Your Name" disabled={isLoading} /> +
+
+ + +
+
+ +
+ +