page.tsx 4.8 KB

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