Profile.tsx 6.3 KB

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