diff --git a/src/app/[locale]/dashboard/profile/page.tsx b/src/app/[locale]/dashboard/profile/page.tsx index 39968ec..07dedd7 100644 --- a/src/app/[locale]/dashboard/profile/page.tsx +++ b/src/app/[locale]/dashboard/profile/page.tsx @@ -7,21 +7,13 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { Save } from 'lucide-react'; +import { Save, Loader2 } from 'lucide-react'; import { useToast } from '@/hooks/use-toast'; import { Textarea } from '@/components/ui/textarea'; import { useI18n, useCurrentLocale, useChangeLocale } from '@/locales/client'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; - -const mockUserProfile = { - name: 'Alice Wonderland', - nickname: 'Alice', - email: 'alice@example.com', - 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', -}; +import { getUserByEmail, updateUserProfile } from '@/app/actions/user'; +import type { User } from '@/types'; export default function ProfilePage() { const { toast } = useToast(); @@ -29,22 +21,48 @@ export default function ProfilePage() { const currentLocale = useCurrentLocale(); const changeLocale = useChangeLocale(); - const [name, setName] = useState(mockUserProfile.name); - const [nickname, setNickname] = useState(mockUserProfile.nickname); - const [email, setEmail] = useState(mockUserProfile.email); - 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 [user, setUser] = useState(null); + const [isFetching, setIsFetching] = useState(true); + + // Form State + const [name, setName] = useState(''); + const [nickname, setNickname] = useState(''); + const [email, setEmail] = useState(''); + const [avatarUrl, setAvatarUrl] = useState(''); + const [bio, setBio] = useState(''); + + // Local state for non-DB fields + const [phone, setPhone] = useState('555-123-4567'); // Mock + const [location, setLocation] = useState('Springfield Gardens, USA'); // Mock + + // Other state const [currentPassword, setCurrentPassword] = useState(''); const [newPassword, setNewPassword] = useState(''); const [confirmNewPassword, setConfirmNewPassword] = useState(''); - const [selectedLanguage, setSelectedLanguage] = useState(currentLocale); - const [isLoading, setIsLoading] = useState(false); const [isPasswordLoading, setIsPasswordLoading] = useState(false); const [isLanguageLoading, setIsLanguageLoading] = useState(false); + + useEffect(() => { + const fetchUser = async () => { + const userEmail = localStorage.getItem('userEmail'); + if (userEmail) { + const fetchedUser = await getUserByEmail(userEmail); + if (fetchedUser) { + setUser(fetchedUser); + setName(fetchedUser.name || ''); + setNickname(fetchedUser.nickname || ''); + setEmail(fetchedUser.email || ''); + setAvatarUrl(fetchedUser.avatarUrl || ''); + setBio(fetchedUser.bio || ''); + } + } + setIsFetching(false); + }; + fetchUser(); + }, []); + useEffect(() => { setSelectedLanguage(currentLocale); @@ -52,10 +70,22 @@ export default function ProfilePage() { const handleProfileUpdate = async (e: React.FormEvent) => { e.preventDefault(); + if (!user) return; setIsLoading(true); - await new Promise(resolve => setTimeout(resolve, 1000)); - console.log("Updating profile:", { name, nickname, avatarUrl, bio, phone, location }); - toast({ title: t('dashboard.profile.save_button'), description: "Your profile information has been saved." }); + + const result = await updateUserProfile({ + id: user.id, + name, + nickname, + avatarUrl, + bio, + }); + + if (result.success) { + toast({ title: t('dashboard.profile.save_button'), description: result.message }); + } else { + toast({ title: t('admin.users.toast_error_title'), description: result.message, variant: "destructive" }); + } setIsLoading(false); }; @@ -70,7 +100,7 @@ export default function ProfilePage() { return; } setIsPasswordLoading(true); - await new Promise(resolve => setTimeout(resolve, 1000)); + await new Promise(resolve => setTimeout(resolve, 1000)); // Mock password change console.log("Changing password"); toast({ title: t('dashboard.profile.update_password_button'), description: "Your password has been changed successfully." }); setCurrentPassword(''); @@ -85,11 +115,23 @@ export default function ProfilePage() { localStorage.setItem('userPreferredLanguage', newLang); setSelectedLanguage(newLang); toast({ title: t('dashboard.profile.language_settings_title'), description: t('dashboard.profile.language_updated_toast') }); - // No need to manually set isLanguageLoading to false if the page reloads/re-renders due to locale change - // However, if changeLocale doesn't cause an immediate unmount, manage loading state appropriately. - // For simplicity, we assume changeLocale will lead to a re-render where currentLocale updates. }; + if (isFetching) { + return ( +
+ +
+ ); + } + + if (!user) { + return ( +
+ Could not load user profile. Please try logging in again. +
+ ); + } return (
@@ -140,11 +182,13 @@ export default function ProfilePage() {
- setPhone(e.target.value)} placeholder="Your Phone" disabled={isLoading} /> + setPhone(e.target.value)} placeholder="Your Phone" disabled={true} /> +

Phone number editing is not yet implemented.

- setLocation(e.target.value)} placeholder="City, Country" disabled={isLoading} /> + setLocation(e.target.value)} placeholder="City, Country" disabled={true} /> +

Location editing is not yet implemented.

diff --git a/src/app/[locale]/toys/[id]/page.tsx b/src/app/[locale]/toys/[id]/page.tsx index 51d28cc..964d2f7 100644 --- a/src/app/[locale]/toys/[id]/page.tsx +++ b/src/app/[locale]/toys/[id]/page.tsx @@ -4,7 +4,7 @@ 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, ShoppingBag, UserCircle2 } from 'lucide-react'; +import { ArrowLeft, DollarSign, MapPin } from 'lucide-react'; import Link from 'next/link'; import { Badge } from '@/components/ui/badge'; import { Separator } from '@/components/ui/separator'; @@ -13,6 +13,8 @@ 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'; interface ToyPageProps { params: { id: string, locale: Locale }; @@ -92,14 +94,19 @@ export default async function ToyPage({ params }: ToyPageProps) {
-
- -
- {t('toy_details.owner')}: - - {toy.ownerName} +
+ + + + {toy.ownerName.split(' ').map(n => n[0]).join('').toUpperCase()} + -
+
+ {t('toy_details.owner')}: + + {toy.ownerName} + +
{toy.location && (
diff --git a/src/app/actions/user.ts b/src/app/actions/user.ts index b52d5c3..84d1ea5 100644 --- a/src/app/actions/user.ts +++ b/src/app/actions/user.ts @@ -99,7 +99,6 @@ interface DeleteUserResult { export async function deleteUser(id: string): Promise { try { const user = getUserById(id); - // Prevent deleting the main admin/user accounts for demo stability if (user && (user.email === 'user@example.com' || user.email === 'admin@example.com')) { return { success: false, message: 'This is a protected demo account and cannot be deleted.' }; } @@ -125,3 +124,58 @@ export async function deleteUser(id: string): Promise { export async function getUsers(): Promise { return dbGetAllUsers(); } + + +export async function getUserByEmail(email: string): Promise { + if (!email) { + return null; + } + try { + const user = db.prepare('SELECT * FROM users WHERE email = ?').get(email); + if (user) { + return user as User; + } + return null; + } catch (error) { + console.error("Error fetching user by email:", error); + return null; + } +} + +interface UpdateProfileData { + id: string; + name: string; + nickname?: string; + avatarUrl?: string; + bio?: string; +} + +export async function updateUserProfile(data: UpdateProfileData): Promise<{ success: boolean; message: string; }> { + const { id, name, nickname, avatarUrl, bio } = data; + + if (!id || !name) { + return { success: false, message: 'User ID and name are required.' }; + } + + try { + const stmt = db.prepare( + 'UPDATE users SET name = @name, nickname = @nickname, avatarUrl = @avatarUrl, bio = @bio WHERE id = @id' + ); + + stmt.run({ + id, + name, + nickname: nickname ?? null, + avatarUrl: avatarUrl ?? '', + bio: bio ?? '' + }); + + revalidatePath(`/dashboard/profile`); + revalidatePath(`/owner/${id}/toys`); + + return { success: true, message: 'Profile updated successfully.' }; + } catch (error) { + console.error('Update profile error:', error); + return { success: false, message: 'An unexpected error occurred while updating your profile.' }; + } +} diff --git a/src/data/operations.ts b/src/data/operations.ts index 37e6384..5af2996 100644 --- a/src/data/operations.ts +++ b/src/data/operations.ts @@ -1,7 +1,6 @@ import db from '@/lib/db'; import type { Toy, User } from '@/types'; -import { mockOwnerProfiles } from '@/lib/mockData'; // Helper to parse toy data from DB, converting JSON strings back to objects const parseToy = (toyData: any): Toy => { @@ -11,6 +10,7 @@ const parseToy = (toyData: any): Toy => { images: toyData.images ? JSON.parse(toyData.images) : [], unavailableRanges: toyData.unavailableRanges ? JSON.parse(toyData.unavailableRanges) : [], pricePerDay: Number(toyData.pricePerDay), + ownerAvatarUrl: toyData.ownerAvatarUrl, dataAiHint: toyData.category?.toLowerCase() || 'toy' }; }; @@ -19,7 +19,7 @@ const parseToy = (toyData: any): Toy => { export function getAllToys(): Toy[] { const stmt = db.prepare(` - SELECT t.*, u.name as ownerName + SELECT t.*, u.name as ownerName, u.avatarUrl as ownerAvatarUrl FROM toys t JOIN users u ON t.ownerId = u.id `); @@ -29,7 +29,7 @@ export function getAllToys(): Toy[] { export function getToyById(id: string): Toy | undefined { const stmt = db.prepare(` - SELECT t.*, u.name as ownerName + SELECT t.*, u.name as ownerName, u.avatarUrl as ownerAvatarUrl FROM toys t JOIN users u ON t.ownerId = u.id WHERE t.id = ? @@ -40,7 +40,7 @@ export function getToyById(id: string): Toy | undefined { export function getToysByOwner(ownerId: string): Toy[] { const stmt = db.prepare(` - SELECT t.*, u.name as ownerName + SELECT t.*, u.name as ownerName, u.avatarUrl as ownerAvatarUrl FROM toys t JOIN users u ON t.ownerId = u.id WHERE t.ownerId = ? @@ -49,9 +49,9 @@ export function getToysByOwner(ownerId: string): Toy[] { return toys.map(parseToy); } -// For now, we keep the mock profiles for the owner page bio/avatar -export function getOwnerProfile(ownerId: string) { - return mockOwnerProfiles[ownerId] ?? null; +// This now fetches the full user profile from the database +export function getOwnerProfile(ownerId: string): User | undefined { + return getUserById(ownerId); } // --- USER OPERATIONS --- diff --git a/src/lib/mockData.ts b/src/lib/mockData.ts index 4c662b0..a211506 100644 --- a/src/lib/mockData.ts +++ b/src/lib/mockData.ts @@ -8,13 +8,13 @@ export const rawUsers: User[] = [ { 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' }, - { id: 'user5', name: 'Diana Prince', nickname: 'Wonder Woman', email: 'user5@example.com', role: 'User' }, - { id: 'user6', name: 'Edward Nigma', nickname: 'Riddler', email: 'user6@example.com', role: 'User' }, - { id: 'admin-main', name: 'Main Admin', nickname: 'Head Honcho', email: 'admin@example.com', role: 'Admin' }, + { 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[] = [ +export const rawToys: Omit[] = [ { id: '1', name: 'Colorful Building Blocks Set', @@ -97,20 +97,3 @@ export const mockToys = []; // Legacy, will be removed later export const mockRentalHistory: RentalHistoryEntry[] = []; export const mockRentalRequests: RentalRequest[] = []; export const mockMessages: (MessageEntry & { rentalRequestId: string })[] = []; -export const mockOwnerProfiles: Record = { - 'user1': { - name: 'Alice W.', - 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!" - }, - 'user2': { - name: 'Bob T.B.', - 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." - }, - 'user3': { - name: 'Captain C.', - 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." - } -}; diff --git a/src/types/index.ts b/src/types/index.ts index 68ce1b3..0094fe2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -16,6 +16,7 @@ export interface Toy { unavailableRanges: { startDate: string; endDate: string }[]; ownerName: string; ownerId: string; + ownerAvatarUrl?: string; pricePerDay?: number; location?: string; dataAiHint?: string;