| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- 'use client';
- import './style.scss';
- import Link from 'next/link';
- import { useState, useEffect, useRef } from 'react';
- import { fetchApi } from '@/lib/utils/client';
- import useAuth from '@/hooks/useAuth';
- import useErrorAlert from '@/hooks/useErrorAlert';
- import Loading from '@/app/component/Loading';
- import NavTabs from '../navTabs';
- const CODE_EXPIRATION_SECONDS = 600; // 10분
- export default function Withdraw()
- {
- const { member, logout } = useAuth();
- const { setError } = useErrorAlert();
- const [loading, setLoading] = useState<boolean>(false);
- const [isComplete, setComplete] = useState<boolean>(false);
- const [agree, setAgree] = useState<boolean>(false);
- const [step, setStep] = useState<1|2>(1);
- const [code, setCode] = useState<string>('');
- const [remaining, setRemaining] = useState<number>(0);
- const codeRef = useRef<HTMLInputElement>(null);
- // 카운트다운
- useEffect(() => {
- if (step !== 2 || remaining <= 0) {
- return;
- }
- const timer = setInterval(() => {
- setRemaining(prev => Math.max(0, prev - 1));
- }, 1000);
- return () => clearInterval(timer);
- }, [step, remaining]);
- const handleRequestCode = async () => {
- if (!member) {
- return;
- }
- if (!agree) {
- alert('탈퇴에 동의해주세요.');
- return;
- }
- setLoading(true);
- try {
- await fetchApi('/api/mypage/withdraw/request-code', {
- method: 'POST'
- });
- setStep(2);
- setRemaining(CODE_EXPIRATION_SECONDS);
- setCode('');
- setTimeout(() => codeRef.current?.focus(), 100);
- } catch (err) {
- if (err instanceof Error) {
- setError(err.message);
- }
- } finally {
- setLoading(false);
- }
- };
- const handleResendCode = async () => {
- setLoading(true);
- try {
- await fetchApi('/api/mypage/withdraw/request-code', {
- method: 'POST'
- });
- setRemaining(CODE_EXPIRATION_SECONDS);
- setCode('');
- alert('인증번호를 재발송했습니다. 메일함을 확인해주세요.');
- } catch (err) {
- if (err instanceof Error) {
- setError(err.message);
- }
- } finally {
- setLoading(false);
- }
- };
- const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
- e.preventDefault();
- if (!member) {
- return;
- }
- if (!code) {
- alert('인증번호를 입력해주세요.');
- codeRef.current?.focus();
- return;
- }
- if (!confirm('정말 탈퇴하시겠습니까?')) {
- return;
- }
- setLoading(true);
- try {
- await fetchApi('/api/mypage/withdraw', {
- method: 'POST',
- body: { code }
- });
- setComplete(true);
- } catch (err) {
- if (err instanceof Error) {
- setError(err.message);
- }
- } finally {
- setLoading(false);
- }
- };
- useEffect(() => {
- if (isComplete) {
- alert('탈퇴가 완료되었습니다.');
- setComplete(false);
- // 탈퇴 후 자동 로그아웃: httpOnly 쿠키(accessToken/refreshToken) 클리어 + AuthContext 리셋 + 홈 이동
- logout();
- }
- }, [isComplete]);
- const formatRemaining = (sec: number) => {
- const m = Math.floor(sec / 60).toString().padStart(2, '0');
- const s = (sec % 60).toString().padStart(2, '0');
- return `${m}:${s}`;
- };
- if (!member) {
- return null;
- }
- return (
- <>
- <NavTabs />
- <div id="withdraw">
- { loading && <Loading /> }
- <h1>회원탈퇴</h1>
- {step === 1 && (
- <div>
- <table className="table-auto max-xl:w-full lg:w-[600px]">
- <tbody>
- <tr>
- <th>
- <blockquote>
- 사용하고 계신 계정({member.email})은 탈퇴할 경우 재사용 및 복구가 불가능합니다.
- <ins>탈퇴한 계정은 본인과 타인 모두 재사용 및 복구가 불가하오니 신중하게 선택하시기 바랍니다.</ins>
- <br/>
- <br/>
- 추가 회원가입은 탈퇴일로부터 90일 후에 가능합니다.
- 탈퇴 후 회원정보와 주요 서비스 이용기록은 모두 삭제되며, 삭제된 데이터는 복구되지 않습니다.
- <br/>
- <br/>
- 삭제되는 내용을 확인하시고 필요한 데이터는 미리 백업을 해주세요.
- 탈퇴 후 게시판, 댓글에 등록한 게시물은 삭제되지 않고 유지됩니다.
- <br/>
- <br/>
- 본인 확인을 위해 가입하신 이메일({member.email})로 인증번호를 발송합니다.
- </blockquote>
- </th>
- </tr>
- <tr>
- <td>
- <label>
- <input type="checkbox" name="agree" checked={agree} onChange={e => setAgree(e.target.checked)} />
- 위 내용으로 탈퇴에 동의합니다.
- </label>
- </td>
- </tr>
- </tbody>
- <tfoot>
- <tr>
- <td>
- <div className="flex justify-center gap-2">
- <button type="button" className="btn btn-primary" onClick={handleRequestCode} disabled={loading || !agree}>
- 인증번호 받기
- </button>
- <Link href="/profile" className="btn btn-default">취소</Link>
- </div>
- </td>
- </tr>
- </tfoot>
- </table>
- </div>
- )}
- {step === 2 && (
- <form method="post" acceptCharset="utf-8" autoComplete="off" onSubmit={handleSubmit}>
- <table className="table-auto max-xl:w-full lg:w-[600px]">
- <tbody>
- <tr>
- <th>
- <blockquote>
- {member.email} 으로 인증번호를 발송했습니다. 메일함을 확인하시고 아래에 6자리 코드를 입력해주세요.
- <br/>
- <br/>
- 이 코드는 10분간 유효하며, 만료되면 재발송 버튼을 눌러주세요.
- </blockquote>
- </th>
- </tr>
- <tr>
- <td>
- <div className='flex flex-col sm:flex-row gap-2 justify-self-center items-center'>
- <label htmlFor="code" className='pe-2 shrink-0'>인증번호</label>
- <input
- type="text"
- name="code"
- id="code"
- className="form-control w-full"
- ref={codeRef}
- value={code}
- onChange={e => setCode(e.target.value.replace(/[^0-9]/g, '').slice(0, 6))}
- maxLength={6}
- inputMode="numeric"
- pattern="[0-9]{6}"
- placeholder="6자리 숫자"
- autoFocus
- />
- <span className='text-sm text-gray-500 shrink-0'>
- {remaining > 0 ? formatRemaining(remaining) : '만료됨'}
- </span>
- </div>
- </td>
- </tr>
- </tbody>
- <tfoot>
- <tr>
- <td>
- <div className="flex justify-center gap-2">
- <button type="submit" className="btn btn-primary" disabled={loading || remaining <= 0}>탈퇴 완료</button>
- <button type="button" className="btn btn-default" onClick={handleResendCode} disabled={loading}>재발송</button>
- <button type="button" className="btn btn-default" onClick={() => { setStep(1); setCode(''); setRemaining(0); }} disabled={loading}>이전</button>
- </div>
- </td>
- </tr>
- </tfoot>
- </table>
- </form>
- )}
- </div>
- </>
- );
- }
|