"use client"; import * as React from "react"; import { toast } from "sonner"; import { Save, UserCircle, Camera, X, Mail, Briefcase, Building2, Shield, Loader2, } from "lucide-react"; import { Header } from "@/components/layout/header"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, CardDescription, } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Badge } from "@/components/ui/badge"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { useUserProfile } from "@/lib/user-profile"; export default function ProfilePage() { const { profile, updateProfile, initials, authentikUser, isAuthenticated, loading } = useUserProfile(); const [form, setForm] = React.useState({ jobTitle: profile.jobTitle, company: profile.company, bio: profile.bio, // Only editable when NOT coming from Authentik displayName: profile.displayName, email: profile.email, avatarUrl: profile.avatarUrl, }); const [dirty, setDirty] = React.useState(false); React.useEffect(() => { setForm({ jobTitle: profile.jobTitle, company: profile.company, bio: profile.bio, displayName: profile.displayName, email: profile.email, avatarUrl: profile.avatarUrl, }); }, [profile]); const handleChange = (field: string, value: string) => { setForm((prev) => ({ ...prev, [field]: value })); setDirty(true); }; const handleSave = () => { const updates: Record = { jobTitle: form.jobTitle, company: form.company, bio: form.bio, }; if (!isAuthenticated) { updates.displayName = form.displayName; updates.email = form.email; updates.avatarUrl = form.avatarUrl; } updateProfile(updates); setDirty(false); toast.success("Profile updated"); }; const handleAvatarUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; if (!file.type.startsWith("image/")) { toast.error("Please select an image file"); return; } if (file.size > 2 * 1024 * 1024) { toast.error("Image must be under 2 MB"); return; } const reader = new FileReader(); reader.onload = () => { handleChange("avatarUrl", reader.result as string); }; reader.readAsDataURL(file); }; const removeAvatar = () => { handleChange("avatarUrl", ""); }; if (loading) { return (
); } const nameFromAuthentik = isAuthenticated && !!authentikUser?.name; const emailFromAuthentik = isAuthenticated && !!authentikUser?.email; const avatarFromAuthentik = isAuthenticated && !!authentikUser?.avatar; return (
{isAuthenticated && (

Signed in via Authentik as {authentikUser?.username}. Name, email, and avatar are managed by your identity provider.

)}
Photo {avatarFromAuthentik ? "Managed by Authentik" : "Your profile picture is visible in the header"}
{form.avatarUrl && ( )} {initials || } {!avatarFromAuthentik && ( )}
{!avatarFromAuthentik && (
{form.avatarUrl && ( )}
)} {avatarFromAuthentik && (

Update your avatar in{" "} Authentik

)} {!avatarFromAuthentik && (

JPG, PNG or WebP. Max 2 MB.

)}
Personal Information {isAuthenticated ? "Some fields are synced from Authentik" : "Update your name, email, and other details"}
handleChange("displayName", e.target.value)} placeholder="Your name" readOnly={nameFromAuthentik} className={nameFromAuthentik ? "bg-muted/40 cursor-default" : ""} />
handleChange("email", e.target.value)} placeholder="you@example.com" readOnly={emailFromAuthentik} className={emailFromAuthentik ? "bg-muted/40 cursor-default" : ""} />
{isAuthenticated && authentikUser?.groups && authentikUser.groups.length > 0 && (
{authentikUser.groups.map((group) => ( {group} ))}
)}
handleChange("jobTitle", e.target.value)} placeholder="e.g. Project Manager" />
handleChange("company", e.target.value)} placeholder="e.g. Echo Labs" />