page.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. 'use client';
  2. import './style.scss';
  3. import Link from 'next/link';
  4. import { useState, useEffect, useCallback, useRef } from 'react';
  5. import { useConfigContext } from '@/contexts/configProvider';
  6. import { useMemberContext } from '@/contexts/memberProvider';
  7. import { ChangePasswordRequest } from '@/dtos/request/account';
  8. import { fetchApi, getPasswordPolicyMessage, throwError } from '@/lib/utils/client';
  9. import Loading from '@/app/component/Loading';
  10. import NavTabs from '../navTabs';
  11. export default function ChangePassword()
  12. {
  13. const config = useConfigContext();
  14. const { member } = useMemberContext();
  15. const [error, setError] = useState<string>('');
  16. const [loading, setLoading] = useState<boolean>(false);
  17. const [isComplete, setComplete] = useState<boolean>(false);
  18. const [formData, setFormData] = useState({
  19. currentPassword: '',
  20. newPassword: '',
  21. confirmPassword: ''
  22. });
  23. const currentPasswordRef = useRef<HTMLInputElement>(null);
  24. const newPasswordRef = useRef<HTMLInputElement>(null);
  25. const confirmPasswordRef = useRef<HTMLInputElement>(null);
  26. useEffect(() => {
  27. if (error) {
  28. alert(error);
  29. setError('');
  30. }
  31. }, [error]);
  32. const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
  33. e.preventDefault();
  34. if (!member) {
  35. return;
  36. }
  37. if (!formData.currentPassword) {
  38. setError("현재 비밀번호를 입력하세요.");
  39. currentPasswordRef.current?.focus();
  40. return;
  41. }
  42. if (!formData.newPassword) {
  43. setError("새 비밀번호를 입력하세요.");
  44. newPasswordRef.current?.focus();
  45. return;
  46. }
  47. if (!formData.confirmPassword) {
  48. setError("새 비밀번호 확인을 입력하세요.");
  49. confirmPasswordRef.current?.focus();
  50. return;
  51. }
  52. if (formData.newPassword !== formData.confirmPassword) {
  53. setError("새 비밀번호가 일치하지 않습니다.");
  54. return;
  55. }
  56. if (formData.currentPassword === formData.newPassword) {
  57. setError("새 비밀번호는 현재 비밀번호와 달라야 합니다.");
  58. return;
  59. }
  60. setLoading(true);
  61. fetchApi('/api/mypage/password', {
  62. method: 'POST',
  63. body: {
  64. CurrentPassword: formData.currentPassword,
  65. NewPassword: formData.newPassword,
  66. ConfirmPassword: formData.confirmPassword
  67. } as ChangePasswordRequest
  68. }).then((res) => {
  69. throwError(res);
  70. setComplete(true);
  71. }).catch(err => {
  72. setError(err.message);
  73. }).finally(() => {
  74. setLoading(false);
  75. });
  76. }
  77. const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
  78. const { id, value } = e.target;
  79. setFormData(prev => ({ ...prev, [id]: value}));
  80. }, []);
  81. useEffect(() => {
  82. if (isComplete)
  83. {
  84. // 입력 초기화
  85. setFormData({
  86. currentPassword: '',
  87. newPassword: '',
  88. confirmPassword: ''
  89. });
  90. setComplete(false);
  91. alert("비밀번호가 변경되었습니다.");
  92. }
  93. }, [isComplete]);
  94. const txtPasswordGuide = getPasswordPolicyMessage({
  95. passwordUppercaseLength: config.account.passwordUppercaseLength,
  96. passwordNumbersLength: config.account.passwordNumbersLength,
  97. passwordSpecialcharsLength: config.account.passwordSpecialcharsLength
  98. });
  99. return (
  100. <>
  101. <NavTabs />
  102. <div id="changePassword">
  103. { loading && <Loading /> }
  104. <h1>비밀번호 변경</h1>
  105. <form method="post" acceptCharset="utf-8" autoComplete="off" onSubmit={handleSubmit}>
  106. <table className="table-auto max-xl:w-full lg:w-[600px]">
  107. <caption>
  108. 비밀번호는 { config.account.passwordMinLength }자 이상 20자 이하로 설정 가능합니다.<br />
  109. { txtPasswordGuide }
  110. </caption>
  111. <colgroup>
  112. <col width="30%"/>
  113. <col width="60%"/>
  114. <col width="10%"/>
  115. </colgroup>
  116. <tbody>
  117. <tr>
  118. <th><label htmlFor='currentPassword'>현재 비밀번호</label></th>
  119. <td>
  120. <input type="password" id="currentPassword" value={formData.currentPassword} onChange={handleChange} placeholder="현재 비밀번호" ref={currentPasswordRef} />
  121. </td>
  122. <td>&nbsp;</td>
  123. </tr>
  124. <tr>
  125. <th><label htmlFor='newPassword'>새 비밀번호</label></th>
  126. <td>
  127. <input type="password" id="newPassword" value={formData.newPassword} onChange={handleChange} placeholder="새 비밀번호" ref={newPasswordRef} />
  128. </td>
  129. <td>&nbsp;</td>
  130. </tr>
  131. <tr>
  132. <th><label htmlFor='confirmPassword'>새 비밀번호 확인</label></th>
  133. <td>
  134. <input type="password" id="confirmPassword" value={formData.confirmPassword} onChange={handleChange} placeholder="새 비밀번호 확인" ref={confirmPasswordRef} />
  135. </td>
  136. <td>&nbsp;</td>
  137. </tr>
  138. </tbody>
  139. <tfoot>
  140. <tr>
  141. <td colSpan={3}>
  142. <div className="flex justify-center gap-2">
  143. <button type="submit" className="btn btn-submit">확인</button>
  144. <Link href="/profile" className="btn btn-default">취소</Link>
  145. </div>
  146. </td>
  147. </tr>
  148. </tfoot>
  149. </table>
  150. </form>
  151. </div>
  152. </>
  153. );
  154. }