Skip to content

Commit d37bf9a

Browse files
committed
Merge branch 'develop'
2 parents f83a879 + 4cb559f commit d37bf9a

File tree

194 files changed

+4717
-2659
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

194 files changed

+4717
-2659
lines changed

src/App.tsx

Lines changed: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1+
/* eslint-disable */
12
import { useEffect, ReactNode, Suspense } from 'react';
23
import {
34
Routes,
45
Route,
56
useLocation,
7+
Navigate,
68
} from 'react-router-dom';
79
import AuthPage from 'pages/Auth/AuthPage';
810
import LoginPage from 'pages/Auth/LoginPage';
911
import BoardPage from 'pages/BoardPage';
1012
import StorePage from 'pages/Store/StorePage';
11-
import NoticePage from 'pages/Notice/NoticePage';
12-
import NoticeListPage from 'pages/Notice/NoticeListPage';
13-
import NoticeDetailPage from 'pages/Notice/NoticeDetailPage';
13+
import ArticlesPage from 'pages/Articles/ArticlesPage';
14+
import ArticleListPage from 'pages/Articles/ArticleListPage';
15+
import ArticlesDetailPage from 'pages/Articles/ArticlesDetailPage';
1416
import Toast from 'components/common/Toast';
1517
import LogPage from 'components/common/LogPage';
1618
import SignupPage from 'pages/Auth/SignupPage';
@@ -27,27 +29,39 @@ import TimetablePage from 'pages/TimetablePage/MainTimetablePage';
2729
import CafeteriaPage from 'pages/Cafeteria';
2830
import MetaHelmet from 'components/common/MetaHelmet';
2931
import ModifyInfoPage from 'pages/Auth/ModifyInfoPage';
30-
import PrivateRoute from 'components/common/PrivateRoute';
3132
import AddReviewPage from 'pages/StoreReviewPage/AddReviewPage';
3233
import EditReviewPage from 'pages/StoreReviewPage/EditReviewPage';
3334
import ReviewReportingPage from 'pages/Store/StoreDetailPage/Review/components/ReviewReporting';
3435
import ModifyTimetablePage from 'pages/TimetablePage/ModifyTimetablePage';
3536
import PageNotFound from 'pages/Error/PageNotFound';
3637
import PolicyPage from 'pages/PolicyPage';
3738
import ROUTES from 'static/routes';
39+
import LostItemWritePage from 'pages/Articles/LostItemWritePage';
40+
import LostItemDetailPage from 'pages/Articles/LostItemDetailPage';
41+
import useTokenState from 'utils/hooks/state/useTokenState';
3842

39-
interface HelmetWrapperProps {
43+
interface WrapperProps {
4044
title: string;
4145
element: ReactNode;
46+
needAuth?: boolean;
4247
}
4348

44-
function HelmetWrapper({ title, element }: HelmetWrapperProps) {
49+
function Wrapper({
50+
title,
51+
element,
52+
needAuth = false, // 로그인이 필요한 라우트
53+
}: WrapperProps) {
4554
const location = useLocation();
55+
const token = useTokenState();
4656

4757
useEffect(() => {
48-
document.title = title;
58+
document.title = `코인 - ${title}`;
4959
}, [title, location]);
5060

61+
if (needAuth && !token) {
62+
return <Navigate replace to={ROUTES.Main()} />;
63+
}
64+
5165
return (
5266
<>
5367
<MetaHelmet title={title} />
@@ -64,47 +78,43 @@ function App() {
6478
<>
6579
<Routes>
6680
<Route path={ROUTES.Main()} element={<BoardPage />}>
67-
<Route path={ROUTES.Timetable()} element={<HelmetWrapper title="코인 - 시간표" element={<TimetablePage />} />} />
68-
<Route path={ROUTES.TimetableRegular({ isLink: false })} element={<HelmetWrapper title="코인 - 시간표 수정" element={<ModifyTimetablePage />} />} />
69-
<Route path={ROUTES.TimetableDirect({ isLink: false })} element={<HelmetWrapper title="코인 - 시간표 수정" element={<ModifyTimetablePage />} />} />
70-
<Route path={ROUTES.Main()} element={<HelmetWrapper title="코인 - 한기대 커뮤니티" element={<IndexPage />} />} />
71-
<Route path={ROUTES.Store()} element={<HelmetWrapper title="코인 - 상점" element={<StorePage />} />} />
72-
<Route path={ROUTES.BenefitStore()} element={<HelmetWrapper title="코인 - 전화 혜택" element={<StoreBenefitPage />} />} />
73-
<Route path={ROUTES.StoreDetail({ isLink: false })} element={<HelmetWrapper title="코인 - 상점 상세" element={<StoreDetailPage />} />} />
74-
<Route path={ROUTES.BusRoute()} element={<HelmetWrapper title="코인 - 버스" element={<BusRoutePage />} />} />
75-
<Route path={ROUTES.BusCourse()} element={<HelmetWrapper title="코인 - 버스" element={<BusCoursePage />} />} />
76-
<Route path={ROUTES.Cafeteria()} element={<HelmetWrapper title="코인 - 식단" element={<CafeteriaPage />} />} />
77-
<Route path={ROUTES.PrivatePolicy()} element={<HelmetWrapper title="코인 - 개인정보 처리방침" element={<PolicyPage />} />} />
78-
<Route path={ROUTES.BoardNotice()} element={<HelmetWrapper title="코인 - 공지사항" element={<NoticePage />} />}>
79-
<Route path={ROUTES.BoardNotice()} element={<NoticeListPage />} />
80-
<Route path={ROUTES.BoardNoticeDetail({ isLink: false })} element={<HelmetWrapper title="코인 - 공지사항 상세" element={<NoticeDetailPage />} />} />
81+
<Route index element={<Wrapper title="한기대 커뮤니티" element={<IndexPage />} />} />
82+
<Route path={ROUTES.PrivatePolicy()} element={<Wrapper title="개인정보 처리방침" element={<PolicyPage />} />} />
83+
84+
<Route path={ROUTES.Timetable()} element={<Wrapper title="시간표" element={<TimetablePage />} />} />
85+
<Route path={ROUTES.TimetableRegular({ isLink: false })} element={<Wrapper title="시간표 수정" element={<ModifyTimetablePage />} />} />
86+
<Route path={ROUTES.TimetableDirect({ isLink: false })} element={<Wrapper title="시간표 수정" element={<ModifyTimetablePage />} />} />
87+
88+
<Route path={ROUTES.Store()} element={<Wrapper title="상점" element={<StorePage />} />} />
89+
<Route path={ROUTES.BenefitStore()} element={<Wrapper title="전화 혜택" element={<StoreBenefitPage />} />} />
90+
<Route path={ROUTES.StoreDetail({ isLink: false })} element={<Wrapper title="상점 상세" element={<StoreDetailPage />} />} />
91+
<Route path={ROUTES.Review({ isLink: false })} element={<Wrapper needAuth title="상점 리뷰" element={<AddReviewPage />} />} />
92+
<Route path={ROUTES.ReviewEdit({ isLink: false })} element={<Wrapper needAuth title="상점 리뷰" element={<EditReviewPage />} />} />
93+
<Route path={ROUTES.ReviewReport({ isLink: false })} element={<Wrapper needAuth title="리뷰 신고" element={<ReviewReportingPage />} />} />
94+
<Route path={ROUTES.Room()} element={<Wrapper title="복덕방" element={<RoomPage />} />} />
95+
<Route path={ROUTES.RoomDetail({ isLink: false })} element={<Wrapper title="복덕방 상세" element={<RoomDetailPage />} />} />
96+
97+
<Route path={ROUTES.BusRoute()} element={<Wrapper title="버스" element={<BusRoutePage />} />} />
98+
<Route path={ROUTES.BusCourse()} element={<Wrapper title="버스" element={<BusCoursePage />} />} />
99+
<Route path={ROUTES.Cafeteria()} element={<Wrapper title="식단" element={<CafeteriaPage />} />} />
100+
<Route path={ROUTES.Articles()} element={<Wrapper title="공지사항" element={<ArticlesPage />} />}>
101+
<Route index element={<ArticleListPage />} />
102+
<Route path={ROUTES.ArticlesDetail({ isLink: false })} element={<Wrapper title="공시사항 상세" element={<ArticlesDetailPage />} />} />
103+
<Route path={ROUTES.LostItemDetail({ isLink: false })} element={<Wrapper title="분실물 상세" element={<LostItemDetailPage />} />} />
81104
</Route>
82-
<Route path={ROUTES.Room()} element={<HelmetWrapper title="코인 - 복덕방" element={<RoomPage />} />} />
83-
<Route path={ROUTES.RoomDetail({ isLink: false })} element={<HelmetWrapper title="코인 - 복덕방 상세" element={<RoomDetailPage />} />} />
84-
<Route path={ROUTES.CampusInfo()} element={<HelmetWrapper title="코인 - 교내 시설물 정보" element={<CampusInfo />} />} />
105+
<Route path={ROUTES.LostItemFound()} element={<Wrapper needAuth title="분실물 글쓰기" element={<LostItemWritePage />} />} />
106+
<Route path={ROUTES.LostItemLost()} element={<Wrapper needAuth title="분실물 글쓰기" element={<LostItemWritePage />} />} />
107+
<Route path={ROUTES.CampusInfo()} element={<Wrapper title="교내 시설물 정보" element={<CampusInfo />} />} />
85108
</Route>
86-
<Route
87-
path={ROUTES.Auth()}
88-
element={<PrivateRoute requireAuthentication={false} element={<AuthPage />} />}
89-
>
90-
<Route index element={<HelmetWrapper title="코인 - 로그인" element={<LoginPage />} />} />
91-
<Route path={ROUTES.AuthSignup()} element={<HelmetWrapper title="코인 - 회원가입" element={<SignupPage />} />} />
92-
<Route path={ROUTES.AuthFindPW()} element={<HelmetWrapper title="코인 - 비밀번호 찾기" element={<FindPasswordPage />} />} />
93-
</Route>
94-
<Route path={ROUTES.Main()} element={<BoardPage />}>
95-
<Route path={ROUTES.Review({ isLink: false })} element={<PrivateRoute requireAuthentication element={<HelmetWrapper title="코인 - 상점 리뷰" element={<AddReviewPage />} />} />} />
96-
<Route path={ROUTES.ReviewEdit({ isLink: false })} element={<PrivateRoute requireAuthentication element={<HelmetWrapper title="코인 - 상점 리뷰" element={<EditReviewPage />} />} />} />
97-
<Route path={ROUTES.ReviewReport({ isLink: false })} element={<PrivateRoute requireAuthentication element={<HelmetWrapper title="코인 - 리뷰 신고" element={<ReviewReportingPage />} />} />} />
98109

99-
</Route>
100110
<Route path={ROUTES.Auth()} element={<AuthPage />}>
101-
<Route path={ROUTES.AuthModifyInfo()} element={<PrivateRoute requireAuthentication element={<HelmetWrapper title="코인 - 유저 정보변경" element={<ModifyInfoPage />} />} />} />
111+
<Route index element={<Wrapper title="로그인" element={<LoginPage />} />} />
112+
<Route path={ROUTES.AuthSignup()} element={<Wrapper title="회원가입" element={<SignupPage />} />} />
113+
<Route path={ROUTES.AuthFindPW()} element={<Wrapper title="비밀번호 찾기" element={<FindPasswordPage />} />} />
114+
<Route path={ROUTES.AuthModifyInfo()} element={<Wrapper needAuth title="유저 정보변경" element={<ModifyInfoPage />} />} />
102115
</Route>
103116

104-
<Route path={ROUTES.Webview()}>
105-
<Route path={ROUTES.WebviewCampusInfo()} element={<HelmetWrapper title="코인 - 교내 시설물 정보" element={<CampusInfo />} />} />
106-
</Route>
107-
<Route path={ROUTES.NotFound()} element={<HelmetWrapper title="코인 - 404 Not Found" element={<PageNotFound />} />} />
117+
<Route path={ROUTES.NotFound()} element={<Wrapper title="404 Not Found" element={<PageNotFound />} />} />
108118
</Routes>
109119
<Toast />
110120
</>

src/api/articles/APIDetail.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { APIRequest, HTTP_METHOD } from 'interfaces/APIRequest';
2+
import {
3+
ArticlesResponse,
4+
ArticleResponse,
5+
HotArticlesResponse,
6+
SingleLostItemArticleResponseDTO,
7+
LostItemArticlesResponseDTO,
8+
LostItemResponse,
9+
LostItemArticlesRequestDTO,
10+
} from './entity';
11+
12+
export class GetArticles<R extends ArticlesResponse> implements APIRequest<R> {
13+
method = HTTP_METHOD.GET;
14+
15+
path: string;
16+
17+
response!: R;
18+
19+
constructor(page: string | undefined) {
20+
this.path = `/articles?page=${page}&limit=10`;
21+
}
22+
}
23+
24+
export class GetArticle<R extends ArticleResponse> implements APIRequest<R> {
25+
method = HTTP_METHOD.GET;
26+
27+
path: string;
28+
29+
response!: R;
30+
31+
constructor(id: string | undefined) {
32+
this.path = `/articles/${id}`;
33+
}
34+
}
35+
36+
export class GetHotArticles<R extends HotArticlesResponse> implements APIRequest<R> {
37+
method = HTTP_METHOD.GET;
38+
39+
path = '/articles/hot';
40+
41+
response!: R;
42+
}
43+
44+
export class GetLostItemArticles<R extends LostItemArticlesResponseDTO> implements APIRequest<R> {
45+
method = HTTP_METHOD.GET;
46+
47+
path = '/articles/lost-item';
48+
49+
response!: R;
50+
}
51+
52+
export class GetSingleLostItemArticle<R
53+
extends SingleLostItemArticleResponseDTO> implements APIRequest<R> {
54+
method = HTTP_METHOD.GET;
55+
56+
path: string;
57+
58+
response!: R;
59+
60+
constructor(id: number) {
61+
this.path = `/articles/lost-item/${id}`;
62+
}
63+
}
64+
65+
export class PostLostItemArticles<R extends LostItemResponse> implements APIRequest<R> {
66+
method = HTTP_METHOD.POST;
67+
68+
path = '/articles/lost-item';
69+
70+
response!: R;
71+
72+
auth = true;
73+
74+
constructor(public authorization: string, public data: LostItemArticlesRequestDTO) { }
75+
}
76+
77+
export class DeleteLostItemArticle<R extends LostItemResponse> implements APIRequest<R> {
78+
method = HTTP_METHOD.DELETE;
79+
80+
path: string;
81+
82+
response!: R;
83+
84+
auth = true;
85+
86+
constructor(public authorization: string, id: number) {
87+
this.path = `/articles/lost-item/${id}`;
88+
}
89+
}

src/api/articles/entity.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { APIResponse } from 'interfaces/APIResponse';
2+
3+
export interface GetArticlesRequest {
4+
boardId: string
5+
page: string
6+
}
7+
8+
export interface Article {
9+
id: number
10+
board_id: number
11+
title: string
12+
author: string
13+
hit: number
14+
registered_at: string // yyyy-MM-dd 아우누리에 게시판에 등록된 날짜
15+
updated_at: string // yyyy-MM-dd HH:mm:ss 이하 형식 동일
16+
}
17+
18+
export interface Attachment {
19+
id: 1,
20+
name: string,
21+
url: string,
22+
created_at: string,
23+
updated_at: string,
24+
}
25+
26+
export interface PaginationInfo {
27+
total_count: number
28+
current_count: number
29+
total_page: number
30+
current_page: number
31+
}
32+
33+
export interface PaginatedResponse<T> extends PaginationInfo, APIResponse {
34+
articles: T[];
35+
}
36+
37+
export type ArticlesResponse = PaginatedResponse<Article>;
38+
export type ArticlesSearchResponse = PaginatedResponse<Article>;
39+
40+
export interface ArticleResponse extends Article, APIResponse {
41+
content: string;
42+
attachments: Attachment[];
43+
prev_id: number;
44+
next_id: number;
45+
}
46+
47+
export interface HotArticle extends Article { }
48+
49+
export type HotArticlesResponse = HotArticle[];
50+
51+
// GET /articles/lost-item
52+
interface LostItemArticleForGetDTO {
53+
id: number;
54+
board_id: number;
55+
category: string;
56+
found_place: string;
57+
found_date: string;
58+
content: string;
59+
author: string;
60+
registered_at: string;
61+
updated_at: string;
62+
}
63+
64+
export interface LostItemArticlesResponseDTO extends APIResponse {
65+
articles: LostItemArticleForGetDTO[];
66+
total_count: number;
67+
current_count: number;
68+
total_page: number;
69+
current_page: number;
70+
}
71+
72+
interface ImageDTO {
73+
id: number;
74+
image_url: string;
75+
}
76+
77+
export interface SingleLostItemArticleResponseDTO extends APIResponse {
78+
id: number;
79+
board_id: number;
80+
category: string;
81+
found_place: string;
82+
found_date: string;
83+
content: string;
84+
author: string;
85+
images: ImageDTO[];
86+
prev_id: number | null;
87+
next_id: number | null;
88+
registered_at: string; // yyyy-MM-dd
89+
updated_at: string; // yyyy-MM-dd HH:mm:ss
90+
}
91+
92+
export interface LostItemResponse extends APIResponse {
93+
id: number;
94+
board_id: number;
95+
category: string;
96+
found_place: string;
97+
found_date: string;
98+
content: string;
99+
author: string;
100+
images: ImageDTO[];
101+
prev_id: number | null;
102+
next_id: number | null;
103+
registered_at: string;
104+
updated_at: string;
105+
}
106+
107+
// POST /articles/lost-item
108+
interface LostItemArticleForPostDTO {
109+
category: string;
110+
found_place: string;
111+
found_date: string; // yyyy-MM-dd
112+
content: string;
113+
images: string[];
114+
}
115+
116+
export interface LostItemArticlesRequestDTO {
117+
articles: Array<LostItemArticleForPostDTO>;
118+
}

src/api/articles/index.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import APIClient from 'utils/ts/apiClient';
2+
import {
3+
GetArticles,
4+
GetHotArticles,
5+
GetArticle,
6+
GetLostItemArticles,
7+
GetSingleLostItemArticle,
8+
DeleteLostItemArticle,
9+
PostLostItemArticles,
10+
} from './APIDetail';
11+
12+
export const getArticles = APIClient.of(GetArticles);
13+
14+
export const getArticle = APIClient.of(GetArticle);
15+
16+
export const getHotArticles = APIClient.of(GetHotArticles);
17+
18+
export const getLostItemArticles = APIClient.of(GetLostItemArticles);
19+
20+
export const getSingleLostItemArticle = APIClient.of(GetSingleLostItemArticle);
21+
22+
export const postLostItemArticle = APIClient.of(PostLostItemArticles);
23+
24+
export const deleteLostItemArticle = APIClient.of(DeleteLostItemArticle);

src/api/index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
export * as abTest from './abTest';
12
export * as auth from './auth';
2-
export * as store from './store';
3-
export * as dept from './dept';
4-
export * as timetable from './timetable';
53
export * as bus from './bus';
6-
export * as notice from './notice';
7-
export * as room from './room';
84
export * as cafeteria from './cafeteria';
95
export * as coopshop from './coopshop';
6+
export * as dept from './dept';
7+
export * as articles from './articles';
108
export * as review from './review';
11-
export * as abTest from './abTest';
9+
export * as room from './room';
10+
export * as store from './store';
11+
export * as timetable from './timetable';
12+
export * as uploadFile from './uploadFile';

0 commit comments

Comments
 (0)