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:
@@ -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>`
|
||||||
|
* 동시 변경.
|
||||||
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user