ㄱㄱㄱ

This commit is contained in:
mota
2025-11-18 07:53:09 +09:00
parent 0452ca2c28
commit 4f7b98dffb
20 changed files with 1348 additions and 196 deletions

View File

@@ -0,0 +1,151 @@
'use client';
import { useState } from "react";
type VerificationState = 'initial' | 'sent' | 'verified' | 'failed' | 'changed';
type MenuAccountOptionProps = {
verificationState: VerificationState;
setVerificationState: (state: VerificationState) => void;
deleteOpen?: boolean;
setDeleteOpen?: (open: boolean) => void;
};
export default function MenuAccountOption({
verificationState,
setVerificationState,
deleteOpen,
setDeleteOpen,
}: MenuAccountOptionProps) {
const [isOpen, setIsOpen] = useState(false);
const itemClass = (active: boolean) =>
[
"relative inline-flex h-6 w-11 items-center rounded-full transition-colors",
active ? "bg-blue-600" : "bg-gray-300",
].join(" ");
const knobClass = (active: boolean) =>
[
"inline-block h-5 w-5 transform rounded-full bg-white transition",
active ? "translate-x-5" : "translate-x-1",
].join(" ");
const is = {
initial: verificationState === 'initial',
sent: verificationState === 'sent',
verified: verificationState === 'verified',
failed: verificationState === 'failed',
changed: verificationState === 'changed',
};
return (
<div>
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className={`fixed bottom-2 right-2 bg-red-400 cursor-pointer rounded-full w-[40px] h-[40px] shadow-xl z-100`}
>
</button>
{isOpen && (
<div className="fixed inset-0 flex items-center justify-center z-50">
<div className="w-[500px] h-[600px] flex bg-white/80 p-10 border rounded-lg relative">
<button
type="button"
aria-label="닫기"
onClick={() => setIsOpen(false)}
className="absolute top-3 right-3 inline-flex items-center justify-center rounded-full w-8 h-8 bg-gray-200 hover:bg-gray-300 text-gray-700"
>
×
</button>
<div className="w-full h-full overflow-auto">
<div className="mb-6">
<p className="text-sm text-gray-700"> : <span className="font-semibold">{verificationState}</span></p>
</div>
<ul className="flex flex-col gap-4">
<li className="flex items-center justify-between">
<p className="mr-4"> </p>
<button
type="button"
aria-label="초기 상태로 설정"
aria-pressed={is.initial}
onClick={() => setVerificationState('initial')}
className={itemClass(is.initial)}
>
<span className={knobClass(is.initial)} />
</button>
</li>
<li className="flex items-center justify-between">
<p className="mr-4"> </p>
<button
type="button"
aria-label="인증번호 전송 상태로 설정"
aria-pressed={is.sent}
onClick={() => setVerificationState('sent')}
className={itemClass(is.sent)}
>
<span className={knobClass(is.sent)} />
</button>
</li>
<li className="flex items-center justify-between">
<p className="mr-4"> </p>
<button
type="button"
aria-label="인증 완료 상태로 설정"
aria-pressed={is.verified}
onClick={() => setVerificationState('verified')}
className={itemClass(is.verified)}
>
<span className={knobClass(is.verified)} />
</button>
</li>
<li className="flex items-center justify-between">
<p className="mr-4"> </p>
<button
type="button"
aria-label="인증 실패 상태로 설정"
aria-pressed={is.failed}
onClick={() => setVerificationState('failed')}
className={itemClass(is.failed)}
>
<span className={knobClass(is.failed)} />
</button>
</li>
</ul>
<div className="mt-6 border-t pt-6">
<li className="flex items-center justify-between">
<p className="mr-4"> </p>
<button
type="button"
aria-label="비밀번호 변경 완료 상태로 설정"
aria-pressed={is.changed}
onClick={() =>
setVerificationState(is.changed ? 'initial' : 'changed')
}
className={itemClass(is.changed)}
>
<span className={knobClass(is.changed)} />
</button>
</li>
<li className="mt-4 flex items-center justify-between">
<p className="mr-4"> </p>
<button
type="button"
aria-label="회원 탈퇴 모달 토글"
aria-pressed={!!deleteOpen}
onClick={() => setDeleteOpen?.(!deleteOpen)}
className={itemClass(!!deleteOpen)}
>
<span className={knobClass(!!deleteOpen)} />
</button>
</li>
</div>
</div>
</div>
</div>
)}
</div>
);
}

View File

@@ -1,10 +1,23 @@
'use client';
import { useState } from "react";
import { useEffect, useState } from "react";
import ChangePasswordModal from "../ChangePasswordModal";
import PasswordChangeDoneModal from "../PasswordChangeDoneModal";
import AccountDeleteModal from "../AccountDeleteModal";
import MenuAccountOption from "@/app/menu/account/MenuAccountOption";
type VerificationState = 'initial' | 'sent' | 'verified' | 'failed' | 'changed';
export default function AccountPage() {
const [open, setOpen] = useState(false);
const [verificationState, setVerificationState] = useState<VerificationState>('initial');
const [doneOpen, setDoneOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
// 개발 옵션에서 'changed'로 전환하면 완료 모달 표시
useEffect(() => {
setDoneOpen(verificationState === 'changed');
}, [verificationState]);
return (
<main className="flex w-full flex-col">
@@ -40,7 +53,11 @@ export default function AccountPage() {
</div>
</div>
<div className="mt-6">
<button className="text-[15px] font-medium leading-[1.5] text-[#f64c4c] underline">
<button
type="button"
onClick={() => setDeleteOpen(true)}
className="text-[15px] font-medium leading-[1.5] text-[#f64c4c] underline cursor-pointer"
>
</button>
</div>
@@ -52,6 +69,28 @@ export default function AccountPage() {
onSubmit={() => {
// TODO: integrate API
}}
devVerificationState={verificationState}
/>
<MenuAccountOption
verificationState={verificationState}
setVerificationState={setVerificationState}
deleteOpen={deleteOpen}
setDeleteOpen={setDeleteOpen}
/>
<PasswordChangeDoneModal
open={doneOpen}
onClose={() => setDoneOpen(false)}
/>
<AccountDeleteModal
open={deleteOpen}
onClose={() => setDeleteOpen(false)}
onConfirm={() => {
// TODO: 탈퇴 API 연동
setDeleteOpen(false);
}}
/>
</main>
);