Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

훈돌라

2024. 7. 25. 이메일 인증 비밀번호 변경 본문

카테고리 없음

2024. 7. 25. 이메일 인증 비밀번호 변경

훈돌라 2024. 7. 25. 22:26

 

'use client';

import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import ResetPasswordModal from '@/components/molecules/ResetPasswordModal';

const ResetPasswordPage: React.FC = () => {
  const router = useRouter();
  const [token, setToken] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const queryParams = new URLSearchParams(window.location.search);
    const token = queryParams.get('token');
    setToken(token);
    setLoading(false);
  }, []);

  const handleClose = () => {
    router.push('/');
  };

  if (loading) {
    return <div>Loading...</div>;
  }

  return <div>{token ? <ResetPasswordModal token={token} onClose={handleClose} /> : <div>Invalid token</div>}</div>;
};

export default ResetPasswordPage;

 

인증 요청한 이메일로 받은 링크를 클릭 시 해당 페이지로 이동하며 패스워드 변경 모달을 띄움

 

import React, { useState } from 'react';
import { supabase } from '@/supabase/client';

interface ResetPasswordModalProps {
  token: string | null;
  onClose: () => void;
}

const ResetPasswordModal: React.FC<ResetPasswordModalProps> = ({ token, onClose }) => {
  const [password, setPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  const [message, setMessage] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  const handleResetPassword = async () => {
    if (password !== confirmPassword) {
      setError('비밀번호가 일치하지 않습니다.');
      return;
    }

    try {
      const { data, error } = await supabase.auth.updateUser({ password });
      if (error) throw error;
      setMessage('비밀번호가 성공적으로 변경되었습니다.');
      setError(null);
      setTimeout(onClose, 2000);
    } catch (error) {
      console.error('비밀번호 변경 실패:', error);
      setMessage(null);
      setError(error instanceof Error ? error.message : 'An error occurred.');
    }
  };

  return (
    <div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-[999]">
      <div className="bg-white p-4 rounded w-96">
        <h1 className="text-xl font-bold mb-4 text-center text-emerald-400">Welcome to PlanTree! </h1>
        <h2 className="text-xl font-bold mb-4 text-center text-black">비밀번호 재설정</h2>
        <input
          type="password"
          placeholder="새 비밀번호를 입력하세요."
          className="mb-4 p-2 border rounded w-full text-black"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
        <input
          type="password"
          placeholder="비밀번호를 다시 입력하세요."
          className="mb-4 p-2 border rounded w-full text-black"
          value={confirmPassword}
          onChange={(e) => setConfirmPassword(e.target.value)}
        />
        {message && <p className="text-green-500">{message}</p>}
        {error && <p className="text-red-500">{error}</p>}
        <div className="flex flex-col gap-2 mt-4">
          <button className="w-full px-4 py-3 bg-gray-500 text-white rounded" onClick={onClose}>
            취소
          </button>
          <button className="w-full px-4 py-3 bg-blue-500 text-white rounded" onClick={handleResetPassword}>
            비밀번호 변경
          </button>
        </div>
      </div>
    </div>
  );
};

export default ResetPasswordModal;

 

패스워드 변경 모달


'use client';

import React, { useState, useEffect } from 'react';
import { supabase } from '../../supabase/client';
import ResetPasswordModal from './ResetPasswordModal';

const LoginModal: React.FC<{ onClose: () => void; onSignupClick: () => void }> = ({ onClose, onSignupClick }) => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [showForgotPasswordModal, setShowForgotPasswordModal] = useState(false);
  const [showResetPasswordModal, setShowResetPasswordModal] = useState(false);
  const [resetToken, setResetToken] = useState<string | null>(null);

  useEffect(() => {
    const queryParams = new URLSearchParams(location.search);
    const token = queryParams.get('token');
    if (token) {
      setResetToken(token);
      setShowResetPasswordModal(true);
    }
  }, [location]);

  const handleBackgroundClick = (e: React.MouseEvent<HTMLDivElement>) => {
    if (e.target === e.currentTarget) {
      onClose();
    }
  };

  const handleForgotPasswordClick = () => {
    setShowForgotPasswordModal(true);
  };

  const handleForgotPasswordClose = () => {
    setShowForgotPasswordModal(false);
  };

  const handleResetPasswordClose = () => {
    setShowResetPasswordModal(false);
  };

  const signIn = async (email: string, password: string) => {
    const { data, error } = await supabase.auth.signInWithPassword({
      email,
      password
    });
    if (error) throw error;
    return data;
  };

  const handleSignIn = async () => {
    try {
      const data = await signIn(email, password);
      console.log('로그인 성공:', data);
      onClose();
      window.location.reload();
    } catch (error) {
      console.error('로그인 실패:', error);
      setError(error instanceof Error ? error.message : 'An error occurred.');
    }
  };

  return (
    <>
      {!showForgotPasswordModal && !showResetPasswordModal && (
        <div
          className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-[999]"
          onClick={handleBackgroundClick}
        >
          <div className="bg-white p-4 rounded">
            <h1 className="text-xl font-bold mb-4 text-center text-emerald-400">Welcome to PlanTree! </h1>
            <h2 className="text-xl font-bold mb-4 text-center text-black">로그인</h2>
            <input
              type="text"
              placeholder="아이디"
              className="mb-2 p-2 border rounded w-full text-black"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
            />
            <input
              type="password"
              placeholder="비밀번호"
              className="mb-4 p-2 border rounded w-full text-black"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
            />
            {error && <p className="text-red-500">{error}</p>}
            <h2 className="text-center cursor-pointer text-rose-300" onClick={handleForgotPasswordClick}>
              비밀번호를 잊어버리셨나요?
            </h2>
            <div className="flex flex-col gap-2 mt-4">
              <button className="w-full px-4 py-3 font-bold bg-gray-500 text-black rounded" onClick={handleSignIn}>
                로그인
              </button>
              <button className="w-full px-4 py-3 font-bold bg-blue-500 text-black rounded" onClick={onSignupClick}>
                회원가입
              </button>
            </div>
          </div>
        </div>
      )}
      {showForgotPasswordModal && <ForgotPasswordModal onClose={handleForgotPasswordClose} />}
      {showResetPasswordModal && <ResetPasswordModal token={resetToken} onClose={handleResetPasswordClose} />}
    </>
  );
};

const ForgotPasswordModal: React.FC<{ onClose: () => void }> = ({ onClose }) => {
  const [email, setEmail] = useState('');
  const [message, setMessage] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  const handleBackgroundClick = (e: React.MouseEvent<HTMLDivElement>) => {
    if (e.target === e.currentTarget) {
      onClose();
    }
  };

  const handleResetPassword = async () => {
    try {
      const { data, error } = await supabase.auth.resetPasswordForEmail(email, {
        redirectTo: 'http://localhost:3000/reset-password?token=YOUR_TOKEN'
      });
      if (error) throw error;
      setMessage('인증번호가 이메일로 전송되었습니다.');
      setError(null);
    } catch (error) {
      console.error('비밀번호 재설정 실패:', error);
      setMessage(null);
      setError(error instanceof Error ? error.message : 'An error occurred.');
    }
  };

  return (
    <div
      className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-[999]"
      onClick={handleBackgroundClick}
    >
      <div className="bg-white p-4 rounded w-96">
        <h1 className="text-xl font-bold mb-4 text-center text-emerald-400">Welcome to PlanTree! </h1>
        <h2 className="text-xl font-bold mb-4 text-center text-black">비밀번호 찾기</h2>
        <input
          type="email"
          placeholder="이메일을 입력하세요."
          className="mb-4 p-2 border rounded w-full text-black"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        {message && <p className="text-green-500">{message}</p>}
        {error && <p className="text-red-500">{error}</p>}
        <h1 className="text-center text-xs">인증번호를 메일로 전송해드립니다.</h1>
        <div className="flex flex-col gap-2 mt-4">
          <button className="w-full px-4 py-3 bg-gray-500 text-white rounded" onClick={onClose}>
            취소
          </button>
          <button className="w-full px-4 py-3 bg-blue-500 text-white rounded" onClick={handleResetPassword}>
            인증번호 받기
          </button>
        </div>
      </div>
    </div>
  );
};

export default LoginModal;


로그인 모달

기존에 로그인 모달에 있던 패스워드 변경 모달을 다른 컴포넌트로 분리해서 사용함



남은 기능

소셜 로그인 ( 구글, 카카오, 네이버)

네이버 할 수 있을까?