diff --git a/index.css b/index.css index e869518..33d423b 100644 --- a/index.css +++ b/index.css @@ -10620,7 +10620,21 @@ padding: 1.25rem 1.5rem; border-top: 1px solid rgba(255, 255, 255, 0.1); display: flex; - justify-content: center; + flex-direction: column; + align-items: center; + gap: 0.75rem; +} + +.upload-progress-calendar-link { + font-size: 0.8rem; + color: rgba(255, 255, 255, 0.45); + cursor: pointer; + text-decoration: underline; + text-underline-offset: 3px; +} + +.upload-progress-calendar-link:hover { + color: rgba(255, 255, 255, 0.75); } .upload-progress-btn { diff --git a/src/components/SocialPostingModal.tsx b/src/components/SocialPostingModal.tsx index 26c5f67..fad79d9 100644 --- a/src/components/SocialPostingModal.tsx +++ b/src/components/SocialPostingModal.tsx @@ -12,6 +12,7 @@ interface SocialPostingModalProps { isOpen: boolean; onClose: () => void; video: VideoListItem | null; + onGoToCalendar?: () => void; } type PrivacyType = 'public' | 'unlisted' | 'private'; @@ -116,7 +117,8 @@ const MiniCalendar: React.FC<{ const SocialPostingModal: React.FC = ({ isOpen, onClose, - video + video, + onGoToCalendar, }) => { const { t } = useTranslation(); const tutorial = useTutorial(); @@ -150,6 +152,7 @@ const SocialPostingModal: React.FC = ({ // 업로드 정보 (모달이 닫힌 후에도 유지) const [uploadVideoTitle, setUploadVideoTitle] = useState(''); const [uploadChannelName, setUploadChannelName] = useState(''); + const [uploadIsScheduled, setUploadIsScheduled] = useState(false); // 드롭다운 외부 클릭 시 닫기 useEffect(() => { @@ -300,6 +303,7 @@ const SocialPostingModal: React.FC = ({ // 업로드 정보 저장 (모달이 닫힌 후에도 유지) setUploadVideoTitle(title.trim()); setUploadChannelName(selectedAcc.display_name); + setUploadIsScheduled(publishTime === 'schedule'); setShowUploadProgress(true); try { @@ -401,6 +405,7 @@ const SocialPostingModal: React.FC = ({ setUploadErrorMessage(undefined); setUploadVideoTitle(''); setUploadChannelName(''); + setUploadIsScheduled(false); }; const handleClose = () => { @@ -429,7 +434,8 @@ const SocialPostingModal: React.FC = ({ channelName={uploadChannelName || selectedAccount?.display_name || ''} youtubeUrl={uploadYoutubeUrl} errorMessage={uploadErrorMessage} - isScheduled={publishTime === 'schedule'} + isScheduled={uploadIsScheduled} + onGoToCalendar={onGoToCalendar} /> ); diff --git a/src/components/UploadProgressModal.tsx b/src/components/UploadProgressModal.tsx index b598478..8572619 100644 --- a/src/components/UploadProgressModal.tsx +++ b/src/components/UploadProgressModal.tsx @@ -14,6 +14,7 @@ interface UploadProgressModalProps { youtubeUrl?: string; errorMessage?: string; isScheduled?: boolean; + onGoToCalendar?: () => void; } const UploadProgressModal: React.FC = ({ @@ -26,6 +27,7 @@ const UploadProgressModal: React.FC = ({ youtubeUrl, errorMessage, isScheduled = false, + onGoToCalendar, }) => { const { t } = useTranslation(); if (!isOpen) return null; @@ -151,9 +153,16 @@ const UploadProgressModal: React.FC = ({ {/* Footer */}
{canClose ? ( - + <> + + {status === 'completed' && onGoToCalendar && ( + { onClose(); onGoToCalendar(); }}> + {t('upload.goToCalendar')} + + )} + ) : (

{t('upload.doNotClose')}

)} diff --git a/src/locales/en.json b/src/locales/en.json index 4e09f94..fb1fb19 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -150,7 +150,8 @@ "viewOnYoutube": "View on YouTube", "confirm": "OK", "close": "Close", - "doNotClose": "Upload is in progress. Do not close this window." + "doNotClose": "Upload is in progress. Do not close this window.", + "goToCalendar": "View in Calendar" }, "landing": { "hero": { diff --git a/src/locales/ko.json b/src/locales/ko.json index 81472b0..962d940 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -150,7 +150,8 @@ "viewOnYoutube": "YouTube에서 보기", "confirm": "확인", "close": "닫기", - "doNotClose": "업로드가 진행 중입니다. 창을 닫지 마세요." + "doNotClose": "업로드가 진행 중입니다. 창을 닫지 마세요.", + "goToCalendar": "캘린더에서 확인" }, "landing": { "hero": { diff --git a/src/pages/Dashboard/ADO2ContentsPage.tsx b/src/pages/Dashboard/ADO2ContentsPage.tsx index b08344b..d3537d5 100644 --- a/src/pages/Dashboard/ADO2ContentsPage.tsx +++ b/src/pages/Dashboard/ADO2ContentsPage.tsx @@ -7,9 +7,10 @@ import SocialPostingModal from '../../components/SocialPostingModal'; interface ADO2ContentsPageProps { onBack: () => void; + onNavigate?: (item: string) => void; } -const ADO2ContentsPage: React.FC = ({ onBack }) => { +const ADO2ContentsPage: React.FC = ({ onBack, onNavigate }) => { const { t } = useTranslation(); const [videos, setVideos] = useState([]); const [total, setTotal] = useState(0); @@ -295,6 +296,7 @@ const ADO2ContentsPage: React.FC = ({ onBack }) => { isOpen={uploadModalOpen} onClose={handleUploadModalClose} video={uploadTargetVideo} + onGoToCalendar={onNavigate ? () => onNavigate('콘텐츠 캘린더') : undefined} />
); diff --git a/src/pages/Dashboard/CompletionContent.tsx b/src/pages/Dashboard/CompletionContent.tsx index 5993fe9..5b0e14f 100755 --- a/src/pages/Dashboard/CompletionContent.tsx +++ b/src/pages/Dashboard/CompletionContent.tsx @@ -13,6 +13,7 @@ interface CompletionContentProps { onVideoStatusChange?: (status: 'idle' | 'generating' | 'complete' | 'error') => void; onVideoProgressChange?: (progress: number) => void; onVideoComplete?: () => void; + onGoToCalendar?: () => void; } type VideoStatus = 'idle' | 'generating' | 'polling' | 'complete' | 'error'; @@ -35,6 +36,7 @@ const CompletionContent: React.FC = ({ onVideoStatusChange, onVideoProgressChange, onVideoComplete, + onGoToCalendar, }) => { const { t } = useTranslation(); const [videoStatus, setVideoStatus] = useState('idle'); @@ -47,18 +49,20 @@ const CompletionContent: React.FC = ({ const displayIntervalRef = useRef | null>(null); const tutorial = useTutorial(); + const tutorialIsActiveRef = useRef(tutorial.isActive); + tutorialIsActiveRef.current = tutorial.isActive; // 영상 생성 중 튜토리얼 트리거 (생성 상태 안내 -> 콘텐츠 정보 -> 내 정보 이동) useEffect(() => { const isComplete = videoStatus === 'complete'; const isProcessing = videoStatus === 'generating' || videoStatus === 'polling'; - - if (isProcessing && !tutorial.isActive && !tutorial.hasSeen(TUTORIAL_KEYS.GENERATING)) { + + if (isProcessing && !tutorialIsActiveRef.current && !tutorial.hasSeen(TUTORIAL_KEYS.GENERATING)) { tutorial.startTutorial(TUTORIAL_KEYS.GENERATING); - } else if (isComplete && !tutorial.isActive && !tutorial.hasSeen(TUTORIAL_KEYS.COMPLETION)) { + } else if (isComplete && !tutorialIsActiveRef.current && !tutorial.hasSeen(TUTORIAL_KEYS.COMPLETION)) { tutorial.startTutorial(TUTORIAL_KEYS.COMPLETION); } - }, [videoStatus, tutorial, tutorial.isActive]); + }, [videoStatus]); // 소셜 미디어 포스팅 모달 const [showSocialModal, setShowSocialModal] = useState(false); @@ -513,6 +517,7 @@ const CompletionContent: React.FC = ({ = ({ onNavig - {/* 채널 아이콘 + 제목 */} -
- + {/* 채널 아이콘 + 채널명 */} +
+ - {item.title} + {item.platform_username || item.platform_user_id || item.channel_name}
+ {/* 제목 */} + + {item.title} + + {/* 실패 메시지 */} {isFailed && item.error_message && (

= ({ setAnalysisError(null); } setActiveItem(item); + refreshCredits(); }; // 새 프로젝트 만들기 - 단계별 컨텐츠 렌더링 @@ -479,6 +480,7 @@ const GenerationFlow: React.FC = ({ onVideoStatusChange={setVideoGenerationStatus} onVideoProgressChange={setVideoGenerationProgress} onVideoComplete={refreshCredits} + onGoToCalendar={() => handleNavigate('콘텐츠 캘린더')} /> ); default: @@ -496,6 +498,7 @@ const GenerationFlow: React.FC = ({ return ( setActiveItem('새 프로젝트 만들기')} + onNavigate={handleNavigate} /> ); case '콘텐츠 캘린더': diff --git a/src/types/api.ts b/src/types/api.ts index c347607..0bb479f 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -379,6 +379,8 @@ export interface UploadHistoryItem { status: 'pending' | 'uploading' | 'completed' | 'failed' | 'scheduled' | 'cancelled'; title: string; channel_name: string; + platform_user_id: string | null; + platform_username: string | null; scheduled_at: string | null; uploaded_at: string | null; created_at: string;