feat(paywall): 의도 기반 Paywall Sheet(더미) 트리거로 정리
맥락: - Focus 경험을 해치지 않으려면 결제 시트는 잠금 항목을 클릭한 의도 순간에만 열려야 합니다. 변경사항: - Plan Pill(NORMAL) 클릭 시 Paywall을 바로 열지 않고 상태 안내만 노출하도록 조정했습니다. - 잠금 카드 클릭에서만 Paywall이 열리도록 트리거를 단일화했습니다. - Paywall Sheet를 3개 가치 포인트 + 2개 CTA 중심의 간결한 구조로 리디자인했습니다. 검증: - npx tsc --noEmit 세션-상태: Paywall 노출이 잠금 클릭 의도 기반으로만 동작함 세션-다음: (선택) Profiles 저장/불러오기 더미 슬롯을 Packs 영역에 추가 가능 세션-리스크: NORMAL 사용자가 Plan Pill에서 업그레이드 진입을 기대할 수 있어 UX 카피 미세 조정 여지
This commit is contained in:
@@ -1,70 +1,32 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { cn } from '@/shared/lib/cn';
|
|
||||||
|
|
||||||
interface PaywallSheetContentProps {
|
interface PaywallSheetContentProps {
|
||||||
onStartPro: () => void;
|
onStartPro: () => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type BillingCycle = 'monthly' | 'yearly';
|
|
||||||
|
|
||||||
const BILLING_OPTIONS: Array<{ id: BillingCycle; label: string; caption: string }> = [
|
|
||||||
{ id: 'monthly', label: '월간', caption: '월 9,900원 (더미)' },
|
|
||||||
{ id: 'yearly', label: '연간', caption: '연 79,000원 (더미)' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const VALUE_POINTS = [
|
const VALUE_POINTS = [
|
||||||
'더 많은 공간 / 고화질 배경',
|
'프리미엄 Scene Packs',
|
||||||
'작업용 BGM / 사운드 확장',
|
'확장 Sound Packs',
|
||||||
'프리셋 팩 / 고급 타이머',
|
'프로필 저장 / 불러오기',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PaywallSheetContent = ({ onStartPro, onClose }: PaywallSheetContentProps) => {
|
export const PaywallSheetContent = ({ onStartPro, onClose }: PaywallSheetContentProps) => {
|
||||||
const [cycle, setCycle] = useState<BillingCycle>('monthly');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<header className="space-y-1">
|
<header className="space-y-1">
|
||||||
<h3 className="text-lg font-semibold tracking-tight text-white">PRO로 더 깊게</h3>
|
<h3 className="text-lg font-semibold tracking-tight text-white">PRO에서 더 많은 공간과 사운드를 열어둘 수 있어요.</h3>
|
||||||
<p className="text-xs text-white/62">필요할 때만 잠금 해제하고, 무대는 그대로 유지해요.</p>
|
<p className="text-xs text-white/62">잠금 항목을 누른 순간에만 열리는 더미 결제 시트입니다.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{VALUE_POINTS.map((point) => (
|
{VALUE_POINTS.map((point) => (
|
||||||
<li key={point} className="rounded-xl border border-white/14 bg-white/[0.04] px-3 py-2 text-sm text-white/86">
|
<li key={point} className="rounded-xl border border-white/14 bg-white/[0.04] px-3 py-2 text-sm text-white/82">
|
||||||
{point}
|
{point}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<section className="rounded-2xl border border-white/14 bg-white/[0.04] p-3">
|
|
||||||
<p className="text-xs text-white/60">가격</p>
|
|
||||||
<div className="mt-2 grid grid-cols-2 gap-2">
|
|
||||||
{BILLING_OPTIONS.map((option) => {
|
|
||||||
const selected = option.id === cycle;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={option.id}
|
|
||||||
type="button"
|
|
||||||
onClick={() => setCycle(option.id)}
|
|
||||||
className={cn(
|
|
||||||
'rounded-xl border px-2.5 py-2 text-left text-xs transition-colors',
|
|
||||||
selected
|
|
||||||
? 'border-sky-200/40 bg-sky-200/14 text-white'
|
|
||||||
: 'border-white/16 bg-white/[0.03] text-white/72 hover:bg-white/[0.08]',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<p className="font-medium">{option.label}</p>
|
|
||||||
<p className="mt-0.5 text-[10px] text-white/56">{option.caption}</p>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-end gap-2">
|
<div className="flex items-center justify-end gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ export const SpaceToolsDockWidget = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
openUtilityPanel('paywall');
|
onStatusMessage({ message: 'NORMAL 플랜 사용 중 · 잠금 항목에서만 업그레이드할 수 있어요.' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLockedClick = (source: string) => {
|
const handleLockedClick = (source: string) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user