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 (
+
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 ? (
+
+
+
+ ) : (
+
+ )}
@@ -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",