튜토리얼 버그 수정

feature-tutorial
김성경 2026-04-14 16:05:08 +09:00
parent 3210efbe30
commit 2c6c4b7c72
6 changed files with 31 additions and 13 deletions

View File

@ -238,9 +238,11 @@ const TutorialOverlay: React.FC<TutorialOverlayProps> = ({
{t('tutorial.prev')} {t('tutorial.prev')}
</button> </button>
)} )}
{(hint.clickToAdvance !== true || isLast) && (
<button className="tutorial-btn-next" onClick={onNext}> <button className="tutorial-btn-next" onClick={onNext}>
{isLast ? t('tutorial.finish') : t('tutorial.next')} {isLast ? t('tutorial.finish') : t('tutorial.next')}
</button> </button>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -155,6 +155,7 @@ export const tutorialSteps: TutorialStepDef[] = [
titleKey: 'tutorial.asset.ratio.title', titleKey: 'tutorial.asset.ratio.title',
descriptionKey: 'tutorial.asset.ratio.desc', descriptionKey: 'tutorial.asset.ratio.desc',
position: 'left', position: 'left',
clickToAdvance: false,
}, },
{ {
targetSelector: '.asset-next-button', targetSelector: '.asset-next-button',
@ -201,6 +202,13 @@ export const tutorialSteps: TutorialStepDef[] = [
position: 'left', position: 'left',
clickToAdvance: false, clickToAdvance: false,
}, },
{
targetSelector: '.status-message-new',
titleKey: 'tutorial.sound.lyricsWait.title',
descriptionKey: 'tutorial.sound.lyricsWait.desc',
position: 'right',
clickToAdvance: false,
},
], ],
}, },
{ {
@ -230,12 +238,14 @@ export const tutorialSteps: TutorialStepDef[] = [
titleKey: 'tutorial.myInfo.connect.title', titleKey: 'tutorial.myInfo.connect.title',
descriptionKey: 'tutorial.myInfo.connect.desc', descriptionKey: 'tutorial.myInfo.connect.desc',
position: 'top', position: 'top',
clickToAdvance: false,
}, },
{ {
targetSelector: '.myinfo-social-btn.connected', targetSelector: '.myinfo-social-buttons',
titleKey: 'tutorial.myInfo.button.title', titleKey: 'tutorial.myInfo.button.title',
descriptionKey: 'tutorial.myInfo.button.desc', descriptionKey: 'tutorial.myInfo.button.desc',
position: 'top', position: 'top',
clickToAdvance: true,
}, },
{ {
targetSelector: '.myinfo-connected-accounts', targetSelector: '.myinfo-connected-accounts',

View File

@ -104,6 +104,7 @@ export function useTutorial(): UseTutorialReturn {
// 마지막 힌트 완료 → seen 기록 + 진행 상태 삭제 // 마지막 힌트 완료 → seen 기록 + 진행 상태 삭제
setIsActive(false); setIsActive(false);
if (tutorialKey) markSeen(tutorialKey); if (tutorialKey) markSeen(tutorialKey);
globalSkip = null; // 완료된 튜토리얼은 globalSkip 해제
onCompleteRef.current?.(); onCompleteRef.current?.();
onCompleteRef.current = undefined; onCompleteRef.current = undefined;
return 0; return 0;
@ -127,7 +128,7 @@ export function useTutorial(): UseTutorialReturn {
setIsRestartPopupVisible(true); setIsRestartPopupVisible(true);
}, []); }, []);
// 팝업에서 확인 → seen 초기화 후 튜토리얼 시작 // 팝업에서 확인 → seen/progress 초기화 후 튜토리얼 시작
const confirmRestart = useCallback(() => { const confirmRestart = useCallback(() => {
setIsRestartPopupVisible(false); setIsRestartPopupVisible(false);
if (pendingRestartKey) { if (pendingRestartKey) {

View File

@ -46,14 +46,15 @@
"language": { "title": "Select Language", "desc": "You can choose the language for the sound.\nWant to continue with Korean?" }, "language": { "title": "Select Language", "desc": "You can choose the language for the sound.\nWant to continue with Korean?" },
"generate": { "title": "Generate Sound", "desc": "AI will generate music in your chosen genre and language." }, "generate": { "title": "Generate Sound", "desc": "AI will generate music in your chosen genre and language." },
"lyrics": { "title": "Lyrics Complete", "desc": "AI wrote lyrics in your selected language.\nCheck the generated lyrics." }, "lyrics": { "title": "Lyrics Complete", "desc": "AI wrote lyrics in your selected language.\nCheck the generated lyrics." },
"lyricsWait": { "title": "Generating Music", "desc": "AI is composing music based on the lyrics.\nPlease wait a moment." },
"audioPlayer": { "title": "Preview the Music", "desc": "Music generation is complete.\nPress play to listen to the generated music." }, "audioPlayer": { "title": "Preview the Music", "desc": "Music generation is complete.\nPress play to listen to the generated music." },
"video": { "title": "Generate Video", "desc": "Click the button to start generating your video." } "video": { "title": "Generate Video", "desc": "Click the button to start generating your video." }
}, },
"myInfo": { "myInfo": {
"connect": { "title": "Connect Social Account", "desc": "You need to link a social account to upload videos." }, "connect": { "title": "Connect Social Account", "desc": "You need to link a social account to upload videos." },
"button": { "title": "Connect Now", "desc": "Click the social media button you want to connect." }, "button": { "title": "Connect Now", "desc": "Click the YouTube connect button.\n(Instagram connection is under development.)" },
"connected": { "title": "Connected Accounts", "desc": "Your linked social accounts appear here. Check after connecting." }, "connected": { "title": "Connected Accounts", "desc": "Your linked social accounts appear here.\nCheck after connecting." },
"ado2": { "title": "Check My Contents", "desc": "After connecting, go to My Contents in the sidebar to view and upload your videos. Click to navigate." } "ado2": { "title": "ADO2 Contents", "desc": "You can now upload the generated video.\nClick to navigate." }
}, },
"ado2": { "ado2": {
"list": { "title": "Generated Videos", "desc": "View all AI-created videos here." }, "list": { "title": "Generated Videos", "desc": "View all AI-created videos here." },
@ -61,12 +62,12 @@
}, },
"completion": { "completion": {
"contentInfo": { "title": "Content Info", "desc": "Check the title, genre, resolution, and lyrics of the generated content." }, "contentInfo": { "title": "Content Info", "desc": "Check the title, genre, resolution, and lyrics of the generated content." },
"generating": { "title": "Generating Video", "desc": "AI is creating your video. Please wait a moment." }, "generating": { "title": "Generating Video", "desc": "AI is creating your video.\nPlease wait a moment." },
"completion": { "title": "Video Complete!", "desc": "Your video is ready. Want to take a look?" }, "completion": { "title": "Video Complete!", "desc": "Your video is ready. Want to take a look?" },
"myInfo": { "title": "Connect Social Account", "desc": "To upload your video to YouTube, connect your social account in My Info. Click to go there." } "myInfo": { "title": "Connect Social Account", "desc": "To upload your video to YouTube, connect your social account in My Info. Click to go there." }
}, },
"upload": { "upload": {
"seo": { "title": "Title & Description", "desc": "AI is generating the title and description for your video.\nPlease wait a moment." }, "seo": { "title": "Title & Description", "desc": "AI is generating the title and description for your video. \nPlease wait a moment." },
"required": { "title": "Required Fields", "desc": "Fields marked with * are required. Please fill them in before uploading." }, "required": { "title": "Required Fields", "desc": "Fields marked with * are required. Please fill them in before uploading." },
"schedule": { "title": "Schedule Upload", "desc": "Post now or schedule for a specific time." }, "schedule": { "title": "Schedule Upload", "desc": "Post now or schedule for a specific time." },
"submit": { "title": "Start Upload", "desc": "Click the Post button to start uploading." } "submit": { "title": "Start Upload", "desc": "Click the Post button to start uploading." }

View File

@ -46,14 +46,15 @@
"language": { "title": "언어 선택", "desc": "사운드의 언어를 선택할 수 있어요. \n이미 선택된 한국어로 진행해볼까요?" }, "language": { "title": "언어 선택", "desc": "사운드의 언어를 선택할 수 있어요. \n이미 선택된 한국어로 진행해볼까요?" },
"generate": { "title": "사운드 생성", "desc": "AI가 선택한 장르와 언어로 음악을 생성해요." }, "generate": { "title": "사운드 생성", "desc": "AI가 선택한 장르와 언어로 음악을 생성해요." },
"lyrics": { "title": "가사 생성 완료", "desc": "AI가 선택한 언어로 가사를 만들었어요.\n생성된 가사를 확인하세요." }, "lyrics": { "title": "가사 생성 완료", "desc": "AI가 선택한 언어로 가사를 만들었어요.\n생성된 가사를 확인하세요." },
"lyricsWait": { "title": "음악 생성 중", "desc": "가사를 바탕으로 AI가 음악을 만들고 있어요.\n잠시만 기다려 주세요." },
"audioPlayer": { "title": "음악 미리 듣기", "desc": "음악 생성이 완료되었어요.\n재생 버튼을 눌러 생성된 음악을 미리 들어보세요." }, "audioPlayer": { "title": "음악 미리 듣기", "desc": "음악 생성이 완료되었어요.\n재생 버튼을 눌러 생성된 음악을 미리 들어보세요." },
"video": { "title": "영상 생성", "desc": "버튼을 클릭해서 영상 생성을 시작하세요." } "video": { "title": "영상 생성", "desc": "버튼을 클릭해서 영상 생성을 시작하세요." }
}, },
"myInfo": { "myInfo": {
"connect": { "title": "소셜 연결", "desc": "영상을 업로드하려면 소셜 계정을 연동해야 해요." }, "connect": { "title": "소셜 연결", "desc": "영상을 업로드하려면 소셜 계정을 연동해야 해요." },
"button": { "title": "연결하기", "desc": "원하는 소셜미디어 버튼을 클릭해서 연동하세요. \n(Instaram연결은 개발 중입니다.)" }, "button": { "title": "연결하기", "desc": "YouTube 연결 버튼을 클릭하세요. \n(Instaram연결은 개발 중입니다.)" },
"connected": { "title": "연결 계정", "desc": "연결된 소셜 계정 목록이에요. \n연결 후 여기서 확인할 수 있어요." }, "connected": { "title": "연결 계정", "desc": "연결된 소셜 계정 목록이에요. \n연결 후 여기서 확인할 수 있어요." },
"ado2": { "title": "ADO2 콘텐츠", "desc": "이제 생성된 영상을 업로드할 수 있어요. 클릭해서 이동하세요." } "ado2": { "title": "ADO2 콘텐츠", "desc": "이제 생성된 영상을 업로드할 수 있어요. \n클릭해서 이동하세요." }
}, },
"ado2": { "ado2": {
"list": { "title": "생성된 영상 목록", "desc": "ADO2에서 만든 영상들을 확인할 수 있어요." }, "list": { "title": "생성된 영상 목록", "desc": "ADO2에서 만든 영상들을 확인할 수 있어요." },
@ -61,7 +62,7 @@
}, },
"completion": { "completion": {
"contentInfo": { "title": "콘텐츠 정보", "desc": "생성된 콘텐츠의 제목, 장르, 규격, 가사 정보를 확인하세요." }, "contentInfo": { "title": "콘텐츠 정보", "desc": "생성된 콘텐츠의 제목, 장르, 규격, 가사 정보를 확인하세요." },
"generating": { "title": "영상 제작 중", "desc": "AI가 영상을 만들고 있어요. 잠시만 기다려 주세요." }, "generating": { "title": "영상 제작 중", "desc": "AI가 영상을 만들고 있어요. \n잠시만 기다려 주세요." },
"completion": { "title": "영상 완성!", "desc": "영상 제작이 완료되었어요. \n영상을 확인해 볼까요?" }, "completion": { "title": "영상 완성!", "desc": "영상 제작이 완료되었어요. \n영상을 확인해 볼까요?" },
"myInfo": { "title": "소셜 계정 연동", "desc": "영상을 유튜브에 업로드하려면 내 정보에서 소셜 계정을 연동해야 해요. 클릭해서 이동하세요." } "myInfo": { "title": "소셜 계정 연동", "desc": "영상을 유튜브에 업로드하려면 내 정보에서 소셜 계정을 연동해야 해요. 클릭해서 이동하세요." }
}, },

View File

@ -323,6 +323,7 @@ const GenerationFlow: React.FC<GenerationFlowProps> = ({
// wizardStep 변경 시 튜토리얼 트리거 (사이드바 메뉴 화면에서는 제외) // wizardStep 변경 시 튜토리얼 트리거 (사이드바 메뉴 화면에서는 제외)
const SIDEBAR_ITEMS = ['대시보드', 'ADO2 콘텐츠', '내 정보', '콘텐츠 캘린더']; const SIDEBAR_ITEMS = ['대시보드', 'ADO2 콘텐츠', '내 정보', '콘텐츠 캘린더'];
useEffect(() => { useEffect(() => {
if (tutorial.isActive) tutorial.skipTutorial();
if (SIDEBAR_ITEMS.includes(activeItem)) return; if (SIDEBAR_ITEMS.includes(activeItem)) return;
const timer = setTimeout(() => { const timer = setTimeout(() => {
if (wizardStep === 1 && !tutorial.hasSeen(TUTORIAL_KEYS.ASSET)) { if (wizardStep === 1 && !tutorial.hasSeen(TUTORIAL_KEYS.ASSET)) {
@ -336,6 +337,7 @@ const GenerationFlow: React.FC<GenerationFlowProps> = ({
// activeItem 변경 시 튜토리얼 트리거 // activeItem 변경 시 튜토리얼 트리거
useEffect(() => { useEffect(() => {
if (tutorial.isActive) tutorial.skipTutorial();
if (activeItem === '내 정보' && !tutorial.hasSeen(TUTORIAL_KEYS.MY_INFO)) { if (activeItem === '내 정보' && !tutorial.hasSeen(TUTORIAL_KEYS.MY_INFO)) {
tutorial.startTutorial(TUTORIAL_KEYS.MY_INFO); tutorial.startTutorial(TUTORIAL_KEYS.MY_INFO);
} else if (activeItem === 'ADO2 콘텐츠' && !tutorial.hasSeen(TUTORIAL_KEYS.ADO2_CONTENTS)) { } else if (activeItem === 'ADO2 콘텐츠' && !tutorial.hasSeen(TUTORIAL_KEYS.ADO2_CONTENTS)) {
@ -509,6 +511,7 @@ const GenerationFlow: React.FC<GenerationFlowProps> = ({
if (activeItem === '내 정보') return TUTORIAL_KEYS.MY_INFO; if (activeItem === '내 정보') return TUTORIAL_KEYS.MY_INFO;
if (activeItem === 'ADO2 콘텐츠') return TUTORIAL_KEYS.ADO2_CONTENTS; if (activeItem === 'ADO2 콘텐츠') return TUTORIAL_KEYS.ADO2_CONTENTS;
if (activeItem === '대시보드') return TUTORIAL_KEYS.DASHBOARD; if (activeItem === '대시보드') return TUTORIAL_KEYS.DASHBOARD;
if (activeItem === '콘텐츠 캘린더') return TUTORIAL_KEYS.CONTENT_CALENDAR;
if (activeItem === '새 프로젝트 만들기') { if (activeItem === '새 프로젝트 만들기') {
if (wizardStep === 1) return TUTORIAL_KEYS.ASSET; if (wizardStep === 1) return TUTORIAL_KEYS.ASSET;
if (wizardStep === 2) return TUTORIAL_KEYS.SOUND; if (wizardStep === 2) return TUTORIAL_KEYS.SOUND;