Report.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. 'use client';
  2. import './style.scss';
  3. import { useEffect, useState, useCallback, useRef } from 'react';
  4. import Loading from '@/app/component/Loading';
  5. import { Button } from '@/components/ui/button';
  6. import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
  7. import { fetchReport } from '@/lib/api/forum/post';
  8. import { throwError } from '@/lib/utils/client';
  9. import { ReportType } from '@/constants/forum';
  10. import PostReportRequest from '@/dtos/request/forum/post/postReportRequest';
  11. type Props = {
  12. isEnable: boolean;
  13. open: boolean;
  14. onChange: (_: boolean) => void;
  15. onComplete: (_: boolean) => void;
  16. postID: number;
  17. memberID?: number;
  18. }
  19. export default function Report({ isEnable, open, onChange, onComplete, postID, memberID }: Props)
  20. {
  21. const [error, setError] = useState<string>('');
  22. const [loading, setLoading] = useState<boolean>(false);
  23. const [form, setForm] = useState<{
  24. type: string;
  25. reason: string;
  26. }>({
  27. type: '',
  28. reason: ''
  29. });
  30. const typeRef = useRef<HTMLSelectElement>(null);
  31. const reasonRef = useRef<HTMLTextAreaElement>(null);
  32. const reportTypeLabels: Record<ReportType, string> = {
  33. [ReportType.None]: '신고 유형을 선택하세요.',
  34. [ReportType.Abuse]: '욕설',
  35. [ReportType.Obscene]: '음란',
  36. [ReportType.Illegal]: '불법',
  37. [ReportType.Impersonation]: '신분 사칭',
  38. [ReportType.CashTrade]: '현금거래 유도',
  39. [ReportType.SpamAd]: '스팸/광고',
  40. [ReportType.Flood]: '도배',
  41. [ReportType.PersonalLeak]: '개인정보 노출',
  42. [ReportType.Other]: '기타'
  43. };
  44. useEffect(() => {
  45. if (error) {
  46. alert(error);
  47. setError('');
  48. }
  49. }, [error]);
  50. const handleChange = (e: React.ChangeEvent<HTMLSelectElement|HTMLTextAreaElement>) => {
  51. const { name, value } = e.target;
  52. setForm((prev) => ({
  53. ...prev,
  54. [name]: value
  55. }));
  56. };
  57. const handleSubmit = useCallback(async () => {
  58. if (!form.type) {
  59. alert('신고 유형을 선택하세요.');
  60. typeRef.current?.focus();
  61. return;
  62. }
  63. if (!form.reason) {
  64. alert('신고 내용을 입력해주세요.');
  65. reasonRef.current?.focus();
  66. return;
  67. }
  68. if (!memberID) {
  69. alert('로그인 후 이용해주세요.');
  70. return;
  71. }
  72. setLoading(true);
  73. onChange(false);
  74. try {
  75. const res = await fetchReport({
  76. postID,
  77. type: Number(form.type) as ReportType,
  78. reason: form.reason
  79. } as PostReportRequest);
  80. if (res.ok) {
  81. alert('신고가 접수되었습니다.');
  82. setForm({ type: '', reason: '' });
  83. onComplete(true);
  84. } else {
  85. throwError(res);
  86. }
  87. } catch (err: any) {
  88. alert(err.message);
  89. } finally {
  90. setLoading(false);
  91. }
  92. }, [form, postID, memberID, onChange]);
  93. if (!isEnable) {
  94. return null;
  95. }
  96. return (
  97. <>
  98. {loading && <Loading />}
  99. <Dialog open={open} onOpenChange={onChange}>
  100. <DialogContent className='w-3xs sm:max-w-md'>
  101. <DialogHeader>
  102. <DialogTitle>게시글 신고</DialogTitle>
  103. </DialogHeader>
  104. <div id='report' className='flex flex-col items-center gap-4'>
  105. <select ref={typeRef} name='type' value={form.type} title='신고 유형' onChange={handleChange}>
  106. {Object.entries(reportTypeLabels).map(([key, label]) => (
  107. <option key={key} value={key}>
  108. {label}
  109. </option>
  110. ))}
  111. </select>
  112. <textarea ref={reasonRef} name='reason' rows={4} value={form.reason} title='신고 내용' onChange={handleChange} maxLength={500} placeholder='신고 내용을 구체적으로 입력해주세요.(500자 이하)'></textarea>
  113. <Button type='button' variant='outline' className='hover:bg-blue-500 hover:text-white sm:w-24' onClick={handleSubmit}>
  114. 신고하기
  115. </Button>
  116. </div>
  117. </DialogContent>
  118. </Dialog>
  119. </>
  120. );
  121. }