feat(profile): role-aware nav + avatar menu + full editable profile
Build backend images / build content-svc (push) Failing after 1m59s
Build backend images / build file-svc (push) Failing after 3m18s
Build backend images / build gateway (push) Failing after 3m28s
Build backend images / build identity-svc (push) Failing after 2m1s
Build backend images / build notification-svc (push) Failing after 4m45s
Build backend images / build render-svc (push) Failing after 5m18s
Build backend images / build studio-svc (push) Failing after 2m12s

Navigation:
- UserMenu (avatar + role-aware dropdown: Dashboard, Admin Panel for admins,
  Profile, Sign out) replaces Sign In/Try Free when logged in (desktop + mobile).
- Real avatars in dashboard sidebar + a new admin-shell profile section.
- Shared Avatar primitive (image with initials fallback). SiteChrome excludes /admin.

Profile (data-collection surface for future AI video generation):
- SettingsProfile rebuilt: avatar upload + slogan, about, company, website,
  country, national code, birthdate, gender. No resume builder (per scope change).
- /api/profile forwards all fields; new user-scoped /api/profile/upload (avatar →
  MinIO via file-svc, sets avatar). Identity UpdateUserRequest/UserResponse widened
  (country/national/method); no DB migration (columns already exist).
- fa+en strings; verified GET/PATCH round-trip + logged-in SSR render.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-05 00:34:25 +03:30
parent 718564bce4
commit d4fee8d1d7
21 changed files with 659 additions and 116 deletions
+13 -1
View File
@@ -73,8 +73,20 @@ export default async function AdminLayout({
},
];
const email = user.email ?? "";
const fullName = typeof user.full_name === "string" ? user.full_name.trim() : "";
return (
<AdminShell groups={groups} brand={t("brand")} back={t("backToDashboard")}>
<AdminShell
groups={groups}
brand={t("brand")}
back={t("backToDashboard")}
user={{
name: fullName || (email ? email.split("@")[0] : "Admin"),
email,
avatarUrl: (user.avatar_url as string | null) ?? null,
}}
>
{children}
</AdminShell>
);
+1
View File
@@ -21,6 +21,7 @@ export default async function DashboardLayout({
userEmail={user.email ?? ""}
userName={user.full_name ?? null}
userId={user.id}
avatarUrl={(user.avatar_url as string | null) ?? null}
>
{children}
</DashboardShell>
+15 -3
View File
@@ -23,8 +23,20 @@ export default async function DashboardSettingsPage() {
const user = await getCurrentUser();
const email = user?.email ?? "";
const displayName =
typeof user?.full_name === "string" ? user.full_name : null;
const u = (user ?? {}) as Record<string, unknown>;
const str = (v: unknown) => (typeof v === "string" ? v : "");
const initialProfile = {
full_name: str(u.full_name),
avatar_url: typeof u.avatar_url === "string" ? u.avatar_url : null,
slogan: str(u.slogan),
about_me: str(u.about_me),
company_name: str(u.company_name),
website_name: str(u.website_name),
country_code: str(u.country_code),
national_code: str(u.national_code),
birth_date: str(u.birth_date),
gender: str(u.gender),
};
const profile = user ? await getUserProfile(user.id) : null;
const plan = profile?.plan ?? "free";
@@ -42,7 +54,7 @@ export default async function DashboardSettingsPage() {
{/* Content */}
<div className="flex-1 p-6">
<div className="mx-auto max-w-2xl space-y-6">
<SettingsProfile email={email} displayName={displayName} />
<SettingsProfile email={email} initial={initialProfile} />
<SettingsSecurity />
<SettingsBilling plan={plan} />
<SettingsNotifications />
+3 -1
View File
@@ -6,6 +6,7 @@ import { NextIntlClientProvider } from "next-intl";
import { DirectionProvider } from "@/components/layout/DirectionProvider";
import { SiteChrome } from "@/components/layout/SiteChrome";
import { getNavUser } from "@/lib/auth/session";
import { routing } from "@/i18n/routing";
import type { Locale } from "@/i18n/routing";
@@ -85,6 +86,7 @@ export default async function LocaleLayout({
const messages = await getMessages();
const isRtl = locale === "fa";
const navUser = await getNavUser();
/**
* Font class strategy:
@@ -112,7 +114,7 @@ export default async function LocaleLayout({
>
<NextIntlClientProvider messages={messages} locale={locale}>
<DirectionProvider dir={isRtl ? "rtl" : "ltr"}>
<SiteChrome>{children}</SiteChrome>
<SiteChrome user={navUser}>{children}</SiteChrome>
</DirectionProvider>
</NextIntlClientProvider>
</body>