121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
// ─── 스튜디오 위저드 상태 관리 훅 ───────────────────────────────────────────
|
|
// DEMO/src/components/studio/StudioWizard.tsx 의 상태 로직에서 이전
|
|
// TODO: Phase 2 — Zustand store 로 전환
|
|
|
|
import { useState } from "react";
|
|
import type { StudioState, StudioChannel, ContentFormat, ContentPillarId, AssetSourceType, SoundSettings, GenerateOutputType } from "@/features/studio/types/studio";
|
|
import { getStepsForChannel } from "@/features/studio/config/studioSteps";
|
|
|
|
const DEFAULT_SOUND: SoundSettings = {
|
|
genre: 'calm',
|
|
trackId: null,
|
|
narrationEnabled: false,
|
|
narrationLanguage: 'ko',
|
|
narrationVoice: 'female',
|
|
subtitleEnabled: true,
|
|
};
|
|
|
|
const INITIAL_STATE: StudioState = {
|
|
channel: null,
|
|
format: null,
|
|
pillarId: null,
|
|
assetSources: [],
|
|
sound: DEFAULT_SOUND,
|
|
outputType: 'video',
|
|
};
|
|
|
|
export function useStudioWizard() {
|
|
const [state, setState] = useState<StudioState>(INITIAL_STATE);
|
|
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
|
|
|
const steps = getStepsForChannel(state.channel);
|
|
const currentStep = steps[currentStepIndex];
|
|
const isFirstStep = currentStepIndex === 0;
|
|
const isLastStep = currentStepIndex === steps.length - 1;
|
|
|
|
// ── 스텝별 진행 가능 여부 ─────────────────────────────────────────────────
|
|
// TODO: 각 스텝 구현 후 세부 유효성 검사 추가
|
|
function canProceed(): boolean {
|
|
switch (currentStep.key) {
|
|
case 'channel-format': return state.channel !== null && state.format !== null;
|
|
case 'strategy-source': return state.pillarId !== null && state.assetSources.length > 0;
|
|
case 'sound': return true;
|
|
case 'generate': return true;
|
|
case 'blog-editor': return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
// ── 스텝 이동 ─────────────────────────────────────────────────────────────
|
|
function goNext() {
|
|
if (!isLastStep && canProceed()) setCurrentStepIndex((i) => i + 1);
|
|
}
|
|
|
|
function goPrev() {
|
|
if (!isFirstStep) setCurrentStepIndex((i) => i - 1);
|
|
}
|
|
|
|
function goToStep(index: number) {
|
|
if (index < currentStepIndex) setCurrentStepIndex(index);
|
|
}
|
|
|
|
// ── 상태 업데이트 핸들러 ──────────────────────────────────────────────────
|
|
function setChannel(channel: StudioChannel) {
|
|
// 채널 변경 시 format 초기화 + blog 채널은 outputType을 image로 고정
|
|
setState((prev) => ({
|
|
...prev,
|
|
channel,
|
|
format: null,
|
|
outputType: channel === 'naver_blog' ? 'image' : 'video',
|
|
}));
|
|
setCurrentStepIndex(0);
|
|
}
|
|
|
|
function setFormat(format: ContentFormat) {
|
|
setState((prev) => ({ ...prev, format }));
|
|
}
|
|
|
|
function setPillar(pillarId: ContentPillarId) {
|
|
setState((prev) => ({ ...prev, pillarId }));
|
|
}
|
|
|
|
function toggleAssetSource(source: AssetSourceType) {
|
|
setState((prev) => {
|
|
const exists = prev.assetSources.includes(source);
|
|
return {
|
|
...prev,
|
|
assetSources: exists
|
|
? prev.assetSources.filter((s) => s !== source)
|
|
: [...prev.assetSources, source],
|
|
};
|
|
});
|
|
}
|
|
|
|
function setSound(sound: Partial<SoundSettings>) {
|
|
setState((prev) => ({ ...prev, sound: { ...prev.sound, ...sound } }));
|
|
}
|
|
|
|
function setOutputType(outputType: GenerateOutputType) {
|
|
setState((prev) => ({ ...prev, outputType }));
|
|
}
|
|
|
|
return {
|
|
state,
|
|
steps,
|
|
currentStep,
|
|
currentStepIndex,
|
|
isFirstStep,
|
|
isLastStep,
|
|
canProceed,
|
|
goNext,
|
|
goPrev,
|
|
goToStep,
|
|
setChannel,
|
|
setFormat,
|
|
setPillar,
|
|
toggleAssetSource,
|
|
setSound,
|
|
setOutputType,
|
|
};
|
|
}
|