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 { useActionState, useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Field } from '@/components/ui/field';
import { Input } from '@/components/ui/input';
import { Modal } from '@/components/ui/modal';
import { Select } from '@/components/ui/select';
import { useFeedback } from '@/app/_hooks/use-feedback';
import {
ADMIN_LOCK_STATUS_OPTIONS,
type AdminMember,
} from '@/lib/domain/admin-member';
import { ADMIN_PASSWORD_HELP_TEXT } from '@/lib/domain/admin-member-form';
import {
INITIAL_ADMIN_MEMBER_FORM_STATE,
updateAdminMemberAction,
} from '../_actions';
import {
AdminMemberFormFields,
FieldError,
} from './admin-member-form-fields';
interface AdminMemberEditModalProps {
member: AdminMember;
onClose: () => void;
}
const FORM_ID = 'admin-member-edit-form';
/**
* 관리자 수정 팝업(시안 ADM_ADM_103_p).
*
* 이름·ID는 읽기 전용이다(시안 ①). 다만 화면의 readOnly는 표시일 뿐이라 Server Action은 두 값을
* 아예 반영하지 않는다 — 직접 POST로 다른 값을 보내도 바뀌지 않는다.
*
* **비밀번호는 [비밀번호 변경]을 눌러야 입력할 수 있다**(시안 ②). 비워 두면 기존 비밀번호를
* 유지한다는 뜻이라 Server Action도 그때는 형식 검증을 건너뛴다. 지금은 저장할 백엔드 API가 없어
* 입력값이 검증 후 버려진다 — 흐름만 완성해 둔 상태다.
*
* "잠김여부"는 활성/비활성으로 표기되지만 백엔드 필드는 `acctLockYn`(잠김 여부)이라 의미가
* 뒤집혀 있다. 그 변환은 도메인(`ADMIN_LOCK_STATUS_OPTIONS`)이 한 곳에서 책임진다.
*/
export function AdminMemberEditModal({
member,
onClose,
}: AdminMemberEditModalProps) {
const { showToast } = useFeedback();
const [state, formAction, isPending] = useActionState(
updateAdminMemberAction,
INITIAL_ADMIN_MEMBER_FORM_STATE
);
const [isPasswordEditable, setIsPasswordEditable] = useState(false);
useEffect(() => {
if (state.status === 'success') {
showToast({ variant: 'success', message: '관리자 정보를 수정했습니다.' });
onClose();
}
}, [state, showToast, onClose]);
const errors = state.status === 'error' ? (state.errors ?? {}) : {};
return (
<Modal
title="관리자 수정"
onClose={onClose}
footer={
<>
<Button type="button" variant="ghost" onClick={onClose}>
취소
</Button>
<Button
type="submit"
form={FORM_ID}
variant="primary"
disabled={isPending}
>
{isPending ? '수정 중...' : '수정'}
</Button>
</>
}
>
<form id={FORM_ID} action={formAction} className="flex flex-col gap-4">
{/* 수정 대상을 가리키는 유일한 입력. 이름·ID는 읽기 전용이라 아예 제출하지 않는다 —
Server Action도 그 둘을 읽지 않으므로 위조해 보내도 반영되지 않는다. */}
<input type="hidden" name="id" value={member.id} />
<p className="text-right text-body-sm text-danger">
* 는 필수 항목입니다.
</p>
<Field label="이름">
<Input type="text" value={member.name} readOnly />
</Field>
<Field label="ID">
<Input type="text" value={member.loginId} readOnly />
</Field>
<Field label="비밀번호">
<div className="flex items-center gap-2">
<Input
type="password"
name="password"
placeholder="비밀번호를 입력하세요."
autoComplete="new-password"
disabled={!isPasswordEditable}
/>
<Button
type="button"
variant="secondary"
onClick={() => setIsPasswordEditable(true)}
disabled={isPasswordEditable}
className="shrink-0"
>
비밀번호 변경
</Button>
</div>
</Field>
{isPasswordEditable && (
<p className="text-body-sm text-foreground-muted">
{ADMIN_PASSWORD_HELP_TEXT}
</p>
)}
<FieldError message={errors.password} />
<AdminMemberFormFields member={member} errors={errors} />
<Field label="잠김여부">
<Select
name="isLocked"
defaultValue={String(member.isLocked ?? false)}
>
{ADMIN_LOCK_STATUS_OPTIONS.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</Select>
</Field>
{state.status === 'error' && state.message && (
<p className="text-body-sm text-danger">{state.message}</p>
)}
</form>
</Modal>
);
}