Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions src/apis/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ const patchReport = async (reportId: number, patchReportRequest: PatchReportRequ
};

// badwords
const getBadWords = async (setBadWords: React.Dispatch<React.SetStateAction<BadWords[]>>) => {
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);
}
Expand All @@ -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,
};
17 changes: 16 additions & 1 deletion src/apis/write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ const postLetter = async (data: LetterRequest) => {
if (!res) throw new Error('편지 전송과정에서 오류가 발생했습니다.');
return res;
} catch (error) {
const errorWithStatus = error as unknown as { status: number };
console.error(error);
return errorWithStatus;
}
};

Expand Down Expand Up @@ -42,4 +44,17 @@ 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) {
const errorWithStatus = error as unknown as { status: number };
console.error(error);
return errorWithStatus;
}
};

export { postLetter, postFirstReply, getPrevLetter, postTemporarySave, postTemporaryLetter };
4 changes: 4 additions & 0 deletions src/assets/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -54,4 +56,6 @@ export {
DeleteIcon,
CancelIcon,
PencilIcon,
ToggleOff,
ToggleOn,
};
3 changes: 3 additions & 0 deletions src/assets/icons/toggle-off.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/toggle-on.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/ToastItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function ToastItem({ toastObj, index }: { toastObj: ToastObj; ind

const TOAST_POSITION = {
Top: 'top-20',
Bottom: 'bottom-5',
Bottom: 'bottom-20',
};

const animation = `toast-blink ${toastObj.time}s ease-in-out forwards`;
Expand Down
32 changes: 16 additions & 16 deletions src/pages/Admin/Filtering.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
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<BadWords[]>([]);
const [badWords, setBadWords] = useState<BadWordsData[]>([]);
const [addInputShow, setAddInputShow] = useState<boolean>(false);

const handleGetBadWords = async () => {
const res = await getBadWords();
if (res?.status === 200) {
setBadWords(res.data.data);
} else {
console.log('검열 조회 오류 발생');
}
};

useEffect(() => {
getBadWords(setBadWords);
handleGetBadWords();
}, []);
return (
<>
<AdminPageTitle>검열 관리 / 필터링 단어 설정</AdminPageTitle>
<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" />
</button>
</span>
);
{badWords.map((badWord) => {
return <FilterTextItem key={badWord.id} badWord={badWord} setBadWords={setBadWords} />;
})}
{addInputShow ? (
<AddInputButton setAddInputShow={setAddInputShow} setBadWords={setBadWords} />
) : (
<span className="flex items-center gap-1.5 rounded-2xl bg-[#C1C1C1] px-4 py-1.5">
<span className="bg-primary-3 flex items-center gap-1.5 rounded-2xl px-4 py-1.5">
추가하기
<button
onClick={() => {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Admin/Report.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function ReportManage() {
reportType: null,
status: 'PENDING',
page: '1',
size: '1',
size: '8',
});

const handleGetReports = async (reportQueryString: ReportQueryString) => {
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Admin/components/AddInputButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function AddInputButton({
setBadWords,
}: {
setAddInputShow: React.Dispatch<React.SetStateAction<boolean>>;
setBadWords: React.Dispatch<React.SetStateAction<BadWords[]>>;
setBadWords: React.Dispatch<React.SetStateAction<BadWordsData[]>>;
}) {
const [inputText, setInputText] = useState<BadWords>({ word: '' });
const inputRef = useRef<HTMLInputElement>(null);
Expand All @@ -23,7 +23,7 @@ export default function AddInputButton({
if (inputText.word === '') return setAddInputShow(false);
const res = await postBadWords(inputText);
if (res?.status === 200) {
setBadWords((cur) => [...cur, inputText]);
setBadWords((cur) => [...cur, res.data.data]);
setAddInputShow(false);
}
};
Expand Down
40 changes: 40 additions & 0 deletions src/pages/Admin/components/FilterTextItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { patchBadWordsUsed } from '@/apis/admin';
import { DeleteIcon, PencilIcon, ToggleOff, ToggleOn } from '@/assets/icons';
import { useState } from 'react';
import PatchInput from './PatchInput';

export default function FilterTextItem({
badWord,
setBadWords,
}: {
badWord: BadWordsData;
setBadWords: React.Dispatch<React.SetStateAction<BadWordsData[]>>;
}) {
const [patchInputShow, setPatchInputShow] = useState<boolean>(false);

return (
<span className="bg-primary-3 flex items-center gap-1.5 rounded-2xl px-4 py-1.5">
{patchInputShow ? (
<PatchInput
badWordId={badWord.id}
setPatchInputShow={setPatchInputShow}
setBadWords={setBadWords}
/>
) : (
badWord.word
)}

<button onClick={() => setPatchInputShow(true)}>
<PencilIcon className="h-4 w-4" />
</button>

<button onClick={() => {}}>
<DeleteIcon className="h-5 w-5" />
</button>

<button onClick={() => patchBadWordsUsed(badWord.id)}>
{true ? <ToggleOn className="h-5 w-5" /> : <ToggleOff className="h-5 w-5" />}
</button>
</span>
);
}
75 changes: 75 additions & 0 deletions src/pages/Admin/components/PatchInput.tsx
Original file line number Diff line number Diff line change
@@ -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<React.SetStateAction<boolean>>;
setBadWords: React.Dispatch<React.SetStateAction<BadWordsData[]>>;
}) {
const [inputText, setInputText] = useState<BadWords>({ word: '' });
const inputRef = useRef<HTMLInputElement>(null);

const handleInputWidth = (event: React.FormEvent<HTMLInputElement>) => {
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 (
<>
<input
ref={inputRef}
type="text"
className="w-10 min-w-10"
onInput={(e) => {
handleInputWidth(e);
}}
onChange={(e) => {
setInputText(() => ({ word: e.target.value }));
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handlePatchBadWords();
}
}}
/>
<button
onClick={() => {
handlePatchBadWords();
}}
>
<AddIcon className="h-4 w-4" />
</button>
</>
);
}
19 changes: 11 additions & 8 deletions src/pages/Admin/components/ReportListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,17 @@ export default function ReportListItem({
<span className="admin-list-set basis-2/10">{`${formattedDate} ${formattedTime}`}</span>
<span className="admin-list-set basis-3/10">{reasonList[report.reasonType]}</span>
</div>
<button
onClick={(e) => {
e.stopPropagation();
setModalOpen((cur) => !cur);
}}
>
<KebobMenuIcon className="h-5 w-5" />
</button>
{report.status === 'PENDING' && (
<button
onClick={(e) => {
e.stopPropagation();
setModalOpen((cur) => !cur);
}}
>
<KebobMenuIcon className="h-5 w-5" />
</button>
)}

{modalOpen && <MenuModal modalContents={modalContents} setModalOpen={setModalOpen} />}
</div>
);
Expand Down
9 changes: 8 additions & 1 deletion src/pages/Notifications/components/SendingModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import LetterWrapper from '@/components/LetterWrapper';
import ModalOverlay from '@/components/ModalOverlay';
import { useNavigate } from 'react-router';
import SendOutlinedIcon from '@mui/icons-material/SendOutlined';

export default function SendingModal({
isOpenSendingModal,
Expand All @@ -21,7 +22,13 @@ export default function SendingModal({
<div className="caption-r flex flex-col gap-2">
<h2 className="body-b mb-3">편지 도착</h2>
<span>편지는 작성된 시점으로 1시간 이후에 도착합니다.</span>
<span>남은시간은 홈 화면의 편지 도착 시간 버튼을 눌러 확인 가능합니다.</span>
<div className="flex flex-col">
<div className="flex items-center gap-1">
<span>남은시간은 홈 화면의 편지 도착 시간 버튼 </span>
<SendOutlinedIcon fontSize="small" />
</div>
<span>을 눌러 확인 가능합니다.</span>
</div>
<button
className="body-b mt-3 flex items-center justify-center"
onClick={() => navigate('/')}
Expand Down
Loading