| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- 'use client';
- import { useEffect, useState } from 'react';
- import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
- import { faCopy, faArrowUpRightFromSquare, faWallet, faCalendarDay, faCalendar, faClock } from '@fortawesome/free-solid-svg-icons';
- import { fetchApi } from '@/lib/utils/client';
- import type { DashboardResponse, DashboardWidgetUrls } from '@/types/response/studio/dashboard';
- import './style.scss';
- const WIDGET_LABELS: { key: keyof Omit<DashboardWidgetUrls, 'widgetToken'>; label: string; description: string }[] = [
- { key: 'alert', label: '후원 알림', description: 'OBS에 후원 알림 표시' },
- { key: 'goal', label: '후원 목표', description: '목표 금액 진행률 표시' },
- { key: 'rank', label: '후원 순위', description: '후원자 순위 표시' },
- { key: 'crew', label: '크루 리더보드', description: '크루 순위 표시' },
- { key: 'remote', label: '리모콘', description: '후원 알림 제어' },
- ];
- export default function DashboardPage() {
- const [data, setData] = useState<DashboardResponse|null>(null);
- const [loading, setLoading] = useState(true);
- const [copiedKey, setCopiedKey] = useState<string|null>(null);
- useEffect(() => {
- loadDashboard();
- }, []);
- const loadDashboard = async () => {
- try {
- const res = await fetchApi<DashboardResponse>('/api/studio/dashboard', { silent: true });
- if (res.data) {
- setData(res.data);
- }
- } catch {} finally {
- setLoading(false);
- }
- };
- const getWidgetFullUrl = (path: string) => {
- if (typeof window === 'undefined') {
- return path;
- }
- return `${window.location.origin}${path}`;
- };
- const handleCopy = async (key: string, path: string) => {
- try {
- await navigator.clipboard.writeText(getWidgetFullUrl(path));
- setCopiedKey(key);
- setTimeout(() => setCopiedKey(null), 2000);
- } catch {}
- };
- const handleOpen = (path: string) => {
- window.open(getWidgetFullUrl(path), '_blank');
- };
- if (loading) {
- return (
- <div className="studio-page">
- <div className="dashboard">
- <h1 className="studio-page__title">대시보드</h1>
- <p className="studio-page__empty">준비 중...</p>
- </div>
- </div>
- );
- }
- const financial = data?.financial;
- const widgets = data?.widgets;
- const recentDonations = data?.recentDonations ?? [];
- return (
- <div className="studio-page">
- <div className="dashboard">
- <h1 className="studio-page__title">대시보드</h1>
- {/* 재무 요약 카드 */}
- <div className="dashboard__cards">
- <div className="dashboard__card">
- <span className="dashboard__card-icon">
- <FontAwesomeIcon icon={faWallet} />
- </span>
- <span className="dashboard__card-label">출금 가능 잔액</span>
- <span className="dashboard__card-value dashboard__card-value--primary">
- {(financial?.availableBalance ?? 0).toLocaleString()}원
- </span>
- </div>
- <div className="dashboard__card">
- <span className="dashboard__card-icon">
- <FontAwesomeIcon icon={faCalendarDay} />
- </span>
- <span className="dashboard__card-label">오늘 후원</span>
- <span className="dashboard__card-value">
- {(financial?.todayDonations ?? 0).toLocaleString()}원
- </span>
- </div>
- <div className="dashboard__card">
- <span className="dashboard__card-icon">
- <FontAwesomeIcon icon={faCalendar} />
- </span>
- <span className="dashboard__card-label">이번 달 후원</span>
- <span className="dashboard__card-value">
- {(financial?.monthDonations ?? 0).toLocaleString()}원
- </span>
- </div>
- <div className="dashboard__card">
- <span className="dashboard__card-icon">
- <FontAwesomeIcon icon={faClock} />
- </span>
- <span className="dashboard__card-label">출금 대기</span>
- <span className="dashboard__card-value dashboard__card-value--danger">
- {(financial?.pendingWithdrawal ?? 0).toLocaleString()}원
- </span>
- </div>
- </div>
- {/* 위젯 URL */}
- {widgets && (
- <section className="dashboard__section">
- <h2 className="dashboard__section-title">위젯 URL</h2>
- <p className="dashboard__section-desc">OBS 브라우저 소스에 아래 URL을 등록하세요.</p>
- <div className="dashboard__widget-list">
- {WIDGET_LABELS.map(({ key, label, description }) => {
- const path = widgets[key];
- const isCopied = copiedKey === key;
- return (
- <div key={key} className="dashboard__widget-item">
- <div className="dashboard__widget-info">
- <span className="dashboard__widget-label">{label}</span>
- <span className="dashboard__widget-desc">{description}</span>
- </div>
- <div className="dashboard__widget-url">
- <code className="dashboard__widget-url-text">{getWidgetFullUrl(path)}</code>
- </div>
- <div className="dashboard__widget-actions">
- <button
- type="button"
- className={`dashboard__widget-btn${isCopied ? ' dashboard__widget-btn--copied' : ''}`}
- onClick={() => handleCopy(key, path)}
- title="URL 복사"
- >
- <FontAwesomeIcon icon={faCopy} />
- {isCopied ? '복사됨' : '복사'}
- </button>
- <button
- type="button"
- className="dashboard__widget-btn dashboard__widget-btn--open"
- onClick={() => handleOpen(path)}
- title="새 창에서 열기"
- >
- <FontAwesomeIcon icon={faArrowUpRightFromSquare} />
- 열기
- </button>
- </div>
- </div>
- );
- })}
- </div>
- </section>
- )}
- {/* 최근 후원 */}
- <section className="dashboard__section">
- <h2 className="dashboard__section-title">최근 후원</h2>
- {recentDonations.length === 0 ? (
- <div className="dashboard__empty">아직 후원 내역이 없습니다.</div>
- ) : (
- <div className="dashboard__donation-list">
- {recentDonations.map(d => (
- <div key={d.id} className="dashboard__donation-item">
- <div className="dashboard__donation-info">
- <span className="dashboard__donation-name">{d.sendName}</span>
- <span className="dashboard__donation-amount">{d.amount.toLocaleString()}원</span>
- </div>
- {d.message && (
- <div className="dashboard__donation-msg">{d.message}</div>
- )}
- <div className="dashboard__donation-time">
- {new Date(d.createdAt).toLocaleString('ko-KR')}
- </div>
- </div>
- ))}
- </div>
- )}
- </section>
- </div>
- </div>
- );
- }
|