File name
Commit message
Commit date
File name
Commit message
Commit date
09-04
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
'use client';
import { useState } from 'react';
import { FoxFileUpload } from '@fox/core/components/fox-file-upload';
import { FoxHelperText } from '@fox/core/components/fox-helper-text';
import {
FILE_MODULE,
MAX_STREAM_UPLOAD_BYTES,
allowedExtensions,
fileModuleAccept,
formatMaxUploadSize,
isAllowedFileExtension,
} from '@/lib/domain/file-module';
const UPLOAD_ENDPOINT = `/contents/upload?module=${FILE_MODULE.vodContent.id}`;
const MAX_SIZE_TEXT = formatMaxUploadSize(MAX_STREAM_UPLOAD_BYTES);
const UPLOAD_HELP_TEXT = `${allowedExtensions(FILE_MODULE.vodContent).join(', ')} · ${MAX_SIZE_TEXT} 이하`;
interface ContentVideoFieldProps {
defaultFileId?: string | null;
/** 이미 올라가 있는 영상의 원본 파일명. 없으면 대체 문구로 그린다. */
defaultFileName?: string | null;
}
/**
* 영상 콘텐츠 — 고르는 즉시 스트리밍 라우트로 올리고 파일 ID만 폼에 싣는다.
*
* 영상은 Server Action의 multipart 한도(10MB)를 넘기 때문에 폼 제출에 파일을 실을 수 없다.
* 업로드가 끝나기 전에 저장하면 영상 없이 저장되므로 그동안은 `videoUploading`을 실어 보내
* Server Action이 막게 한다.
*/
export function ContentVideoField({
defaultFileId,
defaultFileName,
}: ContentVideoFieldProps) {
const [fileId, setFileId] = useState(defaultFileId ?? '');
const [isUploading, setIsUploading] = useState(false);
const [error, setError] = useState('');
// 고른 영상이 끝내 올라가지 않은 상태. 이대로 저장하면 영상 없이 저장되므로 Server Action이
// 막게 한다 — 화면에 오류가 떠 있어도 저장 버튼은 눌리기 때문이다.
const [hasFailedPick, setHasFailedPick] = useState(false);
async function upload(picked: File[]) {
const file = picked[0];
if (!file) {
return;
}
setError('');
setHasFailedPick(false);
if (!isAllowedFileExtension(FILE_MODULE.vodContent, file.name)) {
setHasFailedPick(true);
setError(
`${allowedExtensions(FILE_MODULE.vodContent).join(', ')} 파일만 올릴 수 있습니다.`
);
return;
}
// 넘겨 보내면 앞단이 사유 없는 413으로 끊는다 — 고르는 순간 이유를 말해 준다.
if (file.size > MAX_STREAM_UPLOAD_BYTES) {
setHasFailedPick(true);
setError(`영상은 ${MAX_SIZE_TEXT} 이하만 올릴 수 있습니다.`);
return;
}
const body = new FormData();
body.set('file', file);
setIsUploading(true);
try {
const response = await fetch(UPLOAD_ENDPOINT, { method: 'POST', body });
const payload = (await response.json()) as { id?: string; message?: string };
if (!response.ok || !payload.id) {
setHasFailedPick(true);
setError(payload.message ?? '영상을 업로드하지 못했습니다.');
return;
}
setFileId(payload.id);
} catch {
setHasFailedPick(true);
setError('영상을 업로드하지 못했습니다.');
} finally {
setIsUploading(false);
}
}
return (
<>
<FoxFileUpload
mode="default"
accept={fileModuleAccept(FILE_MODULE.vodContent)}
defaultFiles={
defaultFileId
? [{ id: defaultFileId, name: defaultFileName ?? '등록된 영상' }]
: undefined
}
selectLabel="파일선택"
placeholder="파일을 선택해 주세요."
description={UPLOAD_HELP_TEXT}
disabled={isUploading}
onSelect={upload}
onRemove={() => {
setFileId('');
setError('');
setHasFailedPick(false);
}}
/>
<input type="hidden" name="videoFileId" value={fileId} />
{isUploading && <input type="hidden" name="videoUploading" value="1" />}
{hasFailedPick && <input type="hidden" name="videoUploadFailed" value="1" />}
{isUploading && (
<FoxHelperText type="information" message="영상을 올리는 중입니다…" />
)}
{error && <FoxHelperText type="danger" message={error} />}
</>
);
}