diff --git a/src/components/app-shell.tsx b/src/components/app-shell.tsx index 0e28d99..5087d5e 100644 --- a/src/components/app-shell.tsx +++ b/src/components/app-shell.tsx @@ -1,6 +1,9 @@ import { Link, useLocation, useNavigate } from "@tanstack/react-router"; +import { useEffect, useState } from "react"; import { useAuth } from "@/lib/auth"; import { Button } from "@/components/ui/button"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { supabase } from "@/integrations/supabase/client"; import { Briefcase, Users, @@ -17,6 +20,11 @@ import type { ReactNode } from "react"; import { useBubbleCounts } from "@/lib/use-bubble-counts"; import { NotificationBell } from "@/components/notifications/notification-bell"; +function initials(name: string, email: string) { + const src = (name || email || "").trim(); + return src.split(/\s+/).map((p) => p[0]).filter(Boolean).slice(0, 2).join("").toUpperCase(); +} + interface NavItem { to: string; label: string; @@ -49,6 +57,33 @@ export function AppShell({ children }: { children: ReactNode }) { const location = useLocation(); const navigate = useNavigate(); const counts = useBubbleCounts(); + const [profile, setProfile] = useState<{ full_name: string; avatar_url: string | null } | null>(null); + + useEffect(() => { + if (!user) return; + supabase + .from("profiles") + .select("full_name, avatar_url") + .eq("id", user.id) + .maybeSingle() + .then(({ data }) => { + if (data) setProfile({ full_name: data.full_name || "", avatar_url: (data as any).avatar_url ?? null }); + }); + const ch = supabase + .channel(`profile-self-${user.id}`) + .on( + "postgres_changes", + { event: "UPDATE", schema: "public", table: "profiles", filter: `id=eq.${user.id}` }, + (payload) => { + const r = payload.new as any; + setProfile({ full_name: r.full_name || "", avatar_url: r.avatar_url ?? null }); + }, + ) + .subscribe(); + return () => { + supabase.removeChannel(ch); + }; + }, [user]); const handleSignOut = async () => { await signOut(); @@ -95,10 +130,25 @@ export function AppShell({ children }: { children: ReactNode }) {
-
{user?.email}
-
- {roles.join(" · ") || "no role"} -
+ + + {profile?.avatar_url ? : null} + + {initials(profile?.full_name || "", user?.email || "")} + + +
+
+ {profile?.full_name || user?.email} +
+
+ {roles.join(" · ") || "no role"} +
+
+ + {form.avatar_url && ( + + )} +
+

PNG or JPG, up to 5 MB.

+ + + + + + + Personal information + + + + update("full_name", e.target.value)} + /> + + + + + + update("title", e.target.value)} + placeholder="Attorney, Paralegal, etc." + /> + + + update("phone", e.target.value)} + /> + + + update("timezone", e.target.value)} + placeholder="America/New_York" + /> + + +