feat(ai-character): 커뮤니티 생성 미디어 흐름 개선

This commit is contained in:
Yu Sung
2026-08-05 00:33:49 +09:00
parent 766a06ad0a
commit fb9b550040
22 changed files with 700 additions and 141 deletions

View File

@@ -5,7 +5,7 @@ import { routePaths } from "@/app/route-paths";
import { createAudioContentCreateBody, deactivateAudioContent, updateAudioContent } from "@/features/audio-contents/api/audio-content-api";
import { uploadAudioContent } from "@/features/audio-contents/api/upload-audio-content";
import type { UploadAudioContentOptions, UploadAuthDependencies } from "@/features/audio-contents/api/upload-audio-content";
import { audioErrorMessage, coverErrorMessage, createInitialPrice, defaultAudioContentCreateSettings, formatPrice, isFutureLocalDateTime, parsePrice, toCreateRequest, toUpdateRequest } from "@/features/audio-contents/components/audio-content-form-helpers";
import { audioErrorMessage, coverErrorMessage, createInitialPrice, defaultAudioContentCreateSettings, isFutureLocalDateTime, parsePrice, toCreateRequest, toUpdateRequest } from "@/features/audio-contents/components/audio-content-form-helpers";
import type { AudioContentCreateSettings } from "@/features/audio-contents/components/audio-content-form-helpers";
import { AudioContentCreateOptions } from "@/features/audio-contents/components/AudioContentCreateOptions";
import { AudioContentThemeSelect } from "@/features/audio-contents/components/AudioContentThemeSelect";
@@ -21,6 +21,7 @@ import type { CropRenderRequest } from "@/shared/lib/crop-image";
import { focusFirstInvalidControl } from "@/shared/lib/focus-first-invalid-control";
import { formatSeoulDateTime } from "@/shared/lib/formatters";
import { ConfirmDeactivateDialog } from "@/shared/ui/confirm-deactivate-dialog";
import { CanPriceField } from "@/shared/ui/can-price-field";
import { FileField } from "@/shared/ui/file-field";
import { ImageCropDialog } from "@/shared/ui/image-crop-dialog";
import type { CropSourceImage } from "@/shared/ui/image-crop-dialog";
@@ -80,9 +81,8 @@ export function AudioContentForm({ apiClient, audio, characterId, createCropSour
useEffect(() => () => cropSource?.release?.(), [cropSource]);
function changePrice(value: string) {
const nextPrice = formatPrice(value);
setPrice(nextPrice);
if (mode === "create" && nextPrice === "0") {
setPrice(value);
if (mode === "create" && parsePrice(value) === 0) {
setCreateSettings((current) => ({ ...current, purchaseOption: "BOTH", isGeneratePreview: false, isPointAvailable: false, previewStartTime: null, previewEndTime: null }));
}
}
@@ -236,8 +236,7 @@ export function AudioContentForm({ apiClient, audio, characterId, createCropSour
{errors.detail === undefined ? null : <p className="text-sm font-semibold text-destructive" id={errorIds.detail} role="alert">{errors.detail}</p>}
<TagInput error={errors.tags} errorId={errorIds.tags} label="태그" onChange={setTags} value={tags} />
{mode === "create" ? <AudioContentThemeSelect error={errors.theme} errorId={errorIds.theme} onChange={setThemeId} themes={themes} value={themeId} /> : <p className="rounded-lg border border-border bg-muted p-3 text-sm font-semibold text-muted-foreground">: {audio?.themeStr ?? "-"} ( )</p>}
<label className="flex flex-col gap-2 text-sm font-semibold"><input aria-describedby={errors.price === undefined ? undefined : errorIds.price} aria-invalid={errors.price === undefined ? undefined : true} className="rounded-md border border-input bg-card px-3 py-2 text-base font-normal text-foreground" inputMode="numeric" min="0" onChange={(event) => changePrice(event.currentTarget.value)} step="1" type="number" value={price} /></label><p className="text-sm text-muted-foreground">단위: </p>
{errors.price === undefined ? null : <p className="text-sm font-semibold text-destructive" id={errorIds.price} role="alert">{errors.price}</p>}
<CanPriceField error={errors.price} errorId={errorIds.price} onChange={changePrice} value={price} />
{mode === "create" ? <AudioContentCreateOptions isPaid={isPaid} onChange={setCreateSettings} value={createSettings} /> : null}
{mode === "create" ? <ReleaseScheduleField error={errors.releaseDate} errorId={errorIds.releaseDate} onChange={setReleaseSchedule} value={releaseSchedule} /> : <p className="rounded-lg border border-border bg-muted p-3 text-sm font-semibold text-muted-foreground">: {audio?.releaseDate == null ? "즉시 공개" : formatSeoulDateTime(audio.releaseDate)} ( )</p>}
<p className="rounded-lg border border-border bg-muted p-3 text-sm font-semibold text-muted-foreground">시리즈: 현재 .</p>