Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
dca90d6
feat: 임시저장된 편지 삭제 기능 구현 (#84)
tifsy Mar 7, 2025
70051d3
feat: 롤링페이퍼 배포된 api로 연결 수정 (#85)
AAminha Mar 7, 2025
aeab0e6
feat : 알림 2차 기능 구현 (#81)
wldnjs990 Mar 7, 2025
ae17500
fix: 자잘한 이슈 수정 (#86)
nirii00 Mar 7, 2025
62d67cd
feat : 재사용 가능한 페이지네이션 구현 (#92)
wldnjs990 Mar 7, 2025
266d0e5
feat: 편지 공유 요청 수신 조회 기능 구현 (#90)
tifsy Mar 7, 2025
a0f576b
feat : 편지작성, 랜덤편지, 상세페이지 3차 기능구현 (#94)
wldnjs990 Mar 8, 2025
5b4d4ba
feat: 롤링페이퍼 추가 기능 구현 (#95)
AAminha Mar 9, 2025
f0b5b4f
feat: 임시저장 편지 조회 기능 완성 (#97)
tifsy Mar 9, 2025
4aced28
feat : 토스트 UI 구현 + 알림 페이지 코드 작업 90% 완료 + 실시간 알림, 편지 작성 예외처리에 토스트UI 연결…
wldnjs990 Mar 9, 2025
6917b3b
fix: reissue 문제, 내 편지함 data 최신화 문제 해결 (#100)
nirii00 Mar 9, 2025
a8144d5
feat : 알림 페이지 알림 확인 처리 안되던 현상 수정 + 신고페이지 4차 구현 (#101)
wldnjs990 Mar 9, 2025
58df399
feat: 공유 요청 상세 조회 기능 구현 (#106)
tifsy Mar 10, 2025
74ef07b
fix: 임시저장 편지 작성 페이지 이동 경로 문제 해결 (#109)
tifsy Mar 10, 2025
6059368
feat : 신고 등록 API + ReportModal 수정 (#111)
wldnjs990 Mar 10, 2025
2fb1cc6
fix: QA 반영 (#112)
nirii00 Mar 10, 2025
40c2021
feat : 알림 기능 구현 + QA (#114)
wldnjs990 Mar 10, 2025
f55da9f
feat : 임시저장 데이터 바인딩 및 임시저장 덮어씌우기 작업 완료 (#115)
wldnjs990 Mar 10, 2025
5fd9543
refactor: 2차 QA 반영 (#116)
tifsy Mar 10, 2025
b33b576
fix: 롤링페이퍼 QA 반영 (#120)
AAminha Mar 10, 2025
65f982b
feat : 알림 QA 수정 (#124)
wldnjs990 Mar 10, 2025
a441bd7
deploy : 배포 (#126)
wldnjs990 Mar 10, 2025
a1f9e59
fix: 3차 QA 반영 (#131)
nirii00 Mar 11, 2025
e2d90da
feat: 다크모드 구현 (#132)
nirii00 Mar 11, 2025
f748ef3
fix: 3차 QA 반영 (#133)
tifsy Mar 11, 2025
3b83467
feat : QA반영 (#134)
wldnjs990 Mar 11, 2025
561a24a
feat : 금칙어 수정, 활성/비활성, 삭제 api 연결 완료 (#141)
wldnjs990 Mar 11, 2025
90282b6
fix: 4차 QA 반영 (#143)
tifsy Mar 11, 2025
e8a9021
fix: 4차 QA 반영, 버그 수정 - 세빈 (#144)
nirii00 Mar 11, 2025
111ee61
Merge branch 'main' of https://github.com/prgrms-web-devcourse-final-…
wldnjs990 Mar 12, 2025
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
3 changes: 2 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import FilteredLetterManage from './pages/Admin/FilteredLetter';
import FilteringManage from './pages/Admin/Filtering';
import ReportManage from './pages/Admin/Report';
import AdminRollingPaper from './pages/Admin/RollingPaper';
import AdminRoute from './layouts/AdminRoute';
import AuthCallbackPage from './pages/Auth';
import Home from './pages/Home';
import Landing from './pages/Landing';
Expand Down Expand Up @@ -79,7 +80,7 @@ const App = () => {
</Route>
</Route>

<Route element={<PrivateRoute />}>
<Route element={<AdminRoute />}>
<Route path="admin" element={<AdminPage />}>
<Route path="report" element={<ReportManage />} />
<Route path="badwords" element={<FilteringManage />} />
Expand Down
23 changes: 18 additions & 5 deletions src/apis/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@ const postBadWords = async (badWordsRequest: BadWords) => {
}
};

// 내 상상대로 만든 필터링 단어 취소 버튼
const patchBadWordsUsed = async (badWordId: string) => {
const patchBadWordsUsed = async (badWordId: string, isUsed: string) => {
const reverseIsUsed = isUsed === 'true' ? false : true;

try {
const res = await client.patch(`/api/bad-words/${badWordId}/status`, { isUsed: false });
if (!res) throw new Error('검열 단어 삭제 도중 에러가 발생했습니다.');
const res = await client.patch(`/api/bad-words/${badWordId}/status`, { isUsed: reverseIsUsed });
if (!res) throw new Error('검열 활성화/비활성화 도중 에러가 발생했습니다.');
console.log(res);
return res;
} catch (error) {
Expand All @@ -78,7 +79,18 @@ const patchBadWordsUsed = async (badWordId: string) => {
const patchBadWords = async (badWordId: string, word: string) => {
try {
const res = await client.patch(`/api/bad-words/${badWordId}`, { word: word });
if (!res) throw new Error('검열 단어 삭제 도중 에러가 발생했습니다.');
if (!res) throw new Error('금칙어 수정중 에러가 발생했습니다.');
console.log(res);
return res;
} catch (error) {
console.error(error);
}
};

const deleteBadWords = async (id: string) => {
try {
const res = await client.delete(`/api/bad-words/${id}`);
if (!res) throw new Error('금칙어 삭제 도중 에러가 발생했습니다.');
console.log(res);
return res;
} catch (error) {
Expand All @@ -94,4 +106,5 @@ export {
postBadWords,
patchBadWordsUsed,
patchBadWords,
deleteBadWords,
};
Binary file added src/assets/images/background-dark.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/field-4-dark.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/assets/images/go-to-random-letter.png
Binary file not shown.
Binary file added src/assets/images/go-to-random-letter.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/home-left-mountain-dark.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/assets/images/home-left-mountain.png
Binary file not shown.
Binary file added src/assets/images/home-left-mountain.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/assets/images/home-right-mountain-bottom.png
Binary file not shown.
Binary file added src/assets/images/home-right-mountain-bottom.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/assets/images/home-right-mountain-top.png
Binary file not shown.
Binary file added src/assets/images/home-right-mountain-top.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/images/landing-dark.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/assets/images/landing.png
Binary file not shown.
Binary file added src/assets/images/landing.webp
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/BackgroundBottom.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BgItem from '@/assets/images/field-4.png';
import BgItemDark from '@/assets/images/field-4-dark.png';
import BgItemDark from '@/assets/images/field-4-dark.webp';

import BackgroundImageWrapper from './BackgroundImageWrapper';
import useThemeStore from '@/stores/themeStore';
Expand Down
1 change: 0 additions & 1 deletion src/hooks/useServerSentEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ export const useServerSentEvents = () => {
console.log(event);
const errorEvent = event as unknown as { status?: number };
if (errorEvent.status === 401) {
console.log('401로 인한 리이슈 작업 실행');
callReissue();
closeSSE();
reconnect = setTimeout(connectSSE, 5000);
Expand Down
29 changes: 29 additions & 0 deletions src/layouts/AdminRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useEffect } from 'react';
import { useNavigate, Outlet } from 'react-router';

import useAuthStore from '@/stores/authStore';

export default function AdminRoute() {
const isLoggedIn = useAuthStore((state) => state.isLoggedIn);
const isAdmin = useAuthStore((state) => state.isAdmin);
const navigate = useNavigate();

useEffect(() => {
if (!isLoggedIn) {
navigate('/login', { replace: true });
}
if (!isAdmin) {
navigate('/', { replace: true });
}
}, [isLoggedIn, navigate]);

if (!isLoggedIn || !isAdmin) {
return null;
}

return (
<>
<Outlet />
</>
);
}
4 changes: 2 additions & 2 deletions src/layouts/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ const Header = () => {
</button>
<div className="flex items-center gap-3">
{theme === 'light' ? (
<FlareRoundedIcon className="h-6 w-6 text-white" onClick={toggleTheme} />
) : (
<DarkModeOutlinedIcon className="h-6 w-6 text-white" onClick={toggleTheme} />
) : (
<FlareRoundedIcon className="h-6 w-6 text-white" onClick={toggleTheme} />
)}
<NotificationButton />
<Link to="/mypage">
Expand Down
9 changes: 4 additions & 5 deletions src/layouts/PrivateRoute.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect } from 'react';
import { useNavigate, Outlet } from 'react-router';

import useAuthStore from '@/stores/authStore';
Expand All @@ -9,17 +9,16 @@ export default function PrivateRoute() {
useServerSentEvents();
const isLoggedIn = useAuthStore((state) => state.isLoggedIn);
const navigate = useNavigate();
const [shouldRender, setShouldRender] = useState(false);

useServerSentEvents();

useEffect(() => {
if (!isLoggedIn) {
navigate('/login', { replace: true });
} else {
setShouldRender(true);
}
}, [isLoggedIn, navigate]);

if (!shouldRender) {
if (!isLoggedIn) {
return null;
}

Expand Down
2 changes: 1 addition & 1 deletion src/pages/Admin/components/AddInputButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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, res.data.data]);
setBadWords((cur) => [...cur, { ...res.data.data, isUsed: `${res.data.data.isUsed}` }]);
setAddInputShow(false);
}
};
Expand Down
58 changes: 53 additions & 5 deletions src/pages/Admin/components/FilterTextItem.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { patchBadWordsUsed } from '@/apis/admin';
import { deleteBadWords, patchBadWordsUsed } from '@/apis/admin';
import { DeleteIcon, PencilIcon, ToggleOff, ToggleOn } from '@/assets/icons';
import { useState } from 'react';
import PatchInput from './PatchInput';
import { twMerge } from 'tailwind-merge';

export default function FilterTextItem({
badWord,
Expand All @@ -12,8 +13,47 @@ export default function FilterTextItem({
}) {
const [patchInputShow, setPatchInputShow] = useState<boolean>(false);

const handleDeleteBadWords = async (id: string) => {
const res = await deleteBadWords(id);
if (res?.status === 200) {
setBadWords((cur) =>
cur.filter((e) => {
if (e.id === id) {
return null;
}
return e;
}),
);
}
};

const handlePatchBadWordsUsed = async (id: string, isUsed: string) => {
const res = await patchBadWordsUsed(id, isUsed);
if (res?.status === 200) {
setBadWords((cur) =>
cur.map((e) => {
if (e.id === id) {
let reverseIsUsed: string;
if (e.isUsed === 'true') {
reverseIsUsed = 'false';
} else {
reverseIsUsed = 'true';
}
return { ...e, isUsed: reverseIsUsed };
}
return e;
}),
);
}
};

const buttonStyle = twMerge(
`flex items-center gap-1.5 rounded-2xl px-4 py-1.5`,
badWord.isUsed === 'true' ? 'bg-primary-3' : 'bg-[#c1c1c1]',
);

return (
<span className="bg-primary-3 flex items-center gap-1.5 rounded-2xl px-4 py-1.5">
<span className={buttonStyle}>
{patchInputShow ? (
<PatchInput
badWordId={badWord.id}
Expand All @@ -28,12 +68,20 @@ export default function FilterTextItem({
<PencilIcon className="h-4 w-4" />
</button>

<button onClick={() => {}}>
<button
onClick={() => {
handleDeleteBadWords(badWord.id);
}}
>
<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 onClick={() => handlePatchBadWordsUsed(badWord.id, badWord.isUsed)}>
{badWord.isUsed === 'true' ? (
<ToggleOn className="h-5 w-5" />
) : (
<ToggleOff className="h-5 w-5" />
)}
</button>
</span>
);
Expand Down
8 changes: 6 additions & 2 deletions src/pages/Admin/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { twMerge } from 'tailwind-merge';
import { AlarmIcon, ArrowDownIcon } from '@/assets/icons';

import { ADMIN_MENU_LIST } from '../constants';
import useAuthStore from '@/stores/authStore';

export default function Sidebar() {
const location = useLocation();
const logout = useAuthStore((state) => state.logout);

return (
<section className="border-gray-10 flex w-65 shrink-0 flex-col border-r">
Expand All @@ -16,7 +18,7 @@ export default function Sidebar() {
</h1>
<section className="mt-2 flex flex-col px-5 py-4">
<h2 className="body-l-b py-2">현재 로그인 계정</h2>
<p className="body-l-r py-2">{'admin123@test.com'}</p>
<p className="body-l-r py-2">{'wl990@naver.com'}</p>
</section>
<hr className="border-gray-20 mx-2.5" />
<section className="flex flex-col py-5">
Expand Down Expand Up @@ -54,7 +56,9 @@ export default function Sidebar() {
</section>
<button className="mt-auto flex w-full items-center gap-3 px-5 py-3 hover:bg-amber-100">
<AlarmIcon className="text-gray-80 h-5 w-5" />
<span className="text-gray-80 body-l-m">로그아웃</span>
<span className="text-gray-80 body-l-m" onClick={() => logout()}>
로그아웃
</span>
</button>
</section>
);
Expand Down
15 changes: 14 additions & 1 deletion src/pages/Auth/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ const AuthCallbackPage = () => {
const logout = useAuthStore((state) => state.logout);
const setAccessToken = useAuthStore((state) => state.setAccessToken);
const setZipCode = useAuthStore((state) => state.setZipCode);
const setIsAdmin = useAuthStore((state) => state.setIsAdmin);
const navigate = useNavigate();
let accessToken = '';
let role = '';

const handleError = (error: unknown) => {
console.error('AuthCallback Error:', error);
Expand All @@ -32,6 +35,7 @@ const AuthCallbackPage = () => {

login();
if (userInfo.accessToken) setAccessToken(userInfo.accessToken);
accessToken = userInfo.accessToken;

console.log(redirectURL);

Expand All @@ -54,14 +58,23 @@ const AuthCallbackPage = () => {
if (!newAccessToken) throw new Error('Missing new access token');

setAccessToken(newAccessToken);
accessToken = newAccessToken;
}
break;

default:
navigate('/notFound');
return;
}
navigate(redirectURL === 'onboarding' ? '/onboarding' : '/');

role = JSON.parse(atob(accessToken.split('.')[1])).role;

if (role === 'ADMIN') {
setIsAdmin();
navigate('/admin');
} else {
navigate(redirectURL === 'onboarding' ? '/onboarding' : '/');
}
} catch (error) {
handleError(error);
}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Home/components/GoToLetterBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import goToLetterBoard from '@/assets/images/go-to-letter-board.png';

const GoToLetterBoard = () => {
return (
<div className="absolute right-[-56%] bottom-[28%] z-9 flex w-full md:right-[-42%]">
<div className="absolute right-[-56%] bottom-[28%] z-9 flex w-full md:right-[-42%] lg:right-[-20%]">
<div className="text-left">
<p className="text-gray-60 body-r mb-1 ml-2 dark:text-white">게시판</p>
<Link to="/board/letter">
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Home/components/GoToRandomLetter.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Link } from 'react-router';

import goToRandomLetter from '@/assets/images/go-to-random-letter.png';
import goToRandomLetter from '@/assets/images/go-to-random-letter.webp';

const GoToRandomLetter = () => {
return (
Expand All @@ -10,7 +10,7 @@ const GoToRandomLetter = () => {
고민편지 보러가기
</p>
<Link to={'/letter/random'}>
<img src={goToRandomLetter} alt="go to random letter" />
<img src={goToRandomLetter} alt="go to random letter" className="h-45 translate-x-5" />
</Link>
</div>
</>
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Home/components/HomeBackgroundLeft.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import homeLeftMountain from '@/assets/images/home-left-mountain.png';
import homeLeftMountainDark from '@/assets/images/home-left-mountain-dark.png';
import homeLeftMountain from '@/assets/images/home-left-mountain.webp';
import homeLeftMountainDark from '@/assets/images/home-left-mountain-dark.webp';

import BackgroundImageWrapper from '@/components/BackgroundImageWrapper';
import useThemeStore from '@/stores/themeStore';
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Home/components/HomeBackgroundRightBottom.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import homeRightMountainBottom from '@/assets/images/home-right-mountain-bottom.png';
import homeRightMountainBottomDark from '@/assets/images/home-right-mountain-bottom-dark.png';
import homeRightMountainBottom from '@/assets/images/home-right-mountain-bottom.webp';
import homeRightMountainBottomDark from '@/assets/images/home-right-mountain-bottom-dark.webp';
import BackgroundImageWrapper from '@/components/BackgroundImageWrapper';
import useThemeStore from '@/stores/themeStore';

Expand Down
4 changes: 2 additions & 2 deletions src/pages/Home/components/HomeBackgroundRightTop.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import homeRightMountainTop from '@/assets/images/home-right-mountain-top.png';
import homeRightMountainTopDark from '@/assets/images/home-right-mountain-top-dark.png';
import homeRightMountainTop from '@/assets/images/home-right-mountain-top.webp';
import homeRightMountainTopDark from '@/assets/images/home-right-mountain-top-dark.webp';

import BackgroundImageWrapper from '@/components/BackgroundImageWrapper';
import useThemeStore from '@/stores/themeStore';
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Home/components/HomeHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ const HomeHeader = () => {
<header className="fixed top-0 z-40 flex h-16 w-full max-w-150 items-center justify-end p-5">
<div className="flex items-center gap-3">
{theme === 'light' ? (
<FlareRoundedIcon className="h-6 w-6 text-white" onClick={toggleTheme} />
) : (
<DarkModeOutlinedIcon className="h-6 w-6 text-white" onClick={toggleTheme} />
) : (
<FlareRoundedIcon className="h-6 w-6 text-white" onClick={toggleTheme} />
)}
<NotificationButton />
<Link to="/mypage">
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Home/components/RandomCheer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from 'react';

import randomCheerBird from '@/assets/images/field-theme-asset-bird.png';
import randomCheerBirdDark from '@/assets/images/field-theme-asset-bird-dark.png';
import randomCheerBirdDark from '@/assets/images/field-theme-asset-bird-dark.webp';

import { RANDOM_CHEER_LIST } from '../constants';
import useThemeStore from '@/stores/themeStore';
Expand Down
4 changes: 2 additions & 2 deletions src/pages/Landing/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { useState, useEffect } from 'react';
import { Navigate, useNavigate } from 'react-router';
import { twMerge } from 'tailwind-merge';

import LandingImg from '@/assets/images/landing.png';
import LandingImgDark from '@/assets/images/landing-dark.png';
import LandingImg from '@/assets/images/landing.webp';
import LandingImgDark from '@/assets/images/landing-dark.webp';
import useAuthStore from '@/stores/authStore';
import useThemeStore from '@/stores/themeStore';

Expand Down
Loading