page.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. 'use client';
  2. import './style.scss';
  3. import Link from 'next/link';
  4. import { useState, useEffect } from 'react';
  5. import { useConfigContext } from '@/contexts/configProvider';
  6. import { useMemberContext } from '@/contexts/memberProvider';
  7. import useErrorAlert from '@/hooks/useErrorAlert';
  8. import { ChangeIntroRequest } from '@/types/request/account';
  9. import { fetchApi } from '@/lib/utils/client';
  10. import Loading from '@/app/component/Loading';
  11. import Editor from '@/app/component/Editor';
  12. export default function ChangeIntro()
  13. {
  14. const config = useConfigContext();
  15. const { member, setMember } = useMemberContext();
  16. const { setError } = useErrorAlert();
  17. const [loading, setLoading] = useState<boolean>(false);
  18. const [newIntro, setNewIntro] = useState<string|null>('');
  19. useEffect(() => {
  20. if (member?.intro && !newIntro) {
  21. const intro = member.intro.replace(/<img\b[^>]*?\bsrc=[''](\/[^'']*)['']/gi, (match, srcPath) => {
  22. return match.replace(srcPath, process.env.API_URL + srcPath);
  23. });
  24. setNewIntro(intro);
  25. }
  26. }, [member, newIntro]);
  27. const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
  28. e.preventDefault();
  29. if (!member) {
  30. return;
  31. }
  32. setLoading(true);
  33. fetchApi('/api/mypage/intro', {
  34. method: 'POST',
  35. body: { Intro: newIntro } as ChangeIntroRequest
  36. }).then(() => {
  37. member.intro = newIntro;
  38. setMember(member);
  39. setNewIntro(newIntro);
  40. localStorage.setItem('member', JSON.stringify(member));
  41. alert('자기소개가 변경되었습니다.');
  42. }).catch(err => {
  43. setError(err.message);
  44. setNewIntro(member.intro);
  45. }).finally(() => {
  46. setLoading(false);
  47. });
  48. }
  49. const handleDelete = async () => {
  50. if (confirm("자기소개를 삭제하시겠습니까?")) {
  51. if (!member || !member.intro) {
  52. return;
  53. }
  54. setLoading(true);
  55. fetchApi('/api/mypage/intro', { method: 'DELETE' }).then(() => {
  56. member.intro = null;
  57. setMember(member);
  58. localStorage.setItem('member', JSON.stringify(member));
  59. alert("자기소개가 삭제되었습니다.");
  60. }).catch(err => {
  61. setError(err.message);
  62. }).finally(() => {
  63. setLoading(false);
  64. setNewIntro('');
  65. });
  66. }
  67. };
  68. return (
  69. <>
  70. <div id='changeIntro'>
  71. { loading && <Loading /> }
  72. <h1>자기소개 변경</h1>
  73. <form method='post' acceptCharset='utf-8' autoComplete='off' onSubmit={handleSubmit}>
  74. <table className='table-fixed w-full max-xl:w-full xl:w-[810px] border-none'>
  75. <caption>
  76. 커뮤니티 프로필에 표시되는 자기소개를 설정할 수 있습니다.
  77. </caption>
  78. <tbody>
  79. <tr>
  80. <td className='w-full'>
  81. <Editor data={newIntro ?? ''} onChange={setNewIntro} />
  82. </td>
  83. </tr>
  84. </tbody>
  85. <tfoot>
  86. <tr>
  87. <td colSpan={3}>
  88. <div className='flex justify-center gap-2'>
  89. <button type='submit' className='btn btn-submit'>확인</button>
  90. { member?.intro && (
  91. <button type="button" className="btn btn-delete" onClick={handleDelete}>삭제</button>
  92. )}
  93. <Link href='/profile' className='btn btn-default'>취소</Link>
  94. </div>
  95. </td>
  96. </tr>
  97. </tfoot>
  98. </table>
  99. </form>
  100. <br />
  101. <dl className='max-xl:w-full xl:w-[810px]'>
  102. <dt hidden>&nbsp;</dt>
  103. <dd>
  104. <ol>
  105. <li>자기소개는 최대 1000자 이내로 입력 가능합니다.</li>
  106. <li>부적절한 내용은 별도의 고지 없이 삭제될 수 있습니다.</li>
  107. {(config.account.changeIntroDay ?? 0) > 0 && <li>한마디 변경 주기는 {config.account.changeIntroDay}일입니다.</li>}
  108. </ol>
  109. </dd>
  110. </dl>
  111. </div>
  112. </>
  113. );
  114. }