diff --git a/devfront/src/app/routes.tsx b/devfront/src/app/routes.tsx index b28a124c..1586062e 100644 --- a/devfront/src/app/routes.tsx +++ b/devfront/src/app/routes.tsx @@ -8,6 +8,7 @@ import ClientConsentsPage from "../features/clients/ClientConsentsPage"; import ClientDetailsPage from "../features/clients/ClientDetailsPage"; import ClientGeneralPage from "../features/clients/ClientGeneralPage"; import ClientsPage from "../features/clients/ClientsPage"; +import ProfilePage from "../features/profile/ProfilePage"; export const router = createBrowserRouter( [ @@ -33,6 +34,7 @@ export const router = createBrowserRouter( { path: "clients/:id/consents", element: }, { path: "clients/:id/settings", element: }, { path: "audit-logs", element: }, + { path: "profile", element: }, ], }, ], diff --git a/devfront/src/components/layout/AppLayout.tsx b/devfront/src/components/layout/AppLayout.tsx index 92b458a8..cdb98dcb 100644 --- a/devfront/src/components/layout/AppLayout.tsx +++ b/devfront/src/components/layout/AppLayout.tsx @@ -1,3 +1,4 @@ +import { useQuery } from "@tanstack/react-query"; import { BadgeCheck, LogOut, @@ -5,6 +6,7 @@ import { NotebookTabs, ShieldHalf, Sun, + User as UserIcon, } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { useAuth } from "react-oidc-context"; @@ -13,6 +15,8 @@ import { t } from "../../lib/i18n"; import { resolveProfileRole } from "../../lib/role"; import LanguageSelector from "../common/LanguageSelector"; import { Toaster } from "../ui/toaster"; +import { Badge } from "../ui/badge"; +import { fetchMe } from "../../features/auth/authApi"; const navItems = [ { @@ -41,6 +45,13 @@ function AppLayout() { const [isRefreshingSession, setIsRefreshingSession] = useState(false); const [nowMs, setNowMs] = useState(() => Date.now()); + const hasAccessToken = Boolean(auth.user?.access_token); + const { data: profile } = useQuery({ + queryKey: ["userMe"], + queryFn: fetchMe, + enabled: hasAccessToken, + }); + const handleLogout = () => { if (window.confirm(t("msg.dev.logout_confirm", "로그아웃 하시겠습니까?"))) { auth.removeUser(); @@ -100,6 +111,10 @@ function AppLayout() { const currentRole = resolveProfileRole( auth.user?.profile as Record | undefined, ); + + // Use profile.role from API if available, otherwise fallback to local role + const displayRoleKey = profile?.role || currentRole; + const isDevConsoleAllowed = [ "super_admin", "tenant_admin", @@ -306,14 +321,35 @@ function AppLayout() {

{t("ui.dev.profile.menu_title", "Account")}

-
-

- {profileName} -

-

- {profileEmail} -

+
+
+

+ {profileName} +

+

+ {profileEmail} +

+
+
+ + {t(`ui.common.role.${displayRoleKey}`, displayRoleKey.toUpperCase())} + +
+ + +
+ + + +
+ {activeTab === "basic" && ( +
+ + + + + {t("ui.dev.profile.basic.title", "사용자 정보")} + + + +
+

+ + {t("ui.dev.profile.basic.id", "User ID")} +

+

+ {profile.id} +

+
+
+

+ + {t("ui.dev.profile.basic.name", "Name")} +

+

{profile.name}

+
+
+

+ + {t("ui.dev.profile.basic.email", "Email")} +

+

{profile.email}

+
+
+
+ + + + + + {t("ui.dev.profile.org.title", "조직 정보")} + + + +
+

+ {t("ui.dev.profile.org.tenant", "테넌트")} +

+

+ {displayTenant} +

+
+
+

+ {t("ui.dev.profile.org.company_code", "회사 코드")} +

+

{displayCompanyCode}

+
+
+
+
+ )} + + {activeTab === "role" && ( + + + + + {t("ui.dev.profile.role.title", "시스템 역할")} + + + {t( + "ui.dev.profile.role.description", + "현재 계정에 부여된 권한 등급입니다.", + )} + + + +
+
+ +
+
+

+ {t("ui.dev.profile.role.current", "Current Role")} +

+

+ {t(`ui.common.role.${profile.role}`, profile.role.toUpperCase())} +

+
+
+
+
+ )} +
+ + ); +} + +export default ProfilePage; diff --git a/devfront/src/locales/en.toml b/devfront/src/locales/en.toml index 6936c8b2..ceaea07d 100644 --- a/devfront/src/locales/en.toml +++ b/devfront/src/locales/en.toml @@ -1399,6 +1399,36 @@ verify = "Verify" [ui.userfront.signup.success] action = "Action" +[ui.dev.profile] +unknown_name = "Unknown User" +unknown_email = "unknown@example.com" +menu_aria = "Open account menu" +menu_title = "Account" +title = "Profile" +subtitle = "View user details and assigned roles." +loading = "Loading profile..." +error = "Failed to load profile." + +[ui.dev.profile.tab] +basic = "Basic Info" +role = "Roles & Permissions" + +[ui.dev.profile.basic] +title = "User Info" +id = "User ID" +name = "Name" +email = "Email" + +[ui.dev.profile.org] +title = "Organization Info" +tenant = "Tenant" +company_code = "Company Code" + +[ui.dev.profile.role] +title = "System Role" +description = "The permission level granted to this account." +current = "Current Role" + [ui.admin.nav] api_keys = "API Keys" audit_logs = "Audit Logs" diff --git a/devfront/src/locales/ko.toml b/devfront/src/locales/ko.toml index 3879ffd8..eb34ab28 100644 --- a/devfront/src/locales/ko.toml +++ b/devfront/src/locales/ko.toml @@ -1411,3 +1411,33 @@ tenant_dashboard = "테넌트 대시보드" user_groups = "유저 그룹" tenants = "테넌트" users = "사용자" + +[ui.dev.profile] +unknown_name = "알 수 없는 사용자" +unknown_email = "unknown@example.com" +menu_aria = "계정 메뉴 열기" +menu_title = "Account" +title = "내 정보" +subtitle = "사용자 상세 정보 및 할당된 역할(Role)을 확인합니다." +loading = "프로필 정보를 불러오는 중..." +error = "프로필 정보를 불러오지 못했습니다." + +[ui.dev.profile.tab] +basic = "기본 정보" +role = "권한 및 역할" + +[ui.dev.profile.basic] +title = "사용자 정보" +id = "사용자 ID" +name = "이름" +email = "이메일" + +[ui.dev.profile.org] +title = "조직 정보" +tenant = "테넌트" +company_code = "회사 코드" + +[ui.dev.profile.role] +title = "시스템 역할" +description = "현재 계정에 부여된 권한 등급입니다." +current = "현재 역할" diff --git a/devfront/src/locales/template.toml b/devfront/src/locales/template.toml index d3fa256f..4afd8a93 100644 --- a/devfront/src/locales/template.toml +++ b/devfront/src/locales/template.toml @@ -1410,3 +1410,33 @@ verify = "" [ui.userfront.signup.success] action = "" + +[ui.dev.profile] +unknown_name = "" +unknown_email = "" +menu_aria = "" +menu_title = "" +title = "" +subtitle = "" +loading = "" +error = "" + +[ui.dev.profile.tab] +basic = "" +role = "" + +[ui.dev.profile.basic] +title = "" +id = "" +name = "" +email = "" + +[ui.dev.profile.org] +title = "" +tenant = "" +company_code = "" + +[ui.dev.profile.role] +title = "" +description = "" +current = ""