| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- 'use client';
- import '../styles/profile.scss';
- import { useEffect, useState } from 'react';
- import Link from 'next/link';
- import { GoogleOAuthProvider, GoogleLogin, useGoogleOneTapLogin } from '@react-oauth/google';
- import useAuth from '@/hooks/useAuth';
- import { useConfigContext } from '@/contexts/configProvider';
- import { fetchApi } from '@/lib/utils/client';
- import { DropdownData } from '@/types/response/mypage/dropdown';
- import { LoginResponse } from '@/types/response/auth';
- import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
- import SignalSteamIcon from '@/public/icons/user/signal-steam.svg';
- import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
- } from '@/components/ui/dropdown-menu';
- import {
- Package,
- UserRoundCog,
- LogOut
- } from 'lucide-react';
- export default function Profile()
- {
- const config = useConfigContext();
- const clientId = config?.external?.googleClientId ?? '';
- return (
- <GoogleOAuthProvider clientId={clientId} nonce="" locale="ko">
- <ProfileInner />
- </GoogleOAuthProvider>
- );
- }
- function ProfileInner() {
- const { member, login, logout } = useAuth();
- const [open, setOpen] = useState(false);
- const [data, setData] = useState<DropdownData|null>(null);
- // 구글 로그인 핸들러
- const handleGoogleLogin = async (credentialResponse: { credential?: string }) => {
- try {
- const res = await fetchApi<LoginResponse>('/api/auth/google-login', {
- method: 'POST',
- body: {
- credential: credentialResponse.credential
- }
- });
- login(true);
- } catch {
- // silent
- }
- };
- // Google One Tap — 비로그인 상태에서만 활성화
- useGoogleOneTapLogin({
- onSuccess: handleGoogleLogin,
- onError: () => {},
- disabled: !!member
- });
- // 드롭다운 열 때 잔액 조회
- const loadData = async () => {
- const res = await fetchApi<DropdownData>('/api/mypage/dropdown');
- if (res.data) {
- setData(res.data);
- }
- };
- useEffect(() => {
- if (!open) {
- return;
- }
- loadData();
- }, [open]);
- // 충전 완료 시 팝업에서 postMessage 수신 → 잔액 갱신
- useEffect(() => {
- const handleMessage = (e: MessageEvent) => {
- if (e.data?.type === 'CHARGE_COMPLETE') {
- loadData();
- }
- };
- window.addEventListener('message', handleMessage);
- return () => window.removeEventListener('message', handleMessage);
- }, []);
- // ── 비로그인 ──────────────────────────────────
- if (!member) {
- return (
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <label className="profile-dropdown__trigger--guest">
- 로그인
- </label>
- </DropdownMenuTrigger>
- <DropdownMenuContent className="profile-dropdown__login-content" align="end">
- <div className="profile-dropdown__login-panel">
- <GoogleLogin
- onSuccess={handleGoogleLogin}
- onError={() => {}}
- size="large"
- shape="rectangular"
- logo_alignment="center"
- />
- </div>
- </DropdownMenuContent>
- </DropdownMenu>
- );
- }
- // ── 로그인 ────────────────────────────────────
- const displayName = member.name || member.sid;
- const initial = (member.name || member.sid || '?').charAt(0).toUpperCase();
- return (
- <DropdownMenu open={open} onOpenChange={setOpen}>
- <DropdownMenuTrigger asChild>
- <button type="button" className="profile-dropdown__trigger" title={displayName}>
- <Avatar className="h-8 w-8">
- <AvatarImage src={member.thumb || undefined} alt={displayName} />
- <AvatarFallback className="profile-dropdown__fallback">{initial}</AvatarFallback>
- </Avatar>
- </button>
- </DropdownMenuTrigger>
- <DropdownMenuContent className="profile-dropdown__content" align="end">
- {/* 프로필 헤더 */}
- <DropdownMenuLabel className="profile-dropdown__header">
- <Avatar>
- <AvatarImage src={member.thumb || undefined} alt={displayName} />
- <AvatarFallback className="profile-dropdown__fallback">{initial}</AvatarFallback>
- </Avatar>
- <div className="profile-dropdown__user-info">
- <div className="profile-dropdown__name">{displayName}</div>
- {data && (
- <>
- <div className="profile-dropdown__balance">
- <span className="profile-dropdown__balance-icon">P</span>
- <span>{data.spendableBalance.toLocaleString()}</span>
- </div>
- {data.isCreator && data.withdrawableBalance !== null && (
- <div className="profile-dropdown__withdraw">
- <span className="profile-dropdown__withdraw-icon">M</span>
- <span>{data.withdrawableBalance.toLocaleString()}</span>
- </div>
- )}
- </>
- )}
- </div>
- </DropdownMenuLabel>
- <DropdownMenuSeparator />
- {member.isCreator && (
- <DropdownMenuItem asChild>
- <Link href="/studio">
- <span className="profile-dropdown__menu-icon">
- <SignalSteamIcon width={20} height={20} aria-label='채널 관리' />
- </span>
- 채널 관리
- </Link>
- </DropdownMenuItem>
- )}
- <DropdownMenuItem asChild>
- <Link href="/profile">
- <span className="profile-dropdown__menu-icon">
- <UserRoundCog width={20} height={20} aria-label='내 정보' />
- </span>
- 내 정보
- </Link>
- </DropdownMenuItem>
- <DropdownMenuItem asChild>
- <Link href="/storage">
- <span className="profile-dropdown__menu-icon">
- <Package width={20} height={20} aria-label='보관함' />
- </span>
- 보관함
- </Link>
- </DropdownMenuItem>
- <DropdownMenuSeparator />
- <DropdownMenuItem className="profile-dropdown__danger" onSelect={logout}>
- <span className="profile-dropdown__menu-icon">
- <LogOut width={20} height={20} aria-label='로그아웃' />
- </span>
- 로그아웃
- </DropdownMenuItem>
- </DropdownMenuContent>
- </DropdownMenu>
- );
- }
|