From 78cf43d7ffa2314ea45fa28d1f62a91890f0bb32 Mon Sep 17 00:00:00 2001 From: jaehwang Date: Wed, 16 Sep 2026 13:04:49 +0900 Subject: [PATCH] add icon for user --- backend/routers/auth.py | 9 +++-- backend/tables/user.py | 3 ++ backend/utils/google_identity.py | 6 ++- frontend/app/(ado2)/layout.tsx | 2 +- frontend/app/globals.css | 54 ++++++++++++++++++--------- frontend/components/account-badge.tsx | 33 ++++++++++------ frontend/lib/auth.ts | 1 + 7 files changed, 72 insertions(+), 36 deletions(-) diff --git a/backend/routers/auth.py b/backend/routers/auth.py index e1d6ceb..50aeec3 100644 --- a/backend/routers/auth.py +++ b/backend/routers/auth.py @@ -24,6 +24,7 @@ def user_view(user: User) -> dict: return { "email": user.email, "name": user.name, + "picture": user.picture_url, "jobs_created": user.jobs_created, "job_limit": user.job_limit, "jobs_left": user.jobs_left, @@ -33,11 +34,13 @@ def user_view(user: User) -> dict: async def find_or_create(session: AsyncSession, account) -> User: user = await session.get(User, account.sub) if user is None: - user = User(id=account.sub, email=account.email, name=account.name) + user = User(id=account.sub, email=account.email, name=account.name, + picture_url=account.picture) session.add(user) else: - # 이메일·이름은 구글 쪽에서 바뀔 수 있어 로그인할 때마다 맞춘다 - user.email, user.name = account.email, account.name + # 이메일·이름·사진은 구글 쪽에서 바뀔 수 있어 로그인할 때마다 맞춘다 + user.email, user.name, user.picture_url = (account.email, account.name, + account.picture) await session.commit() return user diff --git a/backend/tables/user.py b/backend/tables/user.py index 202a7c6..6297fb9 100644 --- a/backend/tables/user.py +++ b/backend/tables/user.py @@ -12,6 +12,7 @@ from utils.database import Base USER_ID_LENGTH = 64 EMAIL_LENGTH = 320 +PICTURE_URL_LENGTH = 512 DEFAULT_JOB_LIMIT = 3 # 소유자를 모르는 잡의 자리. 나중에 admin의 sub로 덮어쓴다 @@ -24,6 +25,8 @@ class User(Base): id: Mapped[str] = mapped_column(String(USER_ID_LENGTH), primary_key=True) email: Mapped[str] = mapped_column(String(EMAIL_LENGTH), index=True) name: Mapped[str] = mapped_column(String(255), default="") + # 구글 프로필 사진. 토큰에만 들어오는 값이라 로그인할 때 받아 둔다 + picture_url: Mapped[str] = mapped_column(String(PICTURE_URL_LENGTH), default="") # 만들 수 있는 총 잡 수. 무제한으로 둘 계정은 이 값을 크게 올린다 job_limit: Mapped[int] = mapped_column(Integer, default=DEFAULT_JOB_LIMIT) diff --git a/backend/utils/google_identity.py b/backend/utils/google_identity.py index 2b81fae..b3f3e6b 100644 --- a/backend/utils/google_identity.py +++ b/backend/utils/google_identity.py @@ -38,10 +38,11 @@ class GoogleTokenInvalid(RuntimeError): class GoogleAccount: - def __init__(self, sub: str, email: str, name: str): + def __init__(self, sub: str, email: str, name: str, picture: str): self.sub = sub self.email = email self.name = name + self.picture = picture def is_enabled() -> bool: @@ -113,4 +114,5 @@ async def verify_id_token(credential: str) -> GoogleAccount: raise GoogleTokenInvalid("이메일이 인증되지 않은 계정입니다") return GoogleAccount(sub=sub, email=str(claims.get("email") or ""), - name=str(claims.get("name") or "")) + name=str(claims.get("name") or ""), + picture=str(claims.get("picture") or "")) diff --git a/frontend/app/(ado2)/layout.tsx b/frontend/app/(ado2)/layout.tsx index 6ef3f97..5ae9958 100644 --- a/frontend/app/(ado2)/layout.tsx +++ b/frontend/app/(ado2)/layout.tsx @@ -20,13 +20,13 @@ export default function Ado2Layout({ children }: { children: React.ReactNode }) MOVING POSTER - +
무빙포스터 · 내부 빌드
diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 9efb17c..969b747 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -104,30 +104,48 @@ body { margin: 0; -webkit-font-smoothing: antialiased; letter-spacing: -0.006em; } .sidebar-logo { padding: 1.5rem 1.5rem 0.5rem; } -/* 로고 아래 로그인 상태 */ -.account-badge { +/* 사이드바 맨 아래 계정 카드 */ +.account-card { display: flex; - align-items: baseline; - gap: 0.5rem; - margin-top: 0.6rem; + align-items: center; + gap: 0.6rem; + margin: 0 0.75rem; + padding: 0.7rem 0.8rem; + border-radius: var(--radius-md); + background: var(--color-bg-darker); + border: 1px solid var(--color-border-white-10); min-width: 0; } -.account-badge-name { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: var(--text-xs); - color: var(--color-text-gray-400); -} -.account-badge-quota { +.account-photo { flex: none; - font-size: var(--text-xs); + width: 34px; + height: 34px; + border-radius: var(--radius-full); + object-fit: cover; + background: var(--color-bg-dark); +} +.account-photo--blank { + display: inline-flex; + align-items: center; + justify-content: center; font-weight: 700; color: var(--color-mint); } -.account-badge-link { +.account-body { flex: 1; min-width: 0; } +.account-name { + margin: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: var(--text-sm); + font-weight: 700; +} +.account-credit { + margin: 2px 0 0; + font-size: var(--text-xs); + color: var(--color-text-gray-400); +} +.account-link { flex: none; padding: 0; border: none; @@ -137,7 +155,7 @@ body { margin: 0; -webkit-font-smoothing: antialiased; letter-spacing: -0.006em; font-size: var(--text-xs); color: var(--color-mint); } -.account-badge-link:hover { text-decoration: underline; } +.account-link:hover { text-decoration: underline; } .sidebar-menu { flex: 1; padding: 0 0.75rem; margin-top: 1rem; overflow-y: auto; } .sidebar-item { display: flex; diff --git a/frontend/components/account-badge.tsx b/frontend/components/account-badge.tsx index d54fd06..ec2a025 100644 --- a/frontend/components/account-badge.tsx +++ b/frontend/components/account-badge.tsx @@ -1,6 +1,6 @@ "use client"; -/** 사이드바 로고 아래에 붙는 로그인 상태. 누구로 들어와 있는지와 나가는 길 */ +/** 사이드바 맨 아래 계정 카드. 누구로 들어와 있는지, 남은 크레딧, 나가는 길 */ import { useRouter } from "next/navigation"; import { signOut, useMe } from "@/lib/auth"; @@ -13,22 +13,31 @@ export default function AccountBadge() { if (me === false) { return ( - +
+ +
); } return ( -
- {me.name || me.email} - - {me.jobs_left}/{me.job_limit} - -
); diff --git a/frontend/lib/auth.ts b/frontend/lib/auth.ts index 63e3c3d..a4ad8a5 100644 --- a/frontend/lib/auth.ts +++ b/frontend/lib/auth.ts @@ -17,6 +17,7 @@ const SCRIPT_ID = "google-identity-services"; export interface Me { email: string; name: string; + picture: string; jobs_created: number; job_limit: number; jobs_left: number;