'use client'; import { useState } from 'react'; import Link from 'next/link'; const HIDDEN_PAGES = [ { path: '/freelance', label: '외주 개발 문의', desc: '위시캣·숨고 지원서에 뿌린 기존 링크. 노출 제거 + noindex.', }, { path: '/services/website', label: '홈페이지 제작 상세', desc: '홈페이지 제작 랜딩. 직링크 전용.', }, ]; interface IssuedToken { token: string; url: string; memo: string; expiresAt: string; } export default function AdminHiddenPage() { const [memo, setMemo] = useState(''); const [ttlDays, setTtlDays] = useState(30); const [loading, setLoading] = useState(false); const [issued, setIssued] = useState([]); const [error, setError] = useState(''); async function handleIssue() { setError(''); if (!memo.trim()) { setError('메모를 입력해주세요. (예: 위시캣 xx 프로젝트)'); return; } setLoading(true); try { const res = await fetch('/api/admin/portfolio-token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ memo, ttlDays }), }); if (!res.ok) throw new Error((await res.json()).error || '실패'); const data = await res.json(); const url = `${window.location.origin}/portfolio/${data.token}`; setIssued([{ token: data.token, url, memo: data.memo, expiresAt: data.expiresAt }, ...issued]); setMemo(''); } catch (e) { setError(e instanceof Error ? e.message : '토큰 생성 실패'); } finally { setLoading(false); } } async function copy(text: string) { try { await navigator.clipboard.writeText(text); alert('복사되었습니다'); } catch { alert('복사 실패 — 수동으로 복사해주세요'); } } return (

숨김 페이지 관리

공개 UI에서 숨긴 페이지 바로가기 + 위시캣 제출용 임시 공유 URL 발급.

{/* 숨김 페이지 바로가기 */}

🔗 숨김 페이지 바로가기

{HIDDEN_PAGES.map((p) => (
{p.label} {p.path}

{p.desc}

))}
{/* 포트폴리오 토큰 발급 */}

🎫 포트폴리오 임시 공유 URL 발급

위시캣 지원서 등 외부 제출용. /portfolio/[token] 형태의 프리미엄 게이트웨이 페이지가 생성됩니다. 만료되면 404로 처리됩니다.

setMemo(e.target.value)} placeholder="예: 위시캣 OO 프로젝트 제안서용" className="w-full px-4 py-2.5 border border-slate-300 rounded-lg text-sm focus:outline-none focus:border-violet-500" />
setTtlDays(Number(e.target.value))} className="w-32 px-4 py-2.5 border border-slate-300 rounded-lg text-sm focus:outline-none focus:border-violet-500" />
{error &&

{error}

}
{/* 최근 발급 목록 (세션 메모리만 — 새로고침 시 초기화) */} {issued.length > 0 && (

이번 세션에 발급한 URL

{issued.map((t) => (

📝 {t.memo} · 만료 {new Date(t.expiresAt).toLocaleDateString('ko-KR')}

{t.url}
열기 ↗
))}

※ 발급 목록은 현재 세션에만 표시됩니다. 발급 후 반드시 URL을 복사해두세요.

)}
); }