From 8fb9adc6ac8743879e71a4f84f35bea008ed3cee Mon Sep 17 00:00:00 2001 From: "wl990@naver.com" Date: Tue, 11 Mar 2025 14:10:18 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat=20:=20=ED=8E=B8=EC=A7=80=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=9E=84=EC=8B=9C=EC=A0=80=EC=9E=A5=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=80=20=EC=A0=84=EC=9A=A9=20=ED=8E=B8=EC=A7=80=20=EC=A0=84?= =?UTF-8?q?=EC=86=A1=20api=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/write.ts | 13 +++++++- src/pages/Write/LetterEditor.tsx | 55 ++++++++++++++++++++++---------- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/apis/write.ts b/src/apis/write.ts index 2a93094..8a66848 100644 --- a/src/apis/write.ts +++ b/src/apis/write.ts @@ -42,4 +42,15 @@ const postTemporarySave = async (data: TemporaryRequest) => { } }; -export { postLetter, postFirstReply, getPrevLetter, postTemporarySave }; +const postTemporaryLetter = async (data: TemporaryRequest) => { + console.log('Temporary request', data); + try { + const res = await client.post('/api/letters', data); + if (!res) throw new Error('편지 전송과정에서 오류가 발생했습니다.'); + return res; + } catch (error) { + console.error(error); + } +}; + +export { postLetter, postFirstReply, getPrevLetter, postTemporarySave, postTemporaryLetter }; diff --git a/src/pages/Write/LetterEditor.tsx b/src/pages/Write/LetterEditor.tsx index 1668801..b27ccab 100644 --- a/src/pages/Write/LetterEditor.tsx +++ b/src/pages/Write/LetterEditor.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router'; import { twMerge } from 'tailwind-merge'; -import { postFirstReply, postLetter, postTemporarySave } from '@/apis/write'; +import { postFirstReply, postLetter, postTemporaryLetter, postTemporarySave } from '@/apis/write'; import BackButton from '@/components/BackButton'; import ConfirmModal from '@/components/ConfirmModal'; import WritePageButton from '@/pages/Write/components/WritePageButton'; @@ -42,6 +42,7 @@ export default function LetterEditor({ if (res?.status === 200) { setSend(true); setStep('category'); + setToastActive({ title: '편지 전송을 완료했습니다.', toastType: 'Success' }); } else { setToastActive({ title: '전송중 오류가 발생했습니다.', toastType: 'Error' }); } @@ -54,6 +55,33 @@ export default function LetterEditor({ console.log(prevLetter); setSend(true); setStep('category'); + setToastActive({ title: '편지 전송을 완료했습니다.', toastType: 'Success' }); + } else { + setToastActive({ title: '전송중 오류가 발생했습니다.', toastType: 'Error' }); + } + }; + + const handlePostTemporarySave = async () => { + if (!letterId) return alert('임시저장중 오류 발생'); + const requestLetterId = location.state?.draft.letterId || null; + // MEMO : 임시저장 전송 방식 : 최초임시저장은 letterId : null, 임시저장 업데이트는 letterId : location state로 받아오는 임시저장편지의 letterId값 + const temporaryRequest: TemporaryRequest = { ...letterRequest, letterId: requestLetterId }; + const res = await postTemporarySave(temporaryRequest); + if (res?.status === 200) { + console.log(res); + setToastActive({ title: '임시저장을 완료했습니다.', toastType: 'Success' }); + navigate('/'); + } else { + setToastActive({ title: '임시저장에 실패했습니다.', toastType: 'Error' }); + } + }; + + const handleTemporaryLetter = async (temporaryRequest: TemporaryRequest) => { + const res = await postTemporaryLetter(temporaryRequest); + if (res?.status === 200) { + setSend(true); + setStep('category'); + setToastActive({ title: '편지 전송을 완료했습니다.', toastType: 'Success' }); } else { setToastActive({ title: '전송중 오류가 발생했습니다.', toastType: 'Error' }); } @@ -77,21 +105,6 @@ export default function LetterEditor({ } }, [prevLetter, setLetterRequest, isReply]); - const handlePostTemporarySave = async () => { - if (!letterId) return alert('임시저장중 오류 발생'); - const requestLetterId = location.state?.draft.letterId || null; - // MEMO : 임시저장 전송 방식 : 최초임시저장은 letterId : null, 임시저장 업데이트는 letterId : location state로 받아오는 임시저장편지의 letterId값 - const temporaryRequest: TemporaryRequest = { ...letterRequest, letterId: requestLetterId }; - const res = await postTemporarySave(temporaryRequest); - if (res?.status === 200) { - console.log(res); - setToastActive({ title: '임시저장을 완료했습니다.', toastType: 'Success' }); - navigate('/'); - } else { - setToastActive({ title: '임시저장에 실패했습니다.', toastType: 'Error' }); - } - }; - return (
{isTemporaryConfirmModal && ( @@ -128,7 +141,15 @@ export default function LetterEditor({ console.log(firstReplyRequest); handlePostFirstReply(firstReplyRequest); } else { - handlePostReply(letterRequest); + if (location.state?.isDraft) { + const temporaryRequest: TemporaryRequest = { + ...letterRequest, + letterId: location.state.draft.letterId, + }; + handleTemporaryLetter(temporaryRequest); + } else { + handlePostReply(letterRequest); + } } queryClient.invalidateQueries({ queryKey: ['mailBox'] }); queryClient.invalidateQueries({ queryKey: ['mailBoxDetail'] }); From 7c8cbc40621e9b0c291952a50cad7d4f1d854257 Mon Sep 17 00:00:00 2001 From: "wl990@naver.com" Date: Tue, 11 Mar 2025 14:11:01 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat=20:=20=ED=8E=B8=EC=A7=80=20=EA=B8=88?= =?UTF-8?q?=EC=B9=99=EC=96=B4=20api=20=EC=97=B0=EA=B2=B0=20=EB=B0=91?= =?UTF-8?q?=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/admin.ts | 29 ++++++- src/assets/icons/index.ts | 4 + src/assets/icons/toggle-off.svg | 3 + src/assets/icons/toggle-on.svg | 3 + src/pages/Admin/Filtering.tsx | 32 ++++---- src/pages/Admin/components/AddInputButton.tsx | 4 +- src/pages/Admin/components/FilterTextItem.tsx | 40 ++++++++++ src/pages/Admin/components/PatchInput.tsx | 75 +++++++++++++++++++ src/types/admin.d.ts | 4 + 9 files changed, 172 insertions(+), 22 deletions(-) create mode 100644 src/assets/icons/toggle-off.svg create mode 100644 src/assets/icons/toggle-on.svg create mode 100644 src/pages/Admin/components/FilterTextItem.tsx create mode 100644 src/pages/Admin/components/PatchInput.tsx diff --git a/src/apis/admin.ts b/src/apis/admin.ts index f768098..8508d49 100644 --- a/src/apis/admin.ts +++ b/src/apis/admin.ts @@ -41,11 +41,12 @@ const patchReport = async (reportId: number, patchReportRequest: PatchReportRequ }; // badwords -const getBadWords = async (setBadWords: React.Dispatch>) => { +const getBadWords = async () => { try { const res = await client.get('/api/bad-words'); - setBadWords(res.data.data); + if (!res) throw new Error('금칙어 조회 도중 에러가 발생했습니다.'); console.log(res); + return res; } catch (error) { console.error(error); } @@ -63,14 +64,34 @@ const postBadWords = async (badWordsRequest: BadWords) => { }; // 내 상상대로 만든 필터링 단어 취소 버튼 -const patchBadWords = async (badWordId: number) => { +const patchBadWordsUsed = async (badWordId: string) => { try { const res = await client.patch(`/api/bad-words/${badWordId}/status`, { isUsed: false }); if (!res) throw new Error('검열 단어 삭제 도중 에러가 발생했습니다.'); console.log(res); + return res; + } catch (error) { + console.error(error); + } +}; + +const patchBadWords = async (badWordId: string, word: string) => { + try { + const res = await client.patch(`/api/bad-words/${badWordId}`, { word: word }); + if (!res) throw new Error('검열 단어 삭제 도중 에러가 발생했습니다.'); + console.log(res); + return res; } catch (error) { console.error(error); } }; -export { postReports, getReports, patchReport, getBadWords, postBadWords, patchBadWords }; +export { + postReports, + getReports, + patchReport, + getBadWords, + postBadWords, + patchBadWordsUsed, + patchBadWords, +}; diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts index 42da171..1f952b5 100644 --- a/src/assets/icons/index.ts +++ b/src/assets/icons/index.ts @@ -25,6 +25,8 @@ import SnowIcon from './snow.svg?react'; import StampIcon from './stamp.svg?react'; import ThermostatIcon from './thermostat.svg?react'; import WarmIcon from './warm.svg?react'; +import ToggleOff from './toggle-off.svg?react'; +import ToggleOn from './toggle-on.svg?react'; export { AddIcon, @@ -54,4 +56,6 @@ export { DeleteIcon, CancelIcon, PencilIcon, + ToggleOff, + ToggleOn, }; diff --git a/src/assets/icons/toggle-off.svg b/src/assets/icons/toggle-off.svg new file mode 100644 index 0000000..e75f80e --- /dev/null +++ b/src/assets/icons/toggle-off.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/toggle-on.svg b/src/assets/icons/toggle-on.svg new file mode 100644 index 0000000..e896baa --- /dev/null +++ b/src/assets/icons/toggle-on.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/pages/Admin/Filtering.tsx b/src/pages/Admin/Filtering.tsx index 38ec96e..8f59b21 100644 --- a/src/pages/Admin/Filtering.tsx +++ b/src/pages/Admin/Filtering.tsx @@ -1,19 +1,29 @@ import { useEffect, useState } from 'react'; -import { getBadWords } from '@/apis/admin'; +import { getBadWords, patchBadWordsUsed } 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'; +import FilterTextItem from './components/FilterTextItem'; export default function FilteringManage() { - const [badWords, setBadWords] = useState([]); + const [badWords, setBadWords] = useState([]); const [addInputShow, setAddInputShow] = useState(false); + const handleGetBadWords = async () => { + const res = await getBadWords(); + if (res?.status === 200) { + setBadWords(res.data.data); + } else { + console.log('검열 조회 오류 발생'); + } + }; + useEffect(() => { - getBadWords(setBadWords); + handleGetBadWords(); }, []); return ( <> @@ -21,23 +31,13 @@ export default function FilteringManage() {
- {badWords.map((badWord, idx) => { - return ( - - {badWord.word} - - - ); + {badWords.map((badWord) => { + return ; })} {addInputShow ? ( ) : ( - + 추가하기 + + + + + + ); +} diff --git a/src/pages/Admin/components/PatchInput.tsx b/src/pages/Admin/components/PatchInput.tsx new file mode 100644 index 0000000..f48cd9c --- /dev/null +++ b/src/pages/Admin/components/PatchInput.tsx @@ -0,0 +1,75 @@ +import { useEffect, useRef, useState } from 'react'; + +import { patchBadWords } from '@/apis/admin'; +import { AddIcon } from '@/assets/icons'; + +export default function PatchInput({ + badWordId, + setPatchInputShow, + setBadWords, +}: { + badWordId: string; + setPatchInputShow: React.Dispatch>; + setBadWords: React.Dispatch>; +}) { + const [inputText, setInputText] = useState({ word: '' }); + const inputRef = useRef(null); + + const handleInputWidth = (event: React.FormEvent) => { + const target = event.target as HTMLInputElement; + target.style.width = '50px'; + target.style.width = `${target.scrollWidth}px`; + }; + + const handlePatchBadWords = async () => { + if (inputText.word === '') return setPatchInputShow(false); + const res = await patchBadWords(badWordId, inputText.word); + if (res?.status === 200) { + setBadWords((cur) => + cur.map((e) => { + if (e.id === badWordId) { + return { ...e, word: inputText.word }; + } + return e; + }), + ); + console.log('일단 수정했음 api 새로 업데이트되면 바인딩'); + setPatchInputShow(false); + } + }; + + useEffect(() => { + const inputElement = inputRef.current; + if (inputElement) { + inputElement.focus(); + } + }, []); + + return ( + <> + { + handleInputWidth(e); + }} + onChange={(e) => { + setInputText(() => ({ word: e.target.value })); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + handlePatchBadWords(); + } + }} + /> + + + ); +} diff --git a/src/types/admin.d.ts b/src/types/admin.d.ts index 4ef77be..4efa604 100644 --- a/src/types/admin.d.ts +++ b/src/types/admin.d.ts @@ -73,6 +73,10 @@ interface BadWords { word: string; } +interface BadWordsData extends BadWords { + id: string; +} + // interface ModalContents { title: string; From 414a3834869cc84e03a06ea0ee90f262730e45fb Mon Sep 17 00:00:00 2001 From: "wl990@naver.com" Date: Tue, 11 Mar 2025 14:17:53 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat=20:=20=EA=B4=80=EB=A6=AC=EC=9E=90=20?= =?UTF-8?q?=EC=8B=A0=EA=B3=A0=20=EA=B4=80=EB=A6=AC=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=8B=A0=EA=B3=A0=20=EC=B2=98=EB=A6=AC=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EA=B0=80=20PENDING=EC=9D=B4=20=EC=95=84=EB=8B=8C=20?= =?UTF-8?q?=EA=B0=92=EB=93=A4=20=EC=8B=A0=EA=B3=A0=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Admin/Report.tsx | 2 +- src/pages/Admin/components/ReportListItem.tsx | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/pages/Admin/Report.tsx b/src/pages/Admin/Report.tsx index d107ef6..103b385 100644 --- a/src/pages/Admin/Report.tsx +++ b/src/pages/Admin/Report.tsx @@ -28,7 +28,7 @@ export default function ReportManage() { reportType: null, status: 'PENDING', page: '1', - size: '1', + size: '8', }); const handleGetReports = async (reportQueryString: ReportQueryString) => { diff --git a/src/pages/Admin/components/ReportListItem.tsx b/src/pages/Admin/components/ReportListItem.tsx index fec8aab..51877d2 100644 --- a/src/pages/Admin/components/ReportListItem.tsx +++ b/src/pages/Admin/components/ReportListItem.tsx @@ -64,14 +64,17 @@ export default function ReportListItem({ {`${formattedDate} ${formattedTime}`} {reasonList[report.reasonType]}
- + {report.status === 'PENDING' && ( + + )} + {modalOpen && }
); From 0f7a82a5916bc86bf10a7ac55119c37b6e741ef9 Mon Sep 17 00:00:00 2001 From: "wl990@naver.com" Date: Tue, 11 Mar 2025 14:20:06 +0900 Subject: [PATCH 4/6] =?UTF-8?q?design=20:=20=EB=9E=9C=EB=8D=A4=ED=8E=B8?= =?UTF-8?q?=EC=A7=80=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=83=88=EB=A1=9C?= =?UTF-8?q?=EA=B3=A0=EC=B9=A8=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EC=A1=B0=EC=A0=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/RandomLetters/components/MatchingSelect.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/RandomLetters/components/MatchingSelect.tsx b/src/pages/RandomLetters/components/MatchingSelect.tsx index 61b2b73..e8cdfe1 100644 --- a/src/pages/RandomLetters/components/MatchingSelect.tsx +++ b/src/pages/RandomLetters/components/MatchingSelect.tsx @@ -42,7 +42,7 @@ export default function MatchingSelect({ <>
+
+ + + + ); +} diff --git a/src/pages/Notifications/index.tsx b/src/pages/Notifications/index.tsx index 257453e..4e96ac1 100644 --- a/src/pages/Notifications/index.tsx +++ b/src/pages/Notifications/index.tsx @@ -8,6 +8,7 @@ import NotificationItem from './components/NotificationItem'; import WarningModal from './components/WarningModal'; import SendingModal from './components/SendingModal'; import useNotificationStore from '@/stores/notificationStore'; +import ShareModal from './components/ShareModal'; const NotificationsPage = () => { const navigate = useNavigate(); @@ -19,6 +20,7 @@ const NotificationsPage = () => { const [isOpenWarningModal, setIsOpenWarningModal] = useState(false); const [isOpenSendingModal, setIsOpenSendingModal] = useState(false); + const [isOpenShareModal, setIsOpenShareModal] = useState(false); const [reportContent, setReportContent] = useState(''); @@ -35,7 +37,7 @@ const NotificationsPage = () => { if (typeof content === 'string') setReportContent(content); } if (alarmType === 'SHARE') { - navigate(`/board/letter/${content}`, { state: { isShareLetterPreview: true } }); + setIsOpenShareModal(true); } if (alarmType === 'POSTED') { navigate(`/board/letter/${content}`); @@ -99,6 +101,7 @@ const NotificationsPage = () => { isOpenSendingModal={isOpenSendingModal} setIsOpenSendingModal={setIsOpenSendingModal} /> +
알림