diff --git a/src/lib/api.ts b/src/lib/api.ts index b97e697..eebbe6b 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -18,14 +18,32 @@ interface ApiResponse { export interface User { id: string; email: string; + email_verified: boolean; full_name: string | null; avatar_url: string | null; - is_active: boolean; - is_verified: boolean; + status: string; + last_login_at: string | null; created_at: string; updated_at: string; } +export interface Organization { + id: string; + name: string; + slug: string; + description: string | null; + logo_url: string | null; + is_active: boolean; + role: string; + created_at: string; + updated_at: string; +} + +export interface OrganizationsResponse { + organizations: Organization[]; + count: number; +} + export interface Session { id: string; expires_at: string; @@ -103,6 +121,8 @@ export const api = { method: 'PATCH', body: JSON.stringify(data), }), + + organizations: () => request('/users/me/organizations'), }, }; diff --git a/src/pages/user/ProfilePage.tsx b/src/pages/user/ProfilePage.tsx index de01bac..2fe7bba 100644 --- a/src/pages/user/ProfilePage.tsx +++ b/src/pages/user/ProfilePage.tsx @@ -1,34 +1,107 @@ -import { useState } from "react"; -import { Mail, Building2, Upload, CheckCircle, AlertCircle } from "lucide-react"; +import { useState, useEffect } from "react"; +import { Mail, Building2, Upload, CheckCircle, AlertCircle, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; +import { useAuth } from "@/contexts/AuthContext"; +import { api, Organization, ApiError } from "@/lib/api"; +import { toast } from "@/hooks/use-toast"; export default function ProfilePage() { - const [name, setName] = useState("John Doe"); + const { user, refreshUser } = useAuth(); + const [name, setName] = useState(""); const [isEditing, setIsEditing] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const [organizations, setOrganizations] = useState([]); + const [orgsLoading, setOrgsLoading] = useState(true); - // Mock user data - const user = { - name: "John Doe", - email: "john@example.com", - emailVerified: true, - avatar: null, - initials: "JD", - organizations: [ - { name: "Acme Corp", role: "Admin" }, - { name: "Beta Inc", role: "Member" }, - ], + // Sync local name state with user data + useEffect(() => { + if (user?.full_name) { + setName(user.full_name); + } + }, [user?.full_name]); + + // Fetch organizations + useEffect(() => { + const fetchOrgs = async () => { + try { + const response = await api.users.organizations(); + setOrganizations(response.organizations); + } catch (error) { + if (error instanceof ApiError) { + toast({ + title: "Error loading organizations", + description: error.message, + variant: "destructive", + }); + } + } finally { + setOrgsLoading(false); + } + }; + + fetchOrgs(); + }, []); + + const getInitials = (fullName: string | null) => { + if (!fullName) return "?"; + return fullName + .split(" ") + .map((n) => n[0]) + .join("") + .toUpperCase() + .slice(0, 2); }; - const handleSave = () => { + const handleSave = async () => { + if (!name.trim()) { + toast({ + title: "Name required", + description: "Please enter your full name", + variant: "destructive", + }); + return; + } + + setIsSaving(true); + try { + await api.users.updateMe({ full_name: name.trim() }); + await refreshUser(); + setIsEditing(false); + toast({ + title: "Profile updated", + description: "Your name has been updated successfully", + }); + } catch (error) { + if (error instanceof ApiError) { + toast({ + title: "Update failed", + description: error.message, + variant: "destructive", + }); + } + } finally { + setIsSaving(false); + } + }; + + const handleCancel = () => { + setName(user?.full_name || ""); setIsEditing(false); - // Save logic here }; + if (!user) { + return ( +
+ +
+ ); + } + return (
@@ -49,9 +122,9 @@ export default function ProfilePage() { {/* Avatar */}
- + - {user.initials} + {getInitials(user.full_name)}
@@ -74,15 +147,19 @@ export default function ProfilePage() { id="name" value={name} onChange={(e) => setName(e.target.value)} + disabled={isSaving} /> - - +
) : (
- {user.name} + {user.full_name || "Not set"} @@ -103,7 +180,7 @@ export default function ProfilePage() {
{user.email} - {user.emailVerified ? ( + {user.email_verified ? ( Verified @@ -126,22 +203,41 @@ export default function ProfilePage() { Organizations you're a member of -
- {user.organizations.map((org, index) => ( -
-
-
- + {orgsLoading ? ( +
+ +
+ ) : organizations.length === 0 ? ( +

+ You're not a member of any organizations yet. +

+ ) : ( +
+ {organizations.map((org) => ( +
+
+ {org.logo_url ? ( + + + + + + + ) : ( +
+ +
+ )} + {org.name}
- {org.name} + {org.role}
- {org.role} -
- ))} -
+ ))} +
+ )}