page.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. 'use client';
  2. import './style.scss';
  3. import Link from 'next/link';
  4. import { Checkbox } from '@/components/ui/checkbox';
  5. import { useState, useEffect, useRef } from 'react';
  6. import { GoogleLogin } from '@react-oauth/google';
  7. import { fetchApi, throwError } from '@/lib/utils/client';
  8. import { LoginRequest } from '@/types/request/auth';
  9. import { LoginResponse } from '@/types/response/auth';
  10. import useAuth from '@/hooks/useAuth';
  11. export default function Page()
  12. {
  13. const { login } = useAuth();
  14. const [error, setError] = useState<string>('');
  15. const [loading, setLoading] = useState<boolean>(false);
  16. const [email, setEmail] = useState<string>('');
  17. const [password, setPassword] = useState<string>('');
  18. const [rememberMe, setRememberMe] = useState<boolean>(false);
  19. const emailRef = useRef<HTMLInputElement>(null);
  20. const passwordRef = useRef<HTMLInputElement>(null);
  21. const googleBtnRef = useRef<HTMLDivElement>(null);
  22. const [googleBtnWidth, setGoogleBtnWidth] = useState<number>(0);
  23. useEffect(() => {
  24. if (error) {
  25. alert(error);
  26. setError('');
  27. }
  28. }, [error]);
  29. useEffect(() => {
  30. if (googleBtnRef.current) {
  31. const observer = new ResizeObserver(entries => {
  32. for (const entry of entries) {
  33. setGoogleBtnWidth(Math.floor(entry.contentRect.width));
  34. }
  35. });
  36. observer.observe(googleBtnRef.current);
  37. return () => observer.disconnect();
  38. }
  39. }, []);
  40. const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
  41. e.preventDefault();
  42. try {
  43. if (email.length < 1) {
  44. emailRef.current?.focus();
  45. throw new Error('이메일을 입력하세요.');
  46. }
  47. if (password.length < 1) {
  48. passwordRef.current?.focus();
  49. throw new Error('비밀번호를 입력하세요.');
  50. }
  51. const res = await fetchApi<LoginResponse>('/api/auth/login', {
  52. method: 'POST',
  53. body: { Email: email, Password: password } as LoginRequest
  54. });
  55. throwError(res);
  56. login(rememberMe);
  57. } catch (err) {
  58. if (err instanceof Error) {
  59. setError(err.message);
  60. }
  61. } finally {
  62. setLoading(false);
  63. }
  64. }
  65. // 구글 로그인
  66. const handleGoogleLogin = async (credentialResponse: { credential?: string }) => {
  67. try {
  68. const res = await fetchApi<LoginResponse>('/api/auth/google-login', {
  69. method: 'POST',
  70. body: { credential: credentialResponse.credential }
  71. });
  72. throwError(res);
  73. login(rememberMe);
  74. } catch (err) {
  75. if (err instanceof Error) {
  76. setError(err.message);
  77. }
  78. }
  79. };
  80. const handleGoogleLoginFailed = () => {
  81. setError('Google 로그인에 실패했습니다.');
  82. };
  83. return (
  84. <>
  85. <div id="loginForm" className="row-start-2 flex flex-row flex-wrap gap-2">
  86. <fieldset className="grow">
  87. <legend>로그인</legend>
  88. <form method="post" acceptCharset="utf-8" autoComplete="off" className="grid p-4" onSubmit={handleSubmit}>
  89. <label htmlFor="email">이메일</label>
  90. <input type="email" name="email" id="email" ref={emailRef} maxLength={30} onChange={e => setEmail(e.target.value)} autoComplete="off" autoFocus />
  91. <label htmlFor="password">비밀번호</label>
  92. <input type="password" name="password" id="password" ref={passwordRef} maxLength={20} onChange={e => setPassword(e.target.value)} />
  93. <button type="submit" className="btn btn-submit" disabled={loading}>
  94. {loading ? "로그인 중..." : "로그인"}
  95. </button>
  96. <div ref={googleBtnRef} className='w-full mt-2'>
  97. {googleBtnWidth > 0 && (
  98. <GoogleLogin
  99. onSuccess={handleGoogleLogin}
  100. onError={handleGoogleLoginFailed}
  101. width={googleBtnWidth}
  102. size="large"
  103. shape="rectangular"
  104. context="signin"
  105. ux_mode="redirect"
  106. login_uri={`${window.location.origin}/login/google/callback`}
  107. logo_alignment="center"
  108. />
  109. )}
  110. </div>
  111. </form>
  112. <hr hidden/>
  113. </fieldset>
  114. <fieldset className="grow basis-1/2">
  115. <dl>
  116. <dt>아직 회원이 아니신가요?</dt>
  117. <dd>회원가입 한번으로 커뮤니티에 참여하세요!</dd>
  118. <dd>
  119. <Link href="/register">
  120. <small>></small> 회원가입
  121. </Link>
  122. </dd>
  123. </dl>
  124. <hr />
  125. <dl>
  126. <dt>비밀번호를 잊으셨나요?</dt>
  127. <dd>비밀번호를 깜박했다면 다시 설정할 수 있어요!</dd>
  128. <dd>
  129. <Link href="/forgot-password">
  130. <small>></small> 비밀번호 재설정
  131. </Link>
  132. </dd>
  133. </dl>
  134. <section className="mt-3">
  135. <Checkbox name="remember_me" id="rememberMe" checked={rememberMe} onCheckedChange={(checked) => setRememberMe(checked === true)} />
  136. <label htmlFor="rememberMe">로그인 상태 유지</label>
  137. </section>
  138. </fieldset>
  139. </div>
  140. </>
  141. );
  142. }