page.tsx 4.6 KB

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