page.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. 'use client';
  2. import './style.scss';
  3. import Link from 'next/link';
  4. import Image from 'next/image';
  5. import { useState, useEffect } from 'react';
  6. import { useMemberContext } from '@/contexts/memberProvider';
  7. import { fetchApi, throwError } from '@/lib/utils/client';
  8. import Loading from '@/app/component/Loading';
  9. export default function ChangeThumb()
  10. {
  11. const { member, setMember } = useMemberContext();
  12. const [error, setError] = useState<string>('');
  13. const [loading, setLoading] = useState<boolean>(false);
  14. const [newThumb, setNewThumb] = useState<File|null>(null);
  15. const [preview, setPreview] = useState<string|null>(null);
  16. useEffect(() => {
  17. if (error) {
  18. alert(error);
  19. setError('');
  20. }
  21. }, [error]);
  22. const resetFileInput = () => {
  23. const fileInput = document.getElementById("thumb") as HTMLInputElement;
  24. if (fileInput) fileInput.value = "";
  25. };
  26. const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
  27. e.preventDefault();
  28. if (!member) {
  29. return;
  30. }
  31. if (!newThumb) {
  32. return setError('사진을 선택하세요.');
  33. }
  34. setLoading(true);
  35. const formData = new FormData();
  36. formData.append('thumb', newThumb);
  37. fetchApi<{thumbUrl: string}>('/api/mypage/thumb', {
  38. method: 'POST',
  39. body: formData
  40. }).then((res) => {
  41. throwError(res);
  42. member.thumb = (res.data?.thumbUrl || null);
  43. setMember(member);
  44. localStorage.setItem('member', JSON.stringify(member));
  45. alert("사진이 변경되었습니다.");
  46. }).catch(err => {
  47. setError(err.message);
  48. }).finally(() => {
  49. setLoading(false);
  50. setNewThumb(null);
  51. setPreview(null);
  52. resetFileInput();
  53. });
  54. }
  55. const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  56. if (e.target.files && e.target.files[0]) {
  57. const selectedFile = e.target.files[0];
  58. setNewThumb(selectedFile);
  59. // 이미지 미리보기
  60. const reader = new FileReader();
  61. reader.onloadend = () => setPreview(reader.result as string);
  62. reader.readAsDataURL(selectedFile);
  63. }
  64. };
  65. const handleRemove = async () => {
  66. // 미리보기만 있고 서버에 저장된 사진이 아닌 경우 로컬만 초기화
  67. if (!member?.thumb) {
  68. setNewThumb(null);
  69. setPreview(null);
  70. resetFileInput();
  71. return;
  72. }
  73. if (!confirm('사진을 삭제하시겠습니까?')) {
  74. return;
  75. }
  76. setLoading(true);
  77. fetchApi('/api/mypage/thumb', {
  78. method: 'DELETE'
  79. }).then((res) => {
  80. throwError(res);
  81. member.thumb = null;
  82. setMember(member);
  83. localStorage.setItem('member', JSON.stringify(member));
  84. setNewThumb(null);
  85. setPreview(null);
  86. resetFileInput();
  87. alert("사진이 삭제되었습니다.");
  88. }).catch(err => {
  89. setError(err.message);
  90. }).finally(() => {
  91. setLoading(false);
  92. });
  93. };
  94. return (
  95. <div id="changeThumb">
  96. { loading && <Loading /> }
  97. <h1>회원 사진</h1>
  98. <form method="post" acceptCharset="utf-8" autoComplete="off" onSubmit={handleSubmit}>
  99. <table className="table-auto max-xl:w-full lg:w-[600px]">
  100. <caption>
  101. 커뮤니티 프로필에 대표 사진을 설정할 수 있습니다.
  102. </caption>
  103. <colgroup>
  104. <col width="140px"/>
  105. <col width="*"/>
  106. </colgroup>
  107. <tbody>
  108. <tr>
  109. <th>
  110. <figure>
  111. {preview ?
  112. <Image src={preview} alt="미리보기" width={140} height={140} layout="responsive" style={{objectFit: "cover"}} />
  113. :
  114. member?.thumb ?
  115. <Image src={member.thumb} alt={member.email} width={140} height={140} style={{objectFit: "cover"}} unoptimized={true} />
  116. :
  117. <Image src="/resources/thumb.gif" alt="기본 사진" width={140} height={140} style={{objectFit: "cover"}} />
  118. }
  119. </figure>
  120. </th>
  121. <td>
  122. <div className="flex justify-start gap-2">
  123. <label htmlFor="thumb" className="btn btn-default">사진 변경</label>
  124. <input type="file" id="thumb" accept="image/jpg,image/jpeg,image/png,image/gif" hidden onChange={handleChange} />
  125. {(preview || member?.thumb) &&
  126. <button type="button" className="btn btn-default" onClick={handleRemove}>삭제</button>
  127. }
  128. </div>
  129. <p className="mt-3">
  130. 98x98 크기 이상, 4MB 이하의 사진이 권장됩니다. 이미지 파일을 사용하세요. 사진이 {process.env.NEXT_PUBLIC_SITE_NAME} 커뮤니티 가이드를 준수해야 합니다.
  131. </p>
  132. </td>
  133. </tr>
  134. </tbody>
  135. <tfoot>
  136. <tr>
  137. <td colSpan={2}>
  138. <div className="flex justify-center gap-2">
  139. <button type="submit" className="btn btn-submit">확인</button>
  140. <Link href="/profile" className="btn btn-default">취소</Link>
  141. </div>
  142. </td>
  143. </tr>
  144. </tfoot>
  145. </table>
  146. </form>
  147. </div>
  148. );
  149. }