File name
Commit message
Commit date
File name
Commit message
Commit date
09-16
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 { useActionState, useEffect, useRef, useState } from 'react';
import { FoxButton } from '@fox/core/components/fox-button';
import { FoxFileUpload } from '@fox/core/components/fox-file-upload';
import { FoxFormLabel } from '@fox/core/components/fox-form-label';
import { FoxHelperText } from '@fox/core/components/fox-helper-text';
import { FoxInput } from '@fox/core/components/fox-input';
import { FoxModal } from '@fox/core/components/fox-modal';
import { FoxSelect } from '@fox/core/components/fox-select';
import { FoxTextArea } from '@fox/core/components/fox-text-area';
import { FoxToggleSwitch } from '@fox/core/components/fox-toggle-switch';
import { submitFormAction } from '@/app/_hooks/submit-form-action';
import { useFeedback } from '@/app/_hooks/use-feedback';
import type { CommonCode } from '@/lib/domain/common-code';
import {
BOARD_CATEGORY_OPTIONS,
IS_BOARD_CATEGORY_ENABLED,
INQUIRY_TYPE_OPTIONS,
type BoardPost,
type BoardType,
} from '@/lib/domain/board-post';
import {
CONTENT_MAX_LENGTH,
INITIAL_BOARD_POST_FORM_STATE,
TITLE_MAX_LENGTH,
} from '@/lib/domain/board-post-form';
import { createBoardPostAction, updateBoardPostAction } from '../_actions';
import styles from './board-post-form.module.scss';
interface BoardPostFormModalProps {
boardType: BoardType;
mode: 'create' | 'edit';
post?: BoardPost;
/** 공통코드 `BBS_FAQ_CD` — FAQ의 유형 선택지. 다른 게시판에서는 쓰이지 않는다. */
faqTypes?: readonly CommonCode[];
onClose: () => void;
}
const BOARD_TITLES: Record<BoardType, string> = {
notice: '공지사항',
inquiry: '1:1문의',
faq: 'FAQ',
};
const STATIC_TYPE_OPTIONS: Partial<
Record<BoardType, readonly { value: string; label: string }[]>
> = {
inquiry: INQUIRY_TYPE_OPTIONS,
};
export function BoardPostFormModal({
boardType,
mode,
post,
faqTypes = [],
onClose,
}: BoardPostFormModalProps) {
const isCreate = mode === 'create';
const { showToast } = useFeedback();
const formRef = useRef<HTMLFormElement>(null);
const [state, formAction, isPending] = useActionState(
isCreate ? createBoardPostAction : updateBoardPostAction,
INITIAL_BOARD_POST_FORM_STATE
);
const [categoryCode, setCategoryCode] = useState(
post?.categoryCode || BOARD_CATEGORY_OPTIONS[0].value
);
const [typeCode, setTypeCode] = useState(post?.typeCode ?? '');
const [isPinned, setIsPinned] = useState(post?.isPinned ?? false);
const [isVisible, setIsVisible] = useState(post?.isVisible ?? true);
const [isPushEnabled, setIsPushEnabled] = useState(post?.isPushEnabled ?? false);
useEffect(() => {
if (state.status === 'success') {
showToast({
variant: 'success',
message: isCreate ? '게시물을 등록했습니다.' : '게시물을 수정했습니다.',
});
onClose();
}
}, [state, showToast, onClose, isCreate]);
const errors = state.status === 'error' ? (state.errors ?? {}) : {};
const typeOptions =
boardType === 'faq'
? faqTypes.map((code) => ({ value: code.code, label: code.label }))
: STATIC_TYPE_OPTIONS[boardType];
const hasNoticeOnlyFields = boardType === 'notice';
return (
<FoxModal
dismissible={false}
open
size="md"
title={`${BOARD_TITLES[boardType]} ${isCreate ? '신규 등록' : '수정'}`}
onClose={onClose}
actions={
<>
<FoxButton type="default" size="md" label="취소" onAction={onClose} />
<FoxButton
type="primary"
size="md"
label={isPending ? '저장 중...' : isCreate ? '등록' : '수정'}
disabled={isPending}
onAction={() => formRef.current?.requestSubmit()}
/>
</>
}
>
<form
ref={formRef}
onSubmit={(event) => submitFormAction(event, formAction)}
className={styles.fields}
>
<input type="hidden" name="boardType" value={boardType} />
{!isCreate && post && <input type="hidden" name="id" value={post.id} />}
{IS_BOARD_CATEGORY_ENABLED && (
<div className={styles.field}>
<FoxSelect
size="md"
label="구분"
options={BOARD_CATEGORY_OPTIONS.map((option) => ({
value: option.value,
label: option.label,
}))}
value={categoryCode}
onValueChange={setCategoryCode}
error={Boolean(errors.categoryCode)}
hint={errors.categoryCode}
/>
<input type="hidden" name="categoryCode" value={categoryCode} />
</div>
)}
{typeOptions && typeOptions.length > 0 && (
<div className={styles.field}>
<FoxSelect
size="md"
label="유형"
options={typeOptions.map((option) => ({
value: option.value,
label: option.label,
}))}
value={typeCode}
onValueChange={setTypeCode}
placeholder="유형을 선택하세요."
error={Boolean(errors.typeCode)}
hint={errors.typeCode}
/>
<input type="hidden" name="typeCode" value={typeCode} />
</div>
)}
<div className={styles.field}>
<FoxInput
size="md"
name="title"
label="제목"
requirement="required"
defaultValue={post?.title ?? ''}
placeholder="제목을 입력하세요."
invalid={Boolean(errors.title)}
message={errors.title}
maxLength={TITLE_MAX_LENGTH}
/>
</div>
<div className={styles.field}>
<FoxTextArea
name="content"
label="내용"
requirement="required"
defaultValue={post?.content ?? ''}
placeholder="내용을 입력하세요."
rows={8}
counter
maxLength={CONTENT_MAX_LENGTH}
invalid={Boolean(errors.content)}
message={errors.content}
/>
</div>
<div className={styles.field}>
<FoxFileUpload
mode="default"
name="attachment"
label="첨부파일"
selectLabel="파일선택"
placeholder="파일을 선택해 주세요."
/>
<input
type="hidden"
name="attachmentId"
defaultValue={post?.attachmentId ?? ''}
/>
</div>
{hasNoticeOnlyFields && (
<div className={styles.field}>
<FoxFormLabel as="span">상단고정</FoxFormLabel>
<FoxToggleSwitch
size="md"
name="isPinned"
value="Y"
label={isPinned ? '고정' : '미고정'}
checked={isPinned}
onChange={setIsPinned}
/>
</div>
)}
{hasNoticeOnlyFields && (
<div className={styles.field}>
<FoxFormLabel as="span">게시기간</FoxFormLabel>
<div className={styles.period}>
<FoxInput
size="md"
type="date"
name="startDate"
aria-label="게시 시작일"
defaultValue={post?.startDate ?? ''}
invalid={Boolean(errors.startDate)}
/>
<span aria-hidden="true">~</span>
<FoxInput
size="md"
type="date"
name="endDate"
aria-label="게시 종료일"
defaultValue={post?.endDate ?? ''}
invalid={Boolean(errors.endDate)}
/>
</div>
{(errors.startDate ?? errors.endDate) && (
<FoxHelperText
type="danger"
message={errors.startDate ?? errors.endDate ?? ''}
/>
)}
</div>
)}
<div className={styles.field}>
<FoxFormLabel as="span">사용여부</FoxFormLabel>
<FoxToggleSwitch
size="md"
name="isVisible"
value="Y"
label={isVisible ? '노출' : '미노출'}
checked={isVisible}
onChange={setIsVisible}
/>
</div>
{hasNoticeOnlyFields && (
<div className={styles.field}>
<FoxFormLabel as="span">앱푸쉬 설정</FoxFormLabel>
<FoxToggleSwitch
size="md"
name="isPushEnabled"
value="Y"
label={isPushEnabled ? '발송' : '미발송'}
checked={isPushEnabled}
onChange={setIsPushEnabled}
/>
</div>
)}
{state.status === 'error' && state.message && (
<FoxHelperText type="danger" message={state.message} />
)}
</form>
</FoxModal>
);
}