refactor(packs): TIER_LABEL SSOT + Phase 2 migration note

코드 리뷰 후속 (I1+M2):
- TIER_LABEL: PackTier → 한국어 표시명 single source of truth
- PACK_ASSETS[*].name 백틱 템플릿으로 TIER_LABEL 참조
- extractPackTier if-ladder → LABEL_TO_TIER lookup (자동 derive)
- 마케팅 카피(입문/프로/마스터) 변경 시 한 곳만 수정으로 mypage·music 동기화

추가 코멘트 (M1, I3):
- U+00B7 middle-dot 명시
- Phase 2 PackFile 형태 마이그레이션 가이드 (files: string[] → { label, url? }[])

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-28 03:44:10 +09:00
parent a8fea0368e
commit 2e780f2dcd

View File

@@ -5,9 +5,23 @@ export interface PackAsset {
export type PackTier = 'starter' | 'pro' | 'master'; export type PackTier = 'starter' | 'pro' | 'master';
/**
* Tier 키 → 한국어 표시명 SSOT.
* `app/services/music/page.tsx`의 TIERS와 일치 유지 필요 (현재 입문/프로/마스터).
*/
export const TIER_LABEL: Record<PackTier, string> = {
starter: '입문',
pro: '프로',
master: '마스터',
};
const LABEL_TO_TIER: Record<string, PackTier> = Object.fromEntries(
Object.entries(TIER_LABEL).map(([tier, label]) => [label, tier as PackTier])
);
export const PACK_ASSETS: Record<PackTier, PackAsset> = { export const PACK_ASSETS: Record<PackTier, PackAsset> = {
starter: { starter: {
name: 'AI 음악 마스터 팩 (입문)', name: `AI 음악 마스터 팩 (${TIER_LABEL.starter})`,
files: [ files: [
'Suno 프롬프트 북 PDF (40p)', 'Suno 프롬프트 북 PDF (40p)',
'구조 템플릿 PDF', '구조 템플릿 PDF',
@@ -15,7 +29,7 @@ export const PACK_ASSETS: Record<PackTier, PackAsset> = {
], ],
}, },
pro: { pro: {
name: 'AI 음악 마스터 팩 (프로)', name: `AI 음악 마스터 팩 (${TIER_LABEL.pro})`,
files: [ files: [
'입문 자료 전체', '입문 자료 전체',
'MV 워크플로우 가이드 (Runway · Luma · Pika)', 'MV 워크플로우 가이드 (Runway · Luma · Pika)',
@@ -25,7 +39,7 @@ export const PACK_ASSETS: Record<PackTier, PackAsset> = {
], ],
}, },
master: { master: {
name: 'AI 음악 마스터 팩 (마스터)', name: `AI 음악 마스터 팩 (${TIER_LABEL.master})`,
files: [ files: [
'프로 자료 전체', '프로 자료 전체',
'샘플 프로젝트 장르별 3종', '샘플 프로젝트 장르별 3종',
@@ -38,16 +52,22 @@ export const PACK_ASSETS: Record<PackTier, PackAsset> = {
/** /**
* orders.service ("구매 신청: AI 음악 마스터 팩 · 프로") → tier key. * orders.service ("구매 신청: AI 음악 마스터 팩 · 프로") → tier key.
* 매칭 안 되면 null 반환 (Music 팩 외 의뢰). * 매칭 안 되면 null 반환 (Music 팩 외 의뢰).
*
* NOTE: service 문자열은 U+00B7 MIDDLE DOT (·) 사용. 이 함수는 "마지막 ·" 뒤의
* 단어를 tier 라벨로 인식. 예: "구매 신청: AI 음악 마스터 팩 · 프로" → "프로" → 'pro'.
* Phase 2에서 marketing 카피가 tier 뒤에 추가 ·를 두면 이 로직 재검토 필요.
*/ */
export function extractPackTier(service: string): PackTier | null { export function extractPackTier(service: string): PackTier | null {
if (!service.startsWith('구매 신청:')) return null; if (!service.startsWith('구매 신청:')) return null;
// service 예시: "구매 신청: AI 음악 마스터 팩 · 프로"
// 마지막 "·" 뒤가 tier 이름
const dotIdx = service.lastIndexOf('·'); const dotIdx = service.lastIndexOf('·');
if (dotIdx === -1) return null; if (dotIdx === -1) return null;
const tierName = service.slice(dotIdx + 1).trim(); const tierName = service.slice(dotIdx + 1).trim();
if (tierName === '입문') return 'starter'; return LABEL_TO_TIER[tierName] ?? null;
if (tierName === '프로') return 'pro';
if (tierName === '마스터') return 'master';
return null;
} }
/**
* Phase 2 migration note: `files: string[]` 는 placeholder. Phase 2에서 NAS
* 파일 URL 도입 시 `files: { label: string; url?: string; sizeBytes?: number }[]`
* 형태로 확장 필요. mypage page.tsx 의 `<span>{file}</span>` → `<span>{file.label}</span>`
* 동시 변경.
*/