page.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  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 { fetchChangePhoto } from '@/lib/api/account';
  8. export default function ChangePhoto()
  9. {
  10. const { member, setMember } = useMemberContext();
  11. const [error, setError] = useState<string>('');
  12. const [newPhoto, setNewPhoto] = useState<File|null>(null);
  13. const [preview, setPreview] = useState<string|null>(null);
  14. const [remove, setRemove] = useState<boolean>(false);
  15. useEffect(() => {
  16. if (error) {
  17. alert(error);
  18. setError('');
  19. }
  20. }, [error]);
  21. const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
  22. e.preventDefault();
  23. if (!member) {
  24. return;
  25. }
  26. if (!newPhoto && !remove) {
  27. return setError('사진을 선택하세요.');
  28. }
  29. fetchChangePhoto(newPhoto).then((res) => {
  30. if (!res.ok) {
  31. throw new Error(res.message!);
  32. }
  33. member.photo = (res.data?.photoURL || null);
  34. setMember(member);
  35. localStorage.setItem('member', JSON.stringify(member));
  36. alert("사진이 변경되었습니다.");
  37. }).catch(err => {
  38. handleRemove();
  39. setError(err.message);
  40. }).finally(() => {
  41. setRemove(false);
  42. setNewPhoto(null);
  43. setPreview(null);
  44. });
  45. }
  46. const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  47. if (e.target.files && e.target.files[0]) {
  48. const selectedFile = e.target.files[0];
  49. setNewPhoto(selectedFile);
  50. setRemove(false);
  51. // 이미지 미리보기
  52. const reader = new FileReader();
  53. reader.onloadend = () => setPreview(reader.result as string);
  54. reader.readAsDataURL(selectedFile);
  55. }
  56. };
  57. const handleRemove = () => {
  58. setNewPhoto(null);
  59. setPreview(null);
  60. setRemove(true);
  61. const fileInput = document.getElementById("photo") as HTMLInputElement;
  62. if (fileInput) {
  63. fileInput.value = ""; // 파일 선택 초기화
  64. }
  65. };
  66. return (
  67. <div id="changePhoto">
  68. <h1>회원 사진</h1>
  69. <form id="fChangePhoto" method="post" acceptCharset="utf-8" autoComplete="off" onSubmit={handleSubmit}>
  70. <table className="table-auto max-xl:w-full lg:w-[600px]">
  71. <caption>
  72. 커뮤니티 프로필에 대표 사진을 설정할 수 있습니다.
  73. </caption>
  74. <colgroup>
  75. <col width="140px"/>
  76. <col width="*"/>
  77. </colgroup>
  78. <tbody>
  79. <tr>
  80. <th>
  81. <figure>
  82. {preview ?
  83. <Image src={preview} alt="미리보기" width={140} height={140} layout="responsive" style={{objectFit: "cover"}} />
  84. :
  85. (member.photo && !remove) ?
  86. <Image src={member.photo} alt={member.email} width={140} height={140} style={{objectFit: "cover"}} unoptimized={true} />
  87. :
  88. <Image src="/resources/thumb.gif" alt="기본 사진" width={140} height={140} style={{objectFit: "cover"}} />
  89. }
  90. </figure>
  91. </th>
  92. <td>
  93. <div className="flex justify-start gap-2">
  94. <label htmlFor="photo" className="btn btn-default">사진 변경</label>
  95. <input type="file" id="photo" accept="image/jpg,image/jpeg,image/png,image/gif" hidden onChange={handleChange} />
  96. {(preview || member?.photo) &&
  97. <button type="button" className="btn btn-default" onClick={handleRemove}>삭제</button>
  98. }
  99. </div>
  100. <p className="mt-3">
  101. 98x98 크기 이상, 4MB 이하의 사진이 권장됩니다. 이미지 파일을 사용하세요. 사진이 {process.env.NEXT_PUBLIC_SITE_NAME} 커뮤니티 가이드를 준수해야 합니다.
  102. </p>
  103. </td>
  104. </tr>
  105. </tbody>
  106. <tfoot>
  107. <tr>
  108. <td colSpan={2}>
  109. <div className="flex justify-center gap-2">
  110. <button type="submit" className="btn btn-submit">확인</button>
  111. <Link href="/profile" className="btn btn-default">취소</Link>
  112. </div>
  113. </td>
  114. </tr>
  115. </tfoot>
  116. </table>
  117. </form>
  118. </div>
  119. );
  120. }