diff --git a/surfsense_web/app/dashboard/user/settings/components/ProfileContent.tsx b/surfsense_web/app/dashboard/user/settings/components/ProfileContent.tsx index b0c5869f7..fab978b49 100644 --- a/surfsense_web/app/dashboard/user/settings/components/ProfileContent.tsx +++ b/surfsense_web/app/dashboard/user/settings/components/ProfileContent.tsx @@ -1,16 +1,78 @@ "use client"; -import { Menu, User } from "lucide-react"; +import { useAtomValue } from "jotai"; +import { Loader2, Menu, User } from "lucide-react"; import { AnimatePresence, motion } from "motion/react"; import { useTranslations } from "next-intl"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import { currentUserAtom } from "@/atoms/user/user-query.atoms"; +import { updateUserMutationAtom } from "@/atoms/user/user-mutation.atoms"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; interface ProfileContentProps { onMenuClick: () => void; } +function AvatarDisplay({ url, fallback }: { url?: string; fallback: string }) { + const [hasError, setHasError] = useState(false); + + useEffect(() => { + setHasError(false); + }, [url]); + + if (url && !hasError) { + return ( + Avatar setHasError(true)} + /> + ); + } + + return ( +
+ {fallback} +
+ ); +} + export function ProfileContent({ onMenuClick }: ProfileContentProps) { const t = useTranslations("userSettings"); + const { data: user, isLoading: isUserLoading } = useAtomValue(currentUserAtom); + const { mutateAsync: updateUser, isPending } = useAtomValue(updateUserMutationAtom); + + const [displayName, setDisplayName] = useState(""); + + useEffect(() => { + if (user) { + setDisplayName(user.display_name || ""); + } + }, [user]); + + const getInitials = (email: string) => { + const name = email.split("@")[0]; + return name.slice(0, 2).toUpperCase(); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + try { + await updateUser({ + display_name: displayName || null, + }); + toast.success(t("profile_saved")); + } catch { + toast.error(t("profile_save_error")); + } + }; + + const hasChanges = displayName !== (user?.display_name || ""); return ( - {/* Profile form will be added in Task 5 */} -
-

Profile settings coming soon...

-
+ {isUserLoading ? ( +
+ +
+ ) : ( +
+
+
+
+ + +
+ +
+ + setDisplayName(e.target.value)} + /> +

+ {t("profile_display_name_hint")} +

+
+ +
+ + +
+
+
+ +
+ +
+
+ )}
@@ -77,4 +179,3 @@ export function ProfileContent({ onMenuClick }: ProfileContentProps) { ); } - diff --git a/surfsense_web/messages/en.json b/surfsense_web/messages/en.json index 131cbb360..5159d4df0 100644 --- a/surfsense_web/messages/en.json +++ b/surfsense_web/messages/en.json @@ -113,6 +113,13 @@ "profile_nav_description": "Manage your display name and avatar", "profile_title": "Profile", "profile_description": "Update your personal information", + "profile_avatar": "Profile Picture", + "profile_display_name": "Display Name", + "profile_display_name_hint": "This is how your name appears across the app", + "profile_email": "Email", + "profile_save": "Save Changes", + "profile_saved": "Profile updated successfully", + "profile_save_error": "Failed to update profile", "api_key_nav_label": "API Key", "api_key_nav_description": "Manage your API access token", "api_key_title": "API Key",