page.tsx 4.6 KB

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