forked from baron/baron-sso
[#365] Devfront 프로필 메뉴 개선 및 상세 페이지 구현
This commit is contained in:
@@ -356,7 +356,7 @@ function ClientConsentsPage() {
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
variant="link"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-xs text-muted-foreground p-0 h-auto"
|
||||
onClick={() => {
|
||||
|
||||
@@ -262,7 +262,7 @@ function ClientsPage() {
|
||||
</select>
|
||||
</div>
|
||||
<Button
|
||||
variant="link"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-xs text-muted-foreground ml-auto"
|
||||
onClick={() => {
|
||||
@@ -291,7 +291,7 @@ function ClientsPage() {
|
||||
item.tone === "up"
|
||||
? "success"
|
||||
: item.tone === "down"
|
||||
? "destructive"
|
||||
? "warning"
|
||||
: "muted"
|
||||
}
|
||||
className={cn(
|
||||
|
||||
186
devfront/src/features/profile/ProfilePage.tsx
Normal file
186
devfront/src/features/profile/ProfilePage.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { User, Shield, Briefcase, Mail, Fingerprint, Building2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useAuth } from "react-oidc-context";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../components/ui/card";
|
||||
import { fetchMe } from "../auth/authApi";
|
||||
import { t } from "../../lib/i18n";
|
||||
|
||||
function ProfilePage() {
|
||||
const auth = useAuth();
|
||||
const hasAccessToken = Boolean(auth.user?.access_token);
|
||||
|
||||
const { data: profile, isLoading, error } = useQuery({
|
||||
queryKey: ["userMe"],
|
||||
queryFn: fetchMe,
|
||||
enabled: hasAccessToken,
|
||||
});
|
||||
|
||||
const [activeTab, setActiveTab] = useState<"basic" | "role">("basic");
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="p-8 text-center">
|
||||
{t("ui.dev.profile.loading", "Loading profile...")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !profile) {
|
||||
return (
|
||||
<div className="p-8 text-center text-red-500">
|
||||
{t("ui.dev.profile.error", "Failed to load profile information.")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Fallback to token information if API data is incomplete
|
||||
const displayTenant = profile.tenant?.name || profile.tenantId || auth.user?.profile?.tenant_id?.toString() || "-";
|
||||
const displayCompanyCode = profile.companyCode || auth.user?.profile?.companyCode?.toString() || "-";
|
||||
|
||||
return (
|
||||
<div className="space-y-6 max-w-4xl mx-auto">
|
||||
<div>
|
||||
<h1 className="text-3xl font-black tracking-tight">
|
||||
{t("ui.dev.profile.title", "내 정보")}
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{t(
|
||||
"ui.dev.profile.subtitle",
|
||||
"사용자 상세 정보 및 할당된 역할(Role)을 확인합니다.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-1 border-b border-border pb-px">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("basic")}
|
||||
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
|
||||
activeTab === "basic"
|
||||
? "border-primary text-primary"
|
||||
: "border-transparent text-muted-foreground hover:text-foreground hover:border-border"
|
||||
}`}
|
||||
>
|
||||
{t("ui.dev.profile.tab.basic", "기본 정보")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab("role")}
|
||||
className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors ${
|
||||
activeTab === "role"
|
||||
? "border-primary text-primary"
|
||||
: "border-transparent text-muted-foreground hover:text-foreground hover:border-border"
|
||||
}`}
|
||||
>
|
||||
{t("ui.dev.profile.tab.role", "권한 및 역할")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="pt-4">
|
||||
{activeTab === "basic" && (
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<Card className="glass-panel">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<User className="h-5 w-5 text-primary" />
|
||||
{t("ui.dev.profile.basic.title", "사용자 정보")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-muted-foreground flex items-center gap-2">
|
||||
<Fingerprint className="h-4 w-4" />
|
||||
{t("ui.dev.profile.basic.id", "User ID")}
|
||||
</p>
|
||||
<p className="text-sm break-all font-mono bg-muted/50 p-2 rounded-md">
|
||||
{profile.id}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-muted-foreground flex items-center gap-2">
|
||||
<User className="h-4 w-4" />
|
||||
{t("ui.dev.profile.basic.name", "Name")}
|
||||
</p>
|
||||
<p className="text-sm">{profile.name}</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-muted-foreground flex items-center gap-2">
|
||||
<Mail className="h-4 w-4" />
|
||||
{t("ui.dev.profile.basic.email", "Email")}
|
||||
</p>
|
||||
<p className="text-sm">{profile.email}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="glass-panel">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<Building2 className="h-5 w-5 text-primary" />
|
||||
{t("ui.dev.profile.org.title", "조직 정보")}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-muted-foreground">
|
||||
{t("ui.dev.profile.org.tenant", "테넌트")}
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
{displayTenant}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-muted-foreground">
|
||||
{t("ui.dev.profile.org.company_code", "회사 코드")}
|
||||
</p>
|
||||
<p className="text-sm">{displayCompanyCode}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === "role" && (
|
||||
<Card className="glass-panel">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<Shield className="h-5 w-5 text-primary" />
|
||||
{t("ui.dev.profile.role.title", "시스템 역할")}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t(
|
||||
"ui.dev.profile.role.description",
|
||||
"현재 계정에 부여된 권한 등급입니다.",
|
||||
)}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center gap-4 bg-muted/30 p-4 rounded-lg border border-border">
|
||||
<div className="h-12 w-12 rounded-full bg-primary/20 flex items-center justify-center text-primary">
|
||||
<Briefcase className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground font-medium uppercase tracking-wider">
|
||||
{t("ui.dev.profile.role.current", "Current Role")}
|
||||
</p>
|
||||
<p className="text-xl font-bold mt-1">
|
||||
{t(`ui.common.role.${profile.role}`, profile.role.toUpperCase())}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfilePage;
|
||||
Reference in New Issue
Block a user