-
Notifications
You must be signed in to change notification settings - Fork 2
feat : 관리자 & 신고 2차 구현 #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6e77acd
907a72a
3e653e1
1e61675
d920609
0b1d297
775a265
56c2eff
cd10957
94c1414
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,39 @@ | ||
| import client from './client'; | ||
|
|
||
| const getReports = async ( | ||
| setReports: React.Dispatch<React.SetStateAction<Report[]>>, | ||
| queryString: string = '', | ||
| callBack?: () => void, | ||
| ) => { | ||
| const postReports = async (postReportRequest: PostReportRequest) => { | ||
| try { | ||
| const res = await client.get(`/api/reports${queryString}`); | ||
| setReports(res.data.data); | ||
| if (callBack) callBack(); | ||
| console.log(res.data.data); | ||
| const res = await client.post(`/api/reports`, postReportRequest); | ||
| if (res.status === 200) { | ||
| return res; | ||
| } | ||
| } catch (error) { | ||
| console.error(error); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| }; | ||
|
|
||
| const getReports = async (reportQueryString: ReportQueryString) => { | ||
| try { | ||
| const queryParams = new URLSearchParams(); | ||
| if (reportQueryString.reportType !== null) | ||
| queryParams.append('reportType', reportQueryString.reportType); | ||
| if (reportQueryString.status !== null) queryParams.append('status', reportQueryString.status); | ||
| if (reportQueryString.page !== null) queryParams.append('page', reportQueryString.page); | ||
| if (reportQueryString.size !== null) queryParams.append('size', reportQueryString.size); | ||
|
|
||
| const queryStrings = queryParams.toString(); | ||
| const res = await client.get(`/api/reports?${queryStrings}`); | ||
| if (!res) throw new Error('신고 목록 데이터 조회 도중 에러가 발생했습니다.'); | ||
| console.log(res); | ||
| return res; | ||
| } catch (error) { | ||
| console.error(error); | ||
| } | ||
| }; | ||
|
|
||
| const patchReport = async (reportId: number, reportRequest: ReportRequest) => { | ||
| const patchReport = async (reportId: number, patchReportRequest: PatchReportRequest) => { | ||
| try { | ||
| console.log(`/api/reports/${reportId}`, reportRequest); | ||
| const res = await client.patch(`/api/reports/${reportId}`, reportRequest); | ||
| console.log(`/api/reports/${reportId}`, patchReportRequest); | ||
| const res = await client.patch(`/api/reports/${reportId}`, patchReportRequest); | ||
| console.log(res); | ||
| } catch (error) { | ||
| console.error(error); | ||
|
|
@@ -61,4 +76,4 @@ const patchBadWords = async ( | |
| } | ||
| }; | ||
|
|
||
| export { getReports, patchReport, getBadWords, postBadWords, patchBadWords }; | ||
| export { postReports, getReports, patchReport, getBadWords, postBadWords, patchBadWords }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,57 +1,91 @@ | ||
| import { useState } from 'react'; | ||
| import { useEffect, useState } from 'react'; | ||
| import { twMerge } from 'tailwind-merge'; | ||
|
|
||
| import { postReports } from '@/apis/admin'; | ||
|
|
||
| import ConfirmModal from './ConfirmModal'; | ||
| import TextareaField from './TextareaField'; | ||
|
|
||
| interface ReportModalProps { | ||
| reportType: ReportType; | ||
| letterId: number | null; | ||
| onClose: () => void; | ||
| } | ||
|
|
||
| const REPORT_REASON = ['욕설', '비방', '폭언', '성희롱', '기타']; | ||
| interface ReportReason { | ||
| name: string; | ||
| type: Reason; | ||
| } | ||
| const REPORT_REASON: ReportReason[] = [ | ||
| { name: '욕설', type: 'ABUSE' }, | ||
| { name: '비방', type: 'DEFAMATION' }, | ||
| { name: '폭언', type: 'THREATS' }, | ||
| { name: '성희롱', type: 'HARASSMENT' }, | ||
| { name: '기타', type: 'ETC' }, | ||
| ]; | ||
|
|
||
| const ReportModal = ({ onClose }: ReportModalProps) => { | ||
| const [selected, setSelected] = useState(''); | ||
| const [additionalReason, setAdditionalReason] = useState(''); | ||
| const ReportModal = ({ reportType, letterId, onClose }: ReportModalProps) => { | ||
| const [postReportRequest, setPostReportRequest] = useState<PostReportRequest>({ | ||
| reportType: reportType, | ||
| reasonType: '', | ||
| reason: '', | ||
| letterId: letterId, | ||
| }); | ||
|
|
||
| const handleReasonClick = (reason: string) => { | ||
| if (selected === reason) setSelected(''); | ||
| else setSelected(reason); | ||
| const handleReasonClick = (reason: Reason) => { | ||
| if (postReportRequest.reasonType === reason) | ||
| setPostReportRequest((cur) => ({ ...cur, reasonType: '' })); | ||
| else setPostReportRequest((cur) => ({ ...cur, reasonType: reason })); | ||
| }; | ||
|
|
||
| const handleSubmit = () => { | ||
| onClose(); | ||
| const handleSubmit = async () => { | ||
| const res = await postReports(postReportRequest); | ||
| if (res?.status === 200) { | ||
| alert('신고 처리되었습니다.'); | ||
| onClose(); | ||
| } else if (res?.status === 409) { | ||
| alert('신고한 이력이 있습니다.'); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네넹 토스트UI를 만들어보려 합니다!
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. alert를 toastUI로 대체한다는 말씀이신거죠?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네넹 토스트UI 만들고나면 대부분 alert들을 교체할 거 같습니다! |
||
| onClose(); | ||
| } | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| if (!postReportRequest.letterId) { | ||
| alert('신고 모달을 여는 과정에서 오류가 발생했습니다. 새로고침을 눌러주세요'); | ||
| onClose(); | ||
| } | ||
| }); | ||
|
|
||
| return ( | ||
| <ConfirmModal | ||
| title="신고 사유를 선택해주세요" | ||
| description="신고한 게시물은 관리자 검토 후 처리됩니다." | ||
| cancelText="취소하기" | ||
| confirmText="제출하기" | ||
| confirmDisabled={selected === ''} | ||
| confirmDisabled={postReportRequest.reasonType === ''} | ||
| onCancel={onClose} | ||
| onConfirm={handleSubmit} | ||
| > | ||
| <section className="my-6 flex flex-wrap gap-x-2.5 gap-y-2"> | ||
| {REPORT_REASON.map((reason) => ( | ||
| {REPORT_REASON.map((reason, idx) => ( | ||
| <button | ||
| key={idx} | ||
| type="button" | ||
| className={twMerge( | ||
| 'body-m rounded-full bg-white px-5 py-1.5 text-black', | ||
| selected === reason && 'bg-primary-2', | ||
| postReportRequest.reasonType === reason.type && 'bg-primary-2', | ||
| )} | ||
| onClick={() => handleReasonClick(reason)} | ||
| onClick={() => handleReasonClick(reason.type)} | ||
| > | ||
| {reason} | ||
| {reason.name} | ||
| </button> | ||
| ))} | ||
| </section> | ||
| <TextareaField | ||
| rows={3} | ||
| placeholder="이곳을 눌러 추가 사유를 작성해주세요" | ||
| value={additionalReason} | ||
| onChange={(e) => setAdditionalReason(e.target.value)} | ||
| value={postReportRequest.reason} | ||
| onChange={(e) => setPostReportRequest((cur) => ({ ...cur, reason: e.target.value }))} | ||
| /> | ||
| </ConfirmModal> | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ import { getBadWords } from '@/apis/admin'; | |
| import { AddIcon, AlarmIcon, CancelIcon } from '@/assets/icons'; | ||
|
|
||
| import AddInputButton from './components/AddInputButton'; | ||
| import AdminPageTitle from './components/AdminPageTitle'; | ||
| import WrapperFrame from './components/WrapperFrame'; | ||
| import WrapperTitle from './components/WrapperTitle'; | ||
|
|
||
|
|
@@ -15,37 +16,40 @@ export default function FilteringManage() { | |
| getBadWords(setBadWords); | ||
| }, []); | ||
| return ( | ||
| <WrapperFrame> | ||
| <WrapperTitle title="필터링 단어 설정" Icon={AlarmIcon} /> | ||
| <div className="mt-5 flex w-full flex-wrap gap-4"> | ||
| {badWords.map((badWord, idx) => { | ||
| return ( | ||
| <span | ||
| key={idx} | ||
| className="flex items-center gap-1.5 rounded-2xl bg-[#C1C1C1] px-4 py-1.5" | ||
| > | ||
| {badWord.word} | ||
| <button> | ||
| <CancelIcon className="h-4 w-4" /> | ||
| <> | ||
| <AdminPageTitle>검열 관리 / 필터링 단어 설정</AdminPageTitle> | ||
| <WrapperFrame> | ||
| <WrapperTitle title="필터링 단어" Icon={AlarmIcon} /> | ||
| <div className="mt-5 flex w-full flex-wrap gap-4"> | ||
| {badWords.map((badWord, idx) => { | ||
| return ( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사소한 거지만
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 맞다 그 방법이 더 좋은거 같네요!! 다음에 바꾸겠습니당🥕🥕 |
||
| <span | ||
| key={idx} | ||
| className="flex items-center gap-1.5 rounded-2xl bg-[#C1C1C1] px-4 py-1.5" | ||
| > | ||
| {badWord.word} | ||
| <button> | ||
| <CancelIcon className="h-4 w-4" /> | ||
| </button> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 클릭했을 때의 동작이 필요할 것 같습니다! 추후 개발 예정이실까요?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네넹 필터링 파트는 아직 api 준비중인 것도 있어서 잠시 미뤄뒀습니다! |
||
| </span> | ||
| ); | ||
| })} | ||
| {addInputShow ? ( | ||
| <AddInputButton setAddInputShow={setAddInputShow} setBadWords={setBadWords} /> | ||
| ) : ( | ||
| <span className="flex items-center gap-1.5 rounded-2xl bg-[#C1C1C1] px-4 py-1.5"> | ||
| 추가하기 | ||
| <button | ||
| onClick={() => { | ||
| setAddInputShow(true); | ||
| }} | ||
| > | ||
| <AddIcon className="h-4 w-4" /> | ||
| </button> | ||
| </span> | ||
| ); | ||
| })} | ||
| {addInputShow ? ( | ||
| <AddInputButton setAddInputShow={setAddInputShow} setBadWords={setBadWords} /> | ||
| ) : ( | ||
| <span className="flex items-center gap-1.5 rounded-2xl bg-[#C1C1C1] px-4 py-1.5"> | ||
| 추가하기 | ||
| <button | ||
| onClick={() => { | ||
| setAddInputShow(true); | ||
| }} | ||
| > | ||
| <AddIcon className="h-4 w-4" /> | ||
| </button> | ||
| </span> | ||
| )} | ||
| </div> | ||
| </WrapperFrame> | ||
| )} | ||
| </div> | ||
| </WrapperFrame> | ||
| </> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 알기로는 axios가 status 가200번대를 제외하고는 무조건 error를 반환하도록 하는 걸로 알고 있는데, 200만 특정해서 리턴한 이유가 있을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아하 큰 뜻은 없는데 좀 더 true임을 명확하게 표현해주는거 같아서..? 썻습니다 ㅎ