Profile.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. 'use client';
  2. import '../styles/profile.scss';
  3. import { useEffect, useState } from 'react';
  4. import Link from 'next/link';
  5. import { GoogleOAuthProvider, /* GoogleLogin, useGoogleOneTapLogin */ } from '@react-oauth/google';
  6. import useAuth from '@/hooks/useAuth';
  7. import { useConfigContext } from '@/contexts/configProvider';
  8. import { fetchApi } from '@/lib/utils/client';
  9. import { DropdownData } from '@/types/response/mypage/dropdown';
  10. // import { LoginResponse } from '@/types/response/auth';
  11. import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
  12. import SignalSteamIcon from '@/public/icons/user/signal-steam.svg';
  13. import {
  14. DropdownMenu,
  15. DropdownMenuContent,
  16. DropdownMenuItem,
  17. DropdownMenuLabel,
  18. DropdownMenuSeparator,
  19. DropdownMenuTrigger,
  20. } from '@/components/ui/dropdown-menu';
  21. import {
  22. Package,
  23. UserRoundCog,
  24. LogOut
  25. } from 'lucide-react';
  26. export default function Profile()
  27. {
  28. const config = useConfigContext();
  29. const clientId = config?.external?.googleClientId ?? '';
  30. return (
  31. <GoogleOAuthProvider clientId={clientId} nonce="" locale="ko">
  32. <ProfileInner />
  33. </GoogleOAuthProvider>
  34. );
  35. }
  36. function ProfileInner() {
  37. const { member, /* login, */ logout } = useAuth();
  38. const [open, setOpen] = useState(false);
  39. const [data, setData] = useState<DropdownData|null>(null);
  40. // 구글 로그인 핸들러
  41. /*
  42. const handleGoogleLogin = async (credentialResponse: { credential?: string }) => {
  43. try {
  44. await fetchApi<LoginResponse>('/api/auth/google-login', {
  45. method: 'POST',
  46. body: {
  47. credential: credentialResponse.credential
  48. }
  49. });
  50. login(true);
  51. } catch {
  52. // silent
  53. }
  54. };
  55. */
  56. // Google One Tap — 비로그인 상태에서만 활성화
  57. /*
  58. useGoogleOneTapLogin({
  59. onSuccess: handleGoogleLogin,
  60. onError: () => {},
  61. disabled: !!member
  62. });
  63. */
  64. // 드롭다운 열 때 잔액 조회
  65. const loadData = async () => {
  66. const res = await fetchApi<DropdownData>('/api/mypage/dropdown');
  67. if (res.data) {
  68. setData(res.data);
  69. }
  70. };
  71. useEffect(() => {
  72. if (!open) {
  73. return;
  74. }
  75. loadData();
  76. }, [open]);
  77. // 충전 완료 시 팝업에서 postMessage 수신 → 잔액 갱신
  78. useEffect(() => {
  79. const handleMessage = (e: MessageEvent) => {
  80. if (e.data?.type === 'CHARGE_COMPLETE') {
  81. loadData();
  82. }
  83. };
  84. window.addEventListener('message', handleMessage);
  85. return () => window.removeEventListener('message', handleMessage);
  86. }, []);
  87. // ── 비로그인 ──────────────────────────────────
  88. if (!member) {
  89. return (
  90. <>
  91. {/*
  92. <DropdownMenu open={open} onOpenChange={setOpen}>
  93. <DropdownMenuTrigger asChild>
  94. <label className="profile-dropdown__trigger--guest">
  95. 로그인
  96. </label>
  97. </DropdownMenuTrigger>
  98. <DropdownMenuContent className="profile-dropdown__login-content" align="end">
  99. <div className="profile-dropdown__login-panel">
  100. <GoogleLogin
  101. onSuccess={handleGoogleLogin}
  102. onError={() => {}}
  103. size="large"
  104. shape="rectangular"
  105. logo_alignment="center"
  106. />
  107. </div>
  108. </DropdownMenuContent>
  109. </DropdownMenu>
  110. */}
  111. <div className="profile-dropdown__guest">
  112. <Link href="/login" className="profile-dropdown__guest-link">로그인</Link>
  113. <span className="profile-dropdown__guest-divider">|</span>
  114. <Link href="/register" className="profile-dropdown__guest-link">회원가입</Link>
  115. </div>
  116. </>
  117. );
  118. }
  119. // ── 로그인 ────────────────────────────────────
  120. const displayName = member.name || member.sid;
  121. const initial = (member.name || member.sid || '?').charAt(0).toUpperCase();
  122. return (
  123. <DropdownMenu open={open} onOpenChange={setOpen}>
  124. <DropdownMenuTrigger asChild>
  125. <button type="button" className="profile-dropdown__trigger" title={displayName}>
  126. <Avatar className="h-8 w-8">
  127. <AvatarImage src={member.thumb || undefined} alt={displayName} />
  128. <AvatarFallback className="profile-dropdown__fallback">{initial}</AvatarFallback>
  129. </Avatar>
  130. </button>
  131. </DropdownMenuTrigger>
  132. <DropdownMenuContent className="profile-dropdown__content" align="end">
  133. {/* 프로필 헤더 */}
  134. <DropdownMenuLabel className="profile-dropdown__header">
  135. <Avatar>
  136. <AvatarImage src={member.thumb || undefined} alt={displayName} />
  137. <AvatarFallback className="profile-dropdown__fallback">{initial}</AvatarFallback>
  138. </Avatar>
  139. <div className="profile-dropdown__user-info">
  140. <div className="profile-dropdown__name">{displayName}</div>
  141. {data && (
  142. <>
  143. <div className="profile-dropdown__balance">
  144. <span className="profile-dropdown__balance-icon">P</span>
  145. <span>{data.spendableBalance.toLocaleString()}</span>
  146. </div>
  147. {data.isCreator && data.withdrawableBalance !== null && (
  148. <div className="profile-dropdown__withdraw">
  149. <span className="profile-dropdown__withdraw-icon">M</span>
  150. <span>{data.withdrawableBalance.toLocaleString()}</span>
  151. </div>
  152. )}
  153. </>
  154. )}
  155. </div>
  156. </DropdownMenuLabel>
  157. <DropdownMenuSeparator />
  158. {member.isCreator && (
  159. <DropdownMenuItem asChild>
  160. <Link href="/studio">
  161. <span className="profile-dropdown__menu-icon">
  162. <SignalSteamIcon width={20} height={20} aria-label='채널 관리' />
  163. </span>
  164. 채널 관리
  165. </Link>
  166. </DropdownMenuItem>
  167. )}
  168. <DropdownMenuItem asChild>
  169. <Link href="/profile">
  170. <span className="profile-dropdown__menu-icon">
  171. <UserRoundCog width={20} height={20} aria-label='내 정보' />
  172. </span>
  173. 내 정보
  174. </Link>
  175. </DropdownMenuItem>
  176. <DropdownMenuItem asChild>
  177. <Link href="/storage">
  178. <span className="profile-dropdown__menu-icon">
  179. <Package width={20} height={20} aria-label='보관함' />
  180. </span>
  181. 보관함
  182. </Link>
  183. </DropdownMenuItem>
  184. <DropdownMenuSeparator />
  185. <DropdownMenuItem className="profile-dropdown__danger" onSelect={logout}>
  186. <span className="profile-dropdown__menu-icon">
  187. <LogOut width={20} height={20} aria-label='로그아웃' />
  188. </span>
  189. 로그아웃
  190. </DropdownMenuItem>
  191. </DropdownMenuContent>
  192. </DropdownMenu>
  193. );
  194. }