File name
Commit message
Commit date
File name
Commit message
Commit date
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;
}
/**
* 영상 콘텐츠 — 고르는 즉시 스트리밍 라우트로 올리고 파일 ID만 폼에 싣는다.
*
* 영상은 Server Action의 multipart 한도(10MB)를 넘기 때문에 폼 제출에 파일을 실을 수 없다.
* 업로드가 끝나기 전에 저장하면 영상 없이 저장되므로 그동안은 `videoUploading`을 실어 보내
* Server Action이 막게 한다.
*/
export function ContentVideoField({ defaultFileId }: 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)}
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} />}
</>
);
}