| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- 'use client';
- import { useState, useEffect, useCallback } from 'react';
- import Link from 'next/link';
- import Image from 'next/image';
- import { usePathname } from 'next/navigation';
- import {
- ArrowLeft,
- Bell,
- Calculator,
- ChartLine,
- ChevronDown,
- ChevronsUpDown,
- CircleAlert,
- Coins,
- Gauge,
- Heart,
- Landmark,
- LogOut,
- FileText,
- Target,
- Trophy,
- User,
- Users,
- Wallet,
- } from 'lucide-react';
- import {
- Sidebar as SidebarRoot,
- SidebarContent,
- SidebarHeader,
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
- SidebarGroup,
- SidebarGroupContent,
- useSidebar,
- } from '@/components/ui/sidebar';
- import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
- import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
- } from '@/components/ui/dropdown-menu';
- import { fetchApi } from '@/lib/utils/client';
- import type { StudioSettingsResponse } from '@/types/response/studio/settings';
- export default function Sidebar()
- {
- const pathname = usePathname();
- const { isMobile } = useSidebar();
- const [channel, setChannel] = useState<Pick<StudioSettingsResponse, 'isYouTubeConnected'|'channelName'|'handle'|'thumbnailUrl'>|null>(null);
- const isDonationPath = pathname.startsWith('/studio/donation');
- const isWalletPath = pathname.startsWith('/studio/wallet');
- const isSettlementPath = pathname.startsWith('/studio/settlement');
- const readCookie = (key: string, fallback: boolean) => {
- if (typeof document === 'undefined') return fallback;
- const match = document.cookie.match(new RegExp(`(?:^|; )${key}=([^;]*)`));
- if (!match) return fallback;
- return match[1] !== 'false';
- };
- const [donationOpen, setDonationOpen] = useState(() => readCookie('studio_menu_donation', true) || isDonationPath);
- const [walletOpen, setWalletOpen] = useState(() => readCookie('studio_menu_wallet', true) || isWalletPath);
- const [settlementOpen, setSettlementOpen] = useState(() => readCookie('studio_menu_settlement', true) || isSettlementPath);
- const saveCookie = useCallback((key: string, value: boolean) => {
- document.cookie = `${key}=${value}; path=/studio; max-age=${60 * 60 * 24 * 365}; SameSite=Lax`;
- }, []);
- const handleDonationToggle = useCallback((open: boolean) => {
- setDonationOpen(open);
- saveCookie('studio_menu_donation', open);
- }, [saveCookie]);
- const handleWalletToggle = useCallback((open: boolean) => {
- setWalletOpen(open);
- saveCookie('studio_menu_wallet', open);
- }, [saveCookie]);
- const handleSettlementToggle = useCallback((open: boolean) => {
- setSettlementOpen(open);
- saveCookie('studio_menu_settlement', open);
- }, [saveCookie]);
- const isActive = (href: string) => pathname === href || pathname.startsWith(href + '/');
- useEffect(() => {
- fetchApi<StudioSettingsResponse>('/api/studio/settings')
- .then(res => {
- if (res.data) {
- setChannel(res.data);
- }
- })
- .catch(() => {});
- }, []);
- const isConnected = channel?.isYouTubeConnected === true;
- return (
- <SidebarRoot collapsible="icon">
- <SidebarHeader>
- {/* 상단 Studio 라벨 + 돌아가기 버튼 */}
- <div className="flex items-center gap-1.5 px-2 py-1 group-data-[collapsible=icon]:hidden">
- <Link
- href="/"
- className="flex size-6 items-center justify-center rounded text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-foreground transition-colors"
- title="뒤로가기"
- >
- <ArrowLeft className="size-3.5" />
- </Link>
- <span className="text-xs font-semibold text-sidebar-foreground/50">Studio</span>
- </div>
- {/* YouTube 채널 카드 */}
- <SidebarMenu>
- <SidebarMenuItem>
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <SidebarMenuButton
- size="lg"
- className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
- tooltip={channel?.channelName ?? 'YouTube 채널'}
- >
- {/* 아이콘 (collapse 시 이것만 표시) */}
- <div className={`relative flex size-8 shrink-0 items-center justify-center rounded-lg ${!channel?.thumbnailUrl ? 'bg-sidebar-primary' : 'border'} text-sidebar-primary-foreground overflow-hidden`}>
- {channel?.thumbnailUrl ? (
- <Image
- src={channel.thumbnailUrl}
- alt={channel.channelName ?? 'YouTube'}
- width={32}
- height={32}
- className="size-full object-cover"
- />
- ) : (
- <span className="text-xs font-bold">
- <User />
- </span>
- )}
- {/* 미연동 경고 뱃지 */}
- {channel !== null && !isConnected && (
- <span className="absolute -right-1 -bottom-1 flex size-3.5 items-center justify-center rounded-full bg-background">
- <CircleAlert className="size-2.5 text-amber-500" />
- </span>
- )}
- </div>
- {/* 텍스트 (collapse 시 hidden) */}
- <div className="grid flex-1 text-left text-sm leading-tight">
- <span className="truncate font-semibold">
- {isConnected
- ? (channel?.channelName ?? '채널 이름')
- : '채널 미연동'}
- </span>
- <span className="truncate text-xs text-sidebar-foreground/60">
- {isConnected
- ? (channel?.handle ?? '')
- : '채널을 연결해 주세요'}
- </span>
- </div>
- <ChevronsUpDown className="ml-auto size-4 shrink-0" />
- </SidebarMenuButton>
- </DropdownMenuTrigger>
- <DropdownMenuContent
- className="w-[--anchor-width] min-w-56 rounded-lg"
- align="start"
- side={isMobile ? 'bottom' : 'right'}
- sideOffset={4}
- >
- {isConnected ? (
- <>
- <DropdownMenuLabel className="text-xs text-muted-foreground">
- {channel?.channelName}
- </DropdownMenuLabel>
- <DropdownMenuSeparator />
- <DropdownMenuItem asChild>
- <Link href="/studio/settings">채널 정보 보기</Link>
- </DropdownMenuItem>
- </>
- ) : (
- <>
- <DropdownMenuLabel className="text-xs text-muted-foreground">
- YouTube 채널이 연동되지 않았습니다
- </DropdownMenuLabel>
- <DropdownMenuSeparator />
- <DropdownMenuItem asChild>
- <Link href="/studio/settings">YouTube 연동하기</Link>
- </DropdownMenuItem>
- </>
- )}
- </DropdownMenuContent>
- </DropdownMenu>
- </SidebarMenuItem>
- </SidebarMenu>
- </SidebarHeader>
- <SidebarContent>
- <SidebarGroup>
- <SidebarGroupContent>
- <SidebarMenu>
- {/* 대시보드 */}
- <SidebarMenuItem>
- <SidebarMenuButton asChild isActive={isActive('/studio/dashboard')} tooltip="대시보드">
- <Link href="/studio/dashboard">
- <Gauge />
- <span>대시보드</span>
- </Link>
- </SidebarMenuButton>
- </SidebarMenuItem>
- {/* 후원 설정 */}
- <Collapsible open={donationOpen} onOpenChange={handleDonationToggle}>
- <SidebarMenuItem>
- <CollapsibleTrigger asChild>
- <SidebarMenuButton isActive={isDonationPath} tooltip="후원 설정">
- <Heart />
- <span>후원 설정</span>
- <ChevronDown
- className={`ml-auto size-4 transition-transform duration-200${donationOpen ? ' rotate-180' : ''}`}
- />
- </SidebarMenuButton>
- </CollapsibleTrigger>
- <CollapsibleContent>
- <SidebarMenuSub>
- <SidebarMenuSubItem>
- <SidebarMenuSubButton asChild isActive={isActive('/studio/donation/alert')}>
- <Link href="/studio/donation/alert">
- <Bell />
- <span>알림</span>
- </Link>
- </SidebarMenuSubButton>
- </SidebarMenuSubItem>
- <SidebarMenuSubItem>
- <SidebarMenuSubButton asChild isActive={isActive('/studio/donation/goal')}>
- <Link href="/studio/donation/goal">
- <Target />
- <span>목표</span>
- </Link>
- </SidebarMenuSubButton>
- </SidebarMenuSubItem>
- <SidebarMenuSubItem>
- <SidebarMenuSubButton asChild isActive={isActive('/studio/donation/rank')}>
- <Link href="/studio/donation/rank">
- <Trophy />
- <span>순위</span>
- </Link>
- </SidebarMenuSubButton>
- </SidebarMenuSubItem>
- <SidebarMenuSubItem>
- <SidebarMenuSubButton asChild isActive={isActive('/studio/donation/crew')}>
- <Link href="/studio/donation/crew">
- <Users />
- <span>크루</span>
- </Link>
- </SidebarMenuSubButton>
- </SidebarMenuSubItem>
- </SidebarMenuSub>
- </CollapsibleContent>
- </SidebarMenuItem>
- </Collapsible>
- {/* 지갑 */}
- <Collapsible open={walletOpen} onOpenChange={handleWalletToggle}>
- <SidebarMenuItem>
- <CollapsibleTrigger asChild>
- <SidebarMenuButton isActive={isWalletPath} tooltip="지갑">
- <Wallet />
- <span>지갑</span>
- <ChevronDown
- className={`ml-auto size-4 transition-transform duration-200${walletOpen ? ' rotate-180' : ''}`}
- />
- </SidebarMenuButton>
- </CollapsibleTrigger>
- <CollapsibleContent>
- <SidebarMenuSub>
- <SidebarMenuSubItem>
- <SidebarMenuSubButton asChild isActive={isActive('/studio/wallet/balance')}>
- <Link href="/studio/wallet/balance">
- <Coins />
- <span>잔액</span>
- </Link>
- </SidebarMenuSubButton>
- </SidebarMenuSubItem>
- <SidebarMenuSubItem>
- <SidebarMenuSubButton asChild isActive={isActive('/studio/wallet/revenue')}>
- <Link href="/studio/wallet/revenue">
- <ChartLine />
- <span>수익</span>
- </Link>
- </SidebarMenuSubButton>
- </SidebarMenuSubItem>
- <SidebarMenuSubItem>
- <SidebarMenuSubButton asChild isActive={isActive('/studio/wallet/withdraw')}>
- <Link href="/studio/wallet/withdraw">
- <LogOut />
- <span>출금</span>
- </Link>
- </SidebarMenuSubButton>
- </SidebarMenuSubItem>
- </SidebarMenuSub>
- </CollapsibleContent>
- </SidebarMenuItem>
- </Collapsible>
- {/* 정산 */}
- <Collapsible open={settlementOpen} onOpenChange={handleSettlementToggle}>
- <SidebarMenuItem>
- <CollapsibleTrigger asChild>
- <SidebarMenuButton isActive={isSettlementPath} tooltip="정산">
- <Calculator />
- <span>정산</span>
- <ChevronDown
- className={`ml-auto size-4 transition-transform duration-200${settlementOpen ? ' rotate-180' : ''}`}
- />
- </SidebarMenuButton>
- </CollapsibleTrigger>
- <CollapsibleContent>
- <SidebarMenuSub>
- <SidebarMenuSubItem>
- <SidebarMenuSubButton asChild isActive={isActive('/studio/settlement/account')}>
- <Link href="/studio/settlement/account">
- <Landmark />
- <span>계좌 관리</span>
- </Link>
- </SidebarMenuSubButton>
- </SidebarMenuSubItem>
- <SidebarMenuSubItem>
- <SidebarMenuSubButton asChild isActive={isActive('/studio/settlement/tax')}>
- <Link href="/studio/settlement/tax">
- <FileText />
- <span>원천징수 내역</span>
- </Link>
- </SidebarMenuSubButton>
- </SidebarMenuSubItem>
- </SidebarMenuSub>
- </CollapsibleContent>
- </SidebarMenuItem>
- </Collapsible>
- </SidebarMenu>
- </SidebarGroupContent>
- </SidebarGroup>
- </SidebarContent>
- </SidebarRoot>
- );
- }
|