Compare commits

..

No commits in common. "05dc2e1cc2edd9007876a86319501ded98f031f9" and "e448d8189de6629eb9490978b1752aeaaa853ed3" have entirely different histories.

5 changed files with 219 additions and 285 deletions

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { getSocialAccounts, uploadToSocial, waitForUploadComplete, TokenExpiredError, handleSocialReconnect, getAutoSeoYoutube } from '../utils/api';
import { getSocialAccounts, uploadToSocial, waitForUploadComplete, TokenExpiredError, handleSocialReconnect } from '../utils/api';
import { SocialAccount, VideoListItem, SocialUploadStatusResponse } from '../types/api';
import UploadProgressModal, { UploadStatus } from './UploadProgressModal';
@ -43,7 +43,6 @@ const SocialPostingModal: React.FC<SocialPostingModalProps> = ({
const [isPosting, setIsPosting] = useState(false);
const [isChannelDropdownOpen, setIsChannelDropdownOpen] = useState(false);
const [isPrivacyDropdownOpen, setIsPrivacyDropdownOpen] = useState(false);
const [isLoadingAutoDescription, setIsLoadingAutoDescription] = useState(false);
const channelDropdownRef = useRef<HTMLDivElement>(null);
const privacyDropdownRef = useRef<HTMLDivElement>(null);
@ -76,13 +75,12 @@ const SocialPostingModal: React.FC<SocialPostingModalProps> = ({
useEffect(() => {
if (isOpen) {
loadSocialAccounts();
loadAutocomplete();
// // 비디오 정보로 기본 제목 설정
// if (video) {
// const date = new Date(video.created_at);
// const formattedDate = `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`;
// setTitle(`${video.store_name} ${formattedDate}`);
// }
// 비디오 정보로 기본 제목 설정
if (video) {
const date = new Date(video.created_at);
const formattedDate = `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`;
setTitle(`${video.store_name} ${formattedDate}`);
}
}
}, [isOpen, video]);
@ -107,30 +105,6 @@ const SocialPostingModal: React.FC<SocialPostingModalProps> = ({
}
};
const loadAutocomplete = async () => {
if (!video?.task_id) return;
setIsLoadingAutoDescription(true);
try {
const requestPayload = {
task_id : video.task_id,
};
// Call autoSEO API
console.log('[Upload] Request payload:', requestPayload);
const autoSeoResponse = await getAutoSeoYoutube(requestPayload);
// 각 필드가 있을 때만 덮어씌움 (기존 값 보호)
if (autoSeoResponse.title) setTitle(autoSeoResponse.title);
if (autoSeoResponse.description) setDescription(autoSeoResponse.description);
if (autoSeoResponse.keywords) setTags(autoSeoResponse.keywords.join(','));
} catch (error) {
console.error('Failed to load autocomplete:', error);
// 실패해도 사용자에게 별도 알림 없이 조용히 처리
} finally {
setIsLoadingAutoDescription(false);
}
};
const handlePost = async () => {
if (!selectedChannel || !title.trim() || !video) {
alert(t('social.channelAndTitleRequired'));
@ -389,11 +363,9 @@ const SocialPostingModal: React.FC<SocialPostingModalProps> = ({
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder={isLoadingAutoDescription ? t('social.autoSeoTitle') : t('social.postTitlePlaceholder')}
// placeholder={t('social.postTitlePlaceholder')}
placeholder={t('social.postTitlePlaceholder')}
className="social-posting-input"
maxLength={100}
disabled={isLoadingAutoDescription}
/>
<span className="social-posting-char-count">{title.length}/100</span>
</div>
@ -404,12 +376,10 @@ const SocialPostingModal: React.FC<SocialPostingModalProps> = ({
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder={t(isLoadingAutoDescription ? t('social.autoSeoDescription') : t('social.postContentPlaceholder'))}
// placeholder={t('social.postContentPlaceholder')}
placeholder={t('social.postContentPlaceholder')}
className="social-posting-textarea"
maxLength={5000}
rows={4}
disabled={isLoadingAutoDescription}
/>
<span className="social-posting-char-count">{description.length}/5,000</span>
</div>
@ -421,11 +391,9 @@ const SocialPostingModal: React.FC<SocialPostingModalProps> = ({
type="text"
value={tags}
onChange={(e) => setTags(e.target.value)}
placeholder={t(isLoadingAutoDescription ? t('social.autoSeoTags') : t('social.tagsPlaceholder'))}
// placeholder={t('social.tagsPlaceholder')}
placeholder={t('social.tagsPlaceholder')}
className="social-posting-input"
maxLength={500}
disabled={isLoadingAutoDescription}
/>
<span className="social-posting-char-count">{tags.length}/500</span>
</div>

View File

@ -53,10 +53,7 @@
"selectChannel": "Please select a channel.",
"invalidVideoInfo": "Video information is invalid. (missing video_id)",
"uploadStartFailed": "Failed to start upload.",
"uploadFailed": "Upload failed.",
"autoSeoTitle": "This will be automatically generated. please wait.",
"autoSeoDescription": "This will be automatically generated. please wait.",
"autoSeoTags": "This will be automatically generated. please wait."
"uploadFailed": "Upload failed."
},
"upload": {
"title": "YouTube Upload",

View File

@ -53,10 +53,7 @@
"selectChannel": "채널을 선택해주세요.",
"invalidVideoInfo": "영상 정보가 올바르지 않습니다. (video_id 누락)",
"uploadStartFailed": "업로드 시작에 실패했습니다.",
"uploadFailed": "업로드에 실패했습니다.",
"autoSeoTitle": "자동으로 작성중입니다. 기다려주세요.",
"autoSeoDescription": "자동으로 작성중입니다. 기다려주세요.",
"autoSeoTags": "자동으로 작성중입니다. 기다려주세요."
"uploadFailed": "업로드에 실패했습니다."
},
"upload": {
"title": "YouTube 업로드",

View File

@ -317,18 +317,6 @@ export interface SocialDisconnectResponse {
// Social Upload Types (YouTube Upload)
// ============================================
// 유튜브 SEO Description 자동완성 요청
export interface YTAutoSeoRequest {
task_id: string; // 아카이브의 비디오 ID
}
// 유튜브 SEO Description 자동완성 응답
export interface YTAutoSeoResponse {
title: string;
description: string;
keywords: string[];
}
// 소셜 업로드 요청
export interface SocialUploadRequest {
video_id: number; // 아카이브의 비디오 ID

View File

@ -26,8 +26,6 @@ import {
SocialUploadResponse,
SocialUploadStatusResponse,
TokenExpiredErrorResponse,
YTAutoSeoRequest,
YTAutoSeoResponse,
} from '../types/api';
const API_URL = import.meta.env.VITE_API_URL || 'http://40.82.133.44';
@ -792,20 +790,6 @@ export async function disconnectSocialAccount(accountId: number): Promise<Social
// Social Upload API (YouTube Video Upload)
// ============================================
// YouTube Description API
export async function getAutoSeoYoutube(request: YTAutoSeoRequest): Promise<YTAutoSeoResponse> {
const response = await authenticatedFetch(`${API_URL}/social/seo/youtube`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(request),
});
await handleSocialResponse(response);
return response.json();
}
// YouTube 영상 업로드 시작
export async function uploadToSocial(request: SocialUploadRequest): Promise<SocialUploadResponse> {
const response = await authenticatedFetch(`${API_URL}/social/upload`, {