refactor: AI 음악 메인 개편 — 로또/프롬프트/자동화 삭제, 음악/블로그 팩 신규
- 삭제: services/{lotto,prompt,automation,ai-kit,stock,tools} + api/{lotto,tools}
- 노출 제거: /freelance, /services/website (noindex + robots/sitemap 제외, 외부 지원서 링크 유지)
- 신규: /services/music (3-tier 39k/99k/149k, 4단계 프로세스)
- 신규: /services/blog (블로그 자동화 팩 29k 1회성)
- 신규: PurchaseAgreementModal (전자상거래법 17조 동의 + 계좌이체)
- 개편: 홈 대시보드 (음악 Hero + 사주/블로그팩/일반문의 서브카드)
- 사이드바 재구성, sitemap/robots/JSON-LD 갱신
- 환불정책 신규 상품 반영 + 법적 근거 명시
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,15 +4,15 @@ import { useState } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Sidebar from './Sidebar';
|
||||
|
||||
const AUTH_PATHS = ['/login', '/signup', '/admin'];
|
||||
const STANDALONE_PATHS = ['/login', '/signup', '/admin'];
|
||||
|
||||
export default function DashboardShell({ children }: { children: React.ReactNode }) {
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const pathname = usePathname();
|
||||
|
||||
const isAuthPage = AUTH_PATHS.some((p) => pathname.startsWith(p));
|
||||
const isStandalone = STANDALONE_PATHS.some((p) => pathname.startsWith(p));
|
||||
|
||||
if (isAuthPage) {
|
||||
if (isStandalone) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
|
||||
185
app/components/PurchaseAgreementModal.tsx
Normal file
185
app/components/PurchaseAgreementModal.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
productName: string;
|
||||
price: string;
|
||||
bankInfo?: {
|
||||
bank: string;
|
||||
account: string;
|
||||
holder: string;
|
||||
};
|
||||
}
|
||||
|
||||
const DEFAULT_BANK = {
|
||||
bank: '토스뱅크',
|
||||
account: '1000-0000-0000',
|
||||
holder: '박재오',
|
||||
};
|
||||
|
||||
export default function PurchaseAgreementModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
productName,
|
||||
price,
|
||||
bankInfo = DEFAULT_BANK,
|
||||
}: Props) {
|
||||
const [agreed, setAgreed] = useState(false);
|
||||
const [email, setEmail] = useState('');
|
||||
const [sent, setSent] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setAgreed(false);
|
||||
setEmail('');
|
||||
setSent(false);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!agreed || !email) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
await fetch('/api/contact', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
service: `구매 신청: ${productName}`,
|
||||
name: email.split('@')[0],
|
||||
email,
|
||||
phone: '',
|
||||
message: `상품: ${productName} (${price})\n입금 대기 중. 입금 확인 후 이메일로 상품 전달 예정.`,
|
||||
}),
|
||||
});
|
||||
setSent(true);
|
||||
} catch (e) {
|
||||
alert('신청 전송 실패. 다시 시도해주세요.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className="bg-white rounded-2xl w-full max-w-lg max-h-[90vh] overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="bg-gradient-to-br from-slate-900 to-slate-800 px-6 py-5 text-white">
|
||||
<h3 className="font-extrabold text-lg">{productName}</h3>
|
||||
<p className="text-slate-300 text-sm mt-0.5">{price}</p>
|
||||
</div>
|
||||
|
||||
{sent ? (
|
||||
<div className="p-8 text-center">
|
||||
<div className="text-5xl mb-4">✅</div>
|
||||
<h4 className="text-lg font-extrabold text-slate-900 mb-2">신청 완료</h4>
|
||||
<p className="text-sm text-slate-600 leading-relaxed">
|
||||
아래 계좌로 입금해주시면 <strong>24시간 이내</strong> 이메일로 상품을 전달드립니다.
|
||||
</p>
|
||||
<div className="mt-5 bg-slate-50 border border-slate-200 rounded-xl p-4 text-left">
|
||||
<p className="text-xs text-slate-500 mb-1">입금 계좌</p>
|
||||
<p className="font-mono text-sm text-slate-900">
|
||||
{bankInfo.bank} {bankInfo.account}
|
||||
</p>
|
||||
<p className="text-xs text-slate-600 mt-1">예금주 {bankInfo.holder}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="mt-6 w-full bg-slate-900 text-white py-3 rounded-xl font-bold text-sm hover:bg-slate-800 transition"
|
||||
>
|
||||
닫기
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-6 space-y-5">
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-slate-700 mb-2">
|
||||
이메일 (상품 전달용)
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="your@email.com"
|
||||
className="w-full px-4 py-3 border border-slate-300 rounded-xl text-sm focus:outline-none focus:border-violet-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4 text-xs text-slate-700 leading-relaxed">
|
||||
<p className="font-bold text-amber-900 mb-2">📌 구매 전 확인사항</p>
|
||||
<ul className="space-y-1.5 list-disc pl-4">
|
||||
<li>
|
||||
본 상품은 <strong>디지털 콘텐츠</strong>로, 제공 시작(이메일 전달) 후에는
|
||||
전자상거래법 제17조 제2항 제5호에 따라 청약철회(환불)가 <strong>제한</strong>됩니다.
|
||||
</li>
|
||||
<li>
|
||||
구매 전 랜딩 페이지의 <strong>샘플 미리보기·무료 체험 구간</strong>을 반드시 확인해주세요.
|
||||
</li>
|
||||
<li>
|
||||
파일 손상·전달 누락 등 회사 귀책 사유 시 <strong>즉시 재전달 또는 전액 환불</strong>됩니다.
|
||||
</li>
|
||||
<li>
|
||||
자세한 내용은{' '}
|
||||
<Link href="/legal/refund" className="underline text-amber-900 font-bold" target="_blank">
|
||||
환불 정책
|
||||
</Link>{' '}
|
||||
참조.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<label className="flex items-start gap-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={agreed}
|
||||
onChange={(e) => setAgreed(e.target.checked)}
|
||||
className="mt-0.5 w-4 h-4 accent-violet-600"
|
||||
/>
|
||||
<span className="text-sm text-slate-700 leading-relaxed">
|
||||
위 환불 제한 사항을 확인했으며, 이에{' '}
|
||||
<strong className="text-slate-900">동의</strong>합니다. (필수)
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div className="bg-slate-50 border border-slate-200 rounded-xl p-4 text-xs">
|
||||
<p className="font-bold text-slate-900 mb-1">💳 결제 방법: 계좌이체</p>
|
||||
<p className="font-mono text-slate-700">
|
||||
{bankInfo.bank} {bankInfo.account} ({bankInfo.holder})
|
||||
</p>
|
||||
<p className="text-slate-500 mt-2">
|
||||
신청 후 위 계좌로 입금하시면 24시간 이내 이메일 전달.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="flex-1 py-3 border border-slate-300 rounded-xl text-sm font-bold text-slate-700 hover:bg-slate-50"
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={!agreed || !email || loading}
|
||||
className="flex-[2] py-3 bg-violet-600 hover:bg-violet-500 disabled:bg-slate-300 disabled:cursor-not-allowed text-white rounded-xl text-sm font-bold transition"
|
||||
>
|
||||
{loading ? '전송 중...' : '구매 신청'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -24,32 +24,21 @@ const navGroups: NavGroup[] = [
|
||||
title: 'AI 상품',
|
||||
items: [
|
||||
{
|
||||
href: '/services/prompt',
|
||||
label: '프롬프트 스토어',
|
||||
badge: 'HOT',
|
||||
href: '/services/music',
|
||||
label: 'AI 음악 마스터',
|
||||
badge: 'NEW',
|
||||
icon: (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 19V6l12-3v13M9 19a3 3 0 11-6 0 3 3 0 016 0zm12-3a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
href: '/services/automation',
|
||||
label: '업무 자동화',
|
||||
href: '/services/blog',
|
||||
label: '블로그 자동화 팩',
|
||||
icon: (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
href: '/services/ai-kit',
|
||||
label: 'AI 자동화 키트',
|
||||
badge: '구독',
|
||||
icon: (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
@@ -68,39 +57,6 @@ const navGroups: NavGroup[] = [
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
href: '/tools',
|
||||
label: '도구 쇼케이스',
|
||||
badge: 'DEMO',
|
||||
icon: (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '외주 의뢰',
|
||||
items: [
|
||||
{
|
||||
href: '/freelance',
|
||||
label: '외주 개발 문의',
|
||||
icon: (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
href: '/services/website',
|
||||
label: '홈페이지 제작',
|
||||
icon: (
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user