= [
5 | { group: MEISTER.직업기초능력, scoreType: SCORE.AVG },
6 | { group: MEISTER.직업기초능력, scoreType: SCORE.MY },
7 | { group: MEISTER.직업기초능력, scoreType: SCORE.MAX },
8 |
9 | { group: MEISTER.전문기술역량, scoreType: SCORE.AVG },
10 | { group: MEISTER.전문기술역량, scoreType: SCORE.MY },
11 | { group: MEISTER.전문기술역량, scoreType: SCORE.MAX },
12 |
13 | { group: MEISTER.인성직업의식, scoreType: SCORE.AVG },
14 | { group: MEISTER.인성직업의식, scoreType: SCORE.MY },
15 | { group: MEISTER.인성직업의식, scoreType: SCORE.MAX },
16 |
17 | { group: MEISTER.인문학적소양, scoreType: SCORE.AVG },
18 | { group: MEISTER.인문학적소양, scoreType: SCORE.MY },
19 | { group: MEISTER.인문학적소양, scoreType: SCORE.MAX },
20 |
21 | { group: MEISTER.외국어능력, scoreType: SCORE.AVG },
22 | { group: MEISTER.외국어능력, scoreType: SCORE.MY },
23 | { group: MEISTER.외국어능력, scoreType: SCORE.MAX },
24 | ];
25 |
26 | export default meisterChartData;
27 |
--------------------------------------------------------------------------------
/src/templates/meister/assets/data/meisterList.data.ts:
--------------------------------------------------------------------------------
1 | import { theme } from "@/styles";
2 | import { MeisterKeyType } from "../../types";
3 | import { MEISTER } from "../../constants";
4 |
5 | const meisterListData: Array<{
6 | name: MeisterKeyType;
7 | color: string;
8 | }> = [
9 | { name: MEISTER.전문기술역량, color: theme.primary_mint },
10 | { name: MEISTER.인성직업의식, color: theme.primary_red },
11 | { name: MEISTER.인문학적소양, color: theme.primary_yellow },
12 | { name: MEISTER.외국어능력, color: theme.primary_blue },
13 | ];
14 |
15 | export default meisterListData;
16 |
--------------------------------------------------------------------------------
/src/templates/meister/assets/data/meisterVariable.data.ts:
--------------------------------------------------------------------------------
1 | const meisterVariableData = [
2 | "직업 기초 능력",
3 | "전문 기술 역량",
4 | "인성 직업 의식",
5 | "인문학적 소양",
6 | "외국어 능력",
7 | ];
8 |
9 | export default meisterVariableData;
10 |
--------------------------------------------------------------------------------
/src/templates/meister/assets/data/radarChartVariable.data.ts:
--------------------------------------------------------------------------------
1 | import { MEISTER } from "../../constants";
2 |
3 | const radarChartVariableData = [
4 | { key: MEISTER.직업기초능력, label: "직업기초능력" },
5 | { key: MEISTER.전문기술역량, label: "전문기술역량" },
6 | { key: MEISTER.인성직업의식, label: "인성/직업의식" },
7 | { key: MEISTER.인문학적소양, label: "인문학적 소양" },
8 | { key: MEISTER.외국어능력, label: "외국어 능력" },
9 | ];
10 |
11 | export default radarChartVariableData;
12 |
--------------------------------------------------------------------------------
/src/templates/meister/constants/index.ts:
--------------------------------------------------------------------------------
1 | export { default as MEISTER } from "./meister.constant";
2 | export { default as SCORE } from "./score.constant";
3 |
--------------------------------------------------------------------------------
/src/templates/meister/constants/meister.constant.ts:
--------------------------------------------------------------------------------
1 | const MEISTER = {
2 | 직업기초능력: "basicJobSkills",
3 | 전문기술역량: "professionalTech",
4 | 인성직업의식: "workEthic",
5 | 인문학적소양: "humanities",
6 | 외국어능력: "foreignScore",
7 | } as const;
8 |
9 | export default MEISTER;
10 |
--------------------------------------------------------------------------------
/src/templates/meister/constants/score.constant.ts:
--------------------------------------------------------------------------------
1 | const SCORE = {
2 | AVG: "avg",
3 | MAX: "max",
4 | MY: "meister",
5 | } as const;
6 |
7 | export default SCORE;
8 |
--------------------------------------------------------------------------------
/src/templates/meister/context/buttonSwitch.context.ts:
--------------------------------------------------------------------------------
1 | import { atom } from "jotai";
2 |
3 | const buttonSwitchContext = atom(false);
4 |
5 | export default buttonSwitchContext;
6 |
--------------------------------------------------------------------------------
/src/templates/meister/context/index.ts:
--------------------------------------------------------------------------------
1 | export { default as studentNumberContext } from "./studentNumber.context";
2 | export { default as buttonSwitchContext } from "./buttonSwitch.context";
3 |
--------------------------------------------------------------------------------
/src/templates/meister/context/studentNumber.context.ts:
--------------------------------------------------------------------------------
1 | import { atom } from "jotai";
2 |
3 | const studentNumberContext = atom("");
4 |
5 | export default studentNumberContext;
6 |
--------------------------------------------------------------------------------
/src/templates/meister/helpers/getMeisterChapter.helper.ts:
--------------------------------------------------------------------------------
1 | import { MEISTER } from "../constants";
2 |
3 | const getMeisterChapter = (chapter: string) => {
4 | switch (chapter) {
5 | case MEISTER.직업기초능력:
6 | return "직업기초능력";
7 | case MEISTER.전문기술역량:
8 | return "전문 기술 역량";
9 | case MEISTER.인성직업의식:
10 | return "인성 직업 의식";
11 | case MEISTER.인문학적소양:
12 | return "인문학적 소양";
13 | case MEISTER.외국어능력:
14 | return "외국어 능력";
15 | default:
16 | return chapter;
17 | }
18 | };
19 |
20 | export default getMeisterChapter;
21 |
--------------------------------------------------------------------------------
/src/templates/meister/helpers/getStatusColor.helper.ts:
--------------------------------------------------------------------------------
1 | import { theme } from "@/styles";
2 |
3 | const getStatusColor = (index: number) => {
4 | if (!index) return theme.primary_yellow;
5 | if (index === 1) return theme.primary_blue;
6 | return theme.primary_red;
7 | };
8 |
9 | export default getStatusColor;
10 |
--------------------------------------------------------------------------------
/src/templates/meister/helpers/getStudentId.helper.ts:
--------------------------------------------------------------------------------
1 | const getStudentId = (
2 | grade: number,
3 | classNum: number,
4 | studentNumber: number,
5 | ) => {
6 | return String(grade * 1000 + classNum * 100 + studentNumber || "");
7 | };
8 |
9 | export default getStudentId;
10 |
--------------------------------------------------------------------------------
/src/templates/meister/helpers/getStudentInformationHTML.helper.ts:
--------------------------------------------------------------------------------
1 | const getStudentInformationHTML = (content: string) => {
2 | const HTML_TAG = ``;
3 | const scoreHTMLIndex = content.indexOf(HTML_TAG);
4 | const scoreHTMLCloseIndex = content.indexOf(`
`);
5 | const studentHTMLContent = content.substring(
6 | scoreHTMLIndex,
7 | scoreHTMLCloseIndex,
8 | );
9 |
10 | return studentHTMLContent.replace(HTML_TAG, "");
11 | };
12 |
13 | export default getStudentInformationHTML;
14 |
--------------------------------------------------------------------------------
/src/templates/meister/helpers/get요일ByWeekday.helper.ts:
--------------------------------------------------------------------------------
1 | const get요일ByWeekday = (weekday: string) => {
2 | switch (weekday) {
3 | case "MONDAY":
4 | return "월요일";
5 | case "TUESDAY":
6 | return "화요일";
7 | case "WEDNESDAY":
8 | return "수요일";
9 | case "THURSDAY":
10 | return "목요일";
11 | case "FRIDAY":
12 | return "금요일";
13 | default:
14 | return weekday;
15 | }
16 | };
17 |
18 | export default get요일ByWeekday;
19 |
--------------------------------------------------------------------------------
/src/templates/meister/helpers/index.ts:
--------------------------------------------------------------------------------
1 | export { default as get요일ByWeekday } from "./get요일ByWeekday.helper";
2 | export { default as getMeisterChapter } from "./getMeisterChapter.helper";
3 | export { default as getStatusColor } from "./getStatusColor.helper";
4 | export { default as getStudentInformationHTML } from "./getStudentInformationHTML.helper";
5 | export { default as setMeisterPointNaming } from "./setMeisterPointNaming.helper";
6 | export { default as getStudentId } from "./getStudentId.helper";
7 |
--------------------------------------------------------------------------------
/src/templates/meister/helpers/setMeisterPointNaming.helper.ts:
--------------------------------------------------------------------------------
1 | const setMeisterPointNaming = () => {
2 | document.querySelectorAll(".fas.fa-sad-cry").forEach((item) => {
3 | item?.parentElement?.parentElement?.parentElement?.parentElement?.classList.add(
4 | "bad",
5 | );
6 | });
7 | };
8 |
9 | export default setMeisterPointNaming;
10 |
--------------------------------------------------------------------------------
/src/templates/meister/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { default as useMeister } from "./useMeister";
2 | export { default as useMeisterHTML } from "./useMeisterHTML";
3 |
--------------------------------------------------------------------------------
/src/templates/meister/services/api.service.ts:
--------------------------------------------------------------------------------
1 | import httpClient from "@/apis/httpClient";
2 |
3 | export const getMeister = async () => {
4 | const { data } = await httpClient.meister.get();
5 | return data;
6 | };
7 |
8 | export const getMeisterRanking = async (grade: number) => {
9 | const { data } = await httpClient.ranking.getById({ params: { id: grade } });
10 | return data;
11 | };
12 |
13 | export const getMeisterDetail = async (student: string) => {
14 | const { data } = await httpClient.meister.getDetail({
15 | grade: student[0],
16 | classNo: student[1],
17 | studentNo: +(student[2] + student[3]),
18 | pw: "",
19 | });
20 | return data;
21 | };
22 |
23 | export const putMeisterRankingPrivate = async (flag: boolean) => {
24 | const { data } = await httpClient.private.putPrivateMeister({
25 | privateRanking: flag,
26 | });
27 | return data;
28 | };
29 |
--------------------------------------------------------------------------------
/src/templates/meister/services/query.service.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { KEY } from "@/constants";
3 | import { getMeister, getMeisterDetail, getMeisterRanking } from "./api.service";
4 |
5 | export const useMeisterQuery = () => {
6 | const { data, ...queryRest } = useQuery([KEY.MEISTER], async () =>
7 | getMeister(),
8 | );
9 | return { data, ...queryRest };
10 | };
11 |
12 | export const useMeisterRankingQuery = (grade: number) => {
13 | const { data, ...queryRest } = useQuery([KEY.RANKING], async () =>
14 | getMeisterRanking(grade),
15 | );
16 | return { data, ...queryRest };
17 | };
18 |
19 | export const useMeisterDetailQuery = (studentNumber: string) => {
20 | const { data, ...queryRest } = useQuery(
21 | [KEY.MEISTER_DETAIL],
22 | async () => getMeisterDetail(studentNumber),
23 | // { enabled: studentNumber.length === 4 },
24 | );
25 | return { data, ...queryRest };
26 | };
27 |
--------------------------------------------------------------------------------
/src/templates/meister/types/@props/CircularProgressBoxProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface CircularProgressBoxProps {
2 | chapter: string;
3 | score: string;
4 | statusColor: string;
5 | value: number;
6 | text: string;
7 | }
8 |
--------------------------------------------------------------------------------
/src/templates/meister/types/@props/index.ts:
--------------------------------------------------------------------------------
1 | export type { default as CircularProgressBoxProps } from "./CircularProgressBoxProps.type";
2 |
--------------------------------------------------------------------------------
/src/templates/meister/types/Meister.type.ts:
--------------------------------------------------------------------------------
1 | export default interface Meister {
2 | meister: {
3 | score: number;
4 | positivePoint: number;
5 | negativePoint: number;
6 | lastUpdate: string;
7 | loginError: boolean;
8 | basicJobSkills: number;
9 | professionalTech: number;
10 | workEthic: number;
11 | humanities: number;
12 | foreignScore: number;
13 | };
14 | avg: {
15 | score: number;
16 | basicJobSkills: number;
17 | professionalTech: number;
18 | workEthic: number;
19 | humanities: number;
20 | foreignScore: number;
21 | positivePoint: number;
22 | negativePoint: number;
23 | };
24 | max: {
25 | score: number;
26 | basicJobSkills: number;
27 | professionalTech: number;
28 | workEthic: number;
29 | humanities: number;
30 | foreignScore: number;
31 | positivePoint: number;
32 | negativePoint: number;
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/src/templates/meister/types/MeisterChartData.type.ts:
--------------------------------------------------------------------------------
1 | import { MeisterKeyType, MeisterScoreType } from ".";
2 |
3 | export default interface MeisterChartData {
4 | group: MeisterKeyType;
5 | scoreType: MeisterScoreType;
6 | }
7 |
--------------------------------------------------------------------------------
/src/templates/meister/types/MeisterDetail.type.ts:
--------------------------------------------------------------------------------
1 | export default interface MeisterDetail {
2 | meister: {
3 | scoreHtmlContent: string;
4 | pointHtmlContent: string;
5 | score: number;
6 | positivePoint: number;
7 | negativePoint: number;
8 | lastUpdate: string;
9 | loginError: boolean;
10 | basicJobSkills: number;
11 | professionalTech: number;
12 | workEthic: number;
13 | humanities: number;
14 | foreignScore: number;
15 | };
16 | avg: {
17 | score: number;
18 | basicJobSkills: number;
19 | professionalTech: number;
20 | workEthic: number;
21 | humanities: number;
22 | foreignScore: number;
23 | positivePoint: number;
24 | negativePoint: number;
25 | };
26 | max: {
27 | score: number;
28 | basicJobSkills: number;
29 | professionalTech: number;
30 | workEthic: number;
31 | humanities: number;
32 | foreignScore: number;
33 | positivePoint: number;
34 | negativePoint: number;
35 | };
36 | }
37 |
--------------------------------------------------------------------------------
/src/templates/meister/types/MeisterRankingItem.type.ts:
--------------------------------------------------------------------------------
1 | export default interface MeisterRankingItem {
2 | score: number;
3 | positivePoint: number;
4 | negativePoint: number;
5 | student: {
6 | grade: number;
7 | classNo: number;
8 | studentNo: number;
9 | name: string;
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/src/templates/meister/types/index.ts:
--------------------------------------------------------------------------------
1 | export type { default as Meister } from "./Meister.type";
2 | export type { default as MeisterChartData } from "./MeisterChartData.type";
3 | export type { default as MeisterDetail } from "./MeisterDetail.type";
4 | export type { default as MeisterKeyType } from "./meisterKey.type";
5 | export type { default as MeisterScoreType } from "./meisterScore.type";
6 | export type { default as MeisterRankingItem } from "./MeisterRankingItem.type";
7 |
--------------------------------------------------------------------------------
/src/templates/meister/types/meisterKey.type.ts:
--------------------------------------------------------------------------------
1 | type MeisterKeyType =
2 | | "basicJobSkills"
3 | | "professionalTech"
4 | | "workEthic"
5 | | "humanities"
6 | | "foreignScore";
7 |
8 | export default MeisterKeyType;
9 |
--------------------------------------------------------------------------------
/src/templates/meister/types/meisterScore.type.ts:
--------------------------------------------------------------------------------
1 | type MeisterScoreType = "avg" | "max" | "meister";
2 |
3 | export default MeisterScoreType;
4 |
--------------------------------------------------------------------------------
/src/templates/oauth/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { default as useOAuth } from "./useOAuth";
2 |
--------------------------------------------------------------------------------
/src/templates/oauth/hooks/useOAuth.ts:
--------------------------------------------------------------------------------
1 | import { useQueryClient } from "@tanstack/react-query";
2 | import { useRouter, useSearchParams } from "next/navigation";
3 | import React from "react";
4 | import { KEY } from "@/constants";
5 | import Storage from "@/storage";
6 | import { useLoginMutation } from "../services/mutation.service";
7 |
8 | const useOAuth = () => {
9 | const authCode = useSearchParams().get("code");
10 | const { isSuccess, mutate } = useLoginMutation(authCode);
11 | const queryClient = useQueryClient();
12 | const router = useRouter();
13 |
14 | React.useEffect(() => {
15 | mutate();
16 | queryClient.invalidateQueries([KEY.USER]);
17 | }, []);
18 |
19 | React.useEffect(() => {
20 | if (isSuccess) {
21 | const redirectUrl = Storage.getItem("SETTING:리다이렉트경로") || "/";
22 | router.push(redirectUrl);
23 | }
24 | }, [isSuccess, router]);
25 | };
26 |
27 | export default useOAuth;
28 |
--------------------------------------------------------------------------------
/src/templates/oauth/layouts/index.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import styled from "styled-components";
3 | import { loading } from "@/assets/images";
4 | import { theme, flex, font } from "@/styles";
5 | import { useOAuth } from "../hooks";
6 |
7 | const OAuthPage = () => {
8 | useOAuth();
9 |
10 | return (
11 |
12 |
13 | 로그인 처리중...
14 |
15 | );
16 | };
17 |
18 | const Layout = styled.div`
19 | width: 100%;
20 | height: 80vh;
21 | ${flex.COLUMN_CENTER};
22 | gap: 12px;
23 | `;
24 |
25 | const LoadingText = styled.span`
26 | ${font.H6};
27 | color: ${theme.black};
28 | `;
29 |
30 | export default OAuthPage;
31 |
--------------------------------------------------------------------------------
/src/templates/oauth/services/api.service.ts:
--------------------------------------------------------------------------------
1 | import httpClient from "@/apis/httpClient";
2 |
3 | export const login = async (authCode: string | null) => {
4 | if (authCode) {
5 | const { data } = await httpClient.oauth.login(authCode);
6 | return data;
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/src/templates/oauth/services/mutation.service.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "@tanstack/react-query";
2 | import Storage from "@/storage";
3 | import { TOKEN } from "@/storage/constants";
4 | import { login } from "./api.service";
5 |
6 | export const useLoginMutation = (authCode: string | null) => {
7 | return useMutation(() => login(authCode), {
8 | onSuccess: ({ accessToken, refreshToken }) => {
9 | Storage.setItem(TOKEN.ACCESS, accessToken);
10 | Storage.setItem(TOKEN.REFRESH, refreshToken);
11 | },
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/src/templates/post/assets/data/categoryList.data.ts:
--------------------------------------------------------------------------------
1 | import { CATEGORY } from "../../constants";
2 |
3 | const categoryListData = [
4 | CATEGORY.COMMON,
5 | CATEGORY.NOTICE,
6 | CATEGORY.PROJECT,
7 | CATEGORY.CODE_REVIEW,
8 | CATEGORY.FOUND,
9 | CATEGORY.LOST,
10 | ];
11 |
12 | export default categoryListData;
13 |
--------------------------------------------------------------------------------
/src/templates/post/assets/data/dateTime.data.ts:
--------------------------------------------------------------------------------
1 | import { POST_INPUT } from "../../constants";
2 |
3 | const dateTimeData: Array<{
4 | label: string;
5 | name: string;
6 | dataName: "startTime" | "endTime";
7 | }> = [
8 | {
9 | label: "시작 날짜",
10 | name: POST_INPUT.START_TIME,
11 | dataName: "startTime",
12 | },
13 | {
14 | label: "마감 날짜",
15 | name: POST_INPUT.END_TIME,
16 | dataName: "endTime",
17 | },
18 | ];
19 |
20 | export default dateTimeData;
21 |
--------------------------------------------------------------------------------
/src/templates/post/assets/data/defaultPost.data.ts:
--------------------------------------------------------------------------------
1 | import { CATEGORY } from "../../constants";
2 | import { Post } from "../../types";
3 |
4 | const defaultPostData: Post = {
5 | id: "",
6 | title: "",
7 | category: CATEGORY.COMMON,
8 | content: "",
9 | prUrl: "",
10 | isFinished: false,
11 | lostThingImage: "",
12 | place: "",
13 | keepingPlace: "",
14 | startTime: "",
15 | endTime: "",
16 | field: "",
17 | user: {
18 | id: -1,
19 | nickName: "",
20 | profileImage: "",
21 | },
22 | createdAt: "",
23 | likeCount: 0,
24 | commentCount: 0,
25 | doesLike: false,
26 | };
27 |
28 | export default defaultPostData;
29 |
--------------------------------------------------------------------------------
/src/templates/post/assets/data/index.ts:
--------------------------------------------------------------------------------
1 | export { default as categoryListData } from "./categoryList.data";
2 | export { default as defaultPostData } from "./defaultPost.data";
3 | export { default as dateTimeData } from "./dateTime.data";
4 |
--------------------------------------------------------------------------------
/src/templates/post/assets/icons/CategoryArrow.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const CategoryArrow = ({ ...props }: React.SVGProps) => {
4 | return (
5 |
17 | );
18 | };
19 |
20 | export default CategoryArrow;
21 |
--------------------------------------------------------------------------------
/src/templates/post/assets/icons/CommentIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const CommentIcon = ({ ...props }: React.SVGProps) => {
4 | return (
5 |
20 | );
21 | };
22 |
23 | export default CommentIcon;
24 |
--------------------------------------------------------------------------------
/src/templates/post/assets/icons/index.ts:
--------------------------------------------------------------------------------
1 | export { default as CommentIcon } from "./CommentIcon";
2 | export { default as LikeIcon } from "./LikeIcon";
3 | export { default as TimeIcon } from "./TimeIcon";
4 | export { default as AddCommentIcon } from "./AddCommentIcon";
5 |
--------------------------------------------------------------------------------
/src/templates/post/assets/images/empty_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Team-INSERT/bssm-frontend/a1c85df5d9325dcce6bb3385720e8bc356b44241/src/templates/post/assets/images/empty_image.png
--------------------------------------------------------------------------------
/src/templates/post/assets/images/index.ts:
--------------------------------------------------------------------------------
1 | export { default as EmptyImage } from "./empty_image.png";
2 |
--------------------------------------------------------------------------------
/src/templates/post/constants/category.constant.ts:
--------------------------------------------------------------------------------
1 | const CATEGORY = {
2 | COMMON: "COMMON",
3 | PROJECT: "PROJECT",
4 | NOTICE: "NOTICE",
5 | CODE_REVIEW: "CODE_REVIEW",
6 | LOST: "LOST",
7 | FOUND: "FOUND",
8 | } as const;
9 |
10 | export default CATEGORY;
11 |
--------------------------------------------------------------------------------
/src/templates/post/constants/index.ts:
--------------------------------------------------------------------------------
1 | export { default as CATEGORY } from "./category.constant";
2 | export { default as POST_INPUT } from "./postInput.constant";
3 | export { default as LIKE } from "./like.constant";
4 |
--------------------------------------------------------------------------------
/src/templates/post/constants/like.constant.ts:
--------------------------------------------------------------------------------
1 | const LIKE = {
2 | POST: "POST",
3 | COMMENT: "COMMENT",
4 | RECOMMENT: "RECOMMENT",
5 | } as const;
6 |
7 | export default LIKE;
8 |
--------------------------------------------------------------------------------
/src/templates/post/constants/postInput.constant.ts:
--------------------------------------------------------------------------------
1 | const POST_INPUT = {
2 | TITLE: "title",
3 | CONTENT: "content",
4 | START_TIME: "startTime",
5 | END_TIME: "endTime",
6 | FIELD: "field",
7 | PR_URL: "prUrl",
8 | PLACE: "place",
9 | KEEPING_PLACE: "keepingPlace",
10 | } as const;
11 |
12 | export default POST_INPUT;
13 |
--------------------------------------------------------------------------------
/src/templates/post/context/currentCategory.context.ts:
--------------------------------------------------------------------------------
1 | import { atom } from "jotai";
2 | import { PostCategoryType } from "../types";
3 | import { CATEGORY } from "../constants";
4 |
5 | const currentCategoryContext = atom(CATEGORY.COMMON);
6 |
7 | export default currentCategoryContext;
8 |
--------------------------------------------------------------------------------
/src/templates/post/context/index.ts:
--------------------------------------------------------------------------------
1 | export { default as currentCategoryContext } from "./currentCategory.context";
2 |
--------------------------------------------------------------------------------
/src/templates/post/helpers/getTextDepthCount.helper.ts:
--------------------------------------------------------------------------------
1 | const getTextDepthCount = (content: string) => {
2 | const depth = content.split("\n").length;
3 | return depth;
4 | };
5 |
6 | export default getTextDepthCount;
7 |
--------------------------------------------------------------------------------
/src/templates/post/helpers/getTextIsOverflow.helper.ts:
--------------------------------------------------------------------------------
1 | const getTextIsOverflow = (content: string) => {
2 | const sentences = content.split("\n");
3 | const depth = sentences.length;
4 |
5 | if (depth > 4) {
6 | const summaryContent = sentences.slice(0, 4).join("\n");
7 | return `${summaryContent}...`;
8 | }
9 | return content;
10 | };
11 |
12 | export default getTextIsOverflow;
13 |
--------------------------------------------------------------------------------
/src/templates/post/helpers/get카테고리명ByCategory.helper.ts:
--------------------------------------------------------------------------------
1 | import { CATEGORY } from "../constants";
2 | import { PostCategoryType } from "../types";
3 |
4 | const get카테고리명ByCategory = (category: PostCategoryType) => {
5 | switch (category) {
6 | case CATEGORY.COMMON:
7 | return "게시판";
8 | case CATEGORY.CODE_REVIEW:
9 | return "코드 리뷰";
10 | case CATEGORY.FOUND:
11 | return "찾았어요";
12 | case CATEGORY.LOST:
13 | return "잃어버렸어요";
14 | case CATEGORY.NOTICE:
15 | return "공지사항";
16 | case CATEGORY.PROJECT:
17 | return "프로젝트";
18 | default:
19 | return category;
20 | }
21 | };
22 |
23 | export default get카테고리명ByCategory;
24 |
--------------------------------------------------------------------------------
/src/templates/post/helpers/get회당불러올게시글개수.helper.ts:
--------------------------------------------------------------------------------
1 | import Storage from "@/storage";
2 |
3 | const get회당불러올게시글개수 = () => {
4 | return Storage.getItem("SETTING:회당불러올게시글수") ?? 20;
5 | };
6 |
7 | export default get회당불러올게시글개수;
8 |
--------------------------------------------------------------------------------
/src/templates/post/helpers/index.ts:
--------------------------------------------------------------------------------
1 | export { default as get회당불러올게시글개수 } from "./get회당불러올게시글개수.helper";
2 | export { default as get카테고리명ByCategory } from "./get카테고리명ByCategory.helper";
3 | export { default as getFilteredPostDataByCategory } from "./getFilteredPostDataByCategory.helper";
4 | export { default as getPostIsValid } from "./getPostIsValid.helper";
5 | export { default as getTextIsOverflow } from "./getTextIsOverflow.helper";
6 | export { default as getTextDepthCount } from "./getTextDepthCount.helper";
7 |
--------------------------------------------------------------------------------
/src/templates/post/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { default as usePost } from "./usePost";
2 | export { default as usePostWritable } from "./usePostWritable";
3 | export { default as useLike } from "./useLike";
4 | export { default as useComment } from "./useComment";
5 | export { default as useRecomment } from "./useRecomment";
6 | export { default as useTextarea } from "./useTextarea";
7 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/components/CodeReviewInputBox.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "@/components/atoms";
2 | import POST_INPUT from "../../constants/postInput.constant";
3 | import { PostCategoryInputBoxProps } from "../../types/@props";
4 |
5 | const CodeReviewInputBox = ({
6 | handleChange,
7 | postData,
8 | }: PostCategoryInputBoxProps) => {
9 | return (
10 |
17 | );
18 | };
19 |
20 | export default CodeReviewInputBox;
21 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/components/ContentInputBox.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { Column } from "@/components/Flex";
3 | import { font } from "@/styles";
4 | import { ContentEditor } from "@/components/common";
5 | import { PostCategoryInputBoxProps } from "../../types/@props";
6 |
7 | const ContentInputBox = ({
8 | handleChange,
9 | postData,
10 | }: PostCategoryInputBoxProps) => {
11 | return (
12 |
13 | 글 내용
14 |
15 |
16 | );
17 | };
18 |
19 | const TitleInputLabelText = styled.span`
20 | ${font.p3};
21 | `;
22 |
23 | export default ContentInputBox;
24 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/components/FoundInputBox.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "@/components/atoms";
2 | import { PostCategoryInputBoxProps } from "../../types/@props";
3 | import { POST_INPUT } from "../../constants";
4 |
5 | const FoundInputBox = ({
6 | handleChange,
7 | postData,
8 | }: PostCategoryInputBoxProps) => {
9 | return (
10 |
17 | );
18 | };
19 |
20 | export default FoundInputBox;
21 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/components/LostFoundInputBox.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { DragDrop } from "@/components/common";
3 | import { Input } from "@/components/atoms";
4 | import { font } from "@/styles";
5 | import { PostCategoryInputBoxProps } from "../../types/@props";
6 | import { CATEGORY } from "../../constants";
7 | import POST_INPUT from "../../constants/postInput.constant";
8 |
9 | const LostFoundInputBox = ({
10 | postData,
11 | lostImageUrl,
12 | handleChange,
13 | handleFileSelect,
14 | }: PostCategoryInputBoxProps) => {
15 | return (
16 | <>
17 |
24 | 물품의 이미지를 업로드해주세요.
25 | {handleFileSelect && (
26 |
27 | )}
28 | >
29 | );
30 | };
31 |
32 | const TitleInputLabelText = styled.h1`
33 | ${font.p3};
34 | `;
35 |
36 | export default LostFoundInputBox;
37 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/components/ProjectInputBox.tsx:
--------------------------------------------------------------------------------
1 | import { Input } from "@/components/atoms";
2 | import { Column, Row } from "@/components/Flex";
3 | import styled from "styled-components";
4 | import { theme, font } from "@/styles";
5 | import POST_INPUT from "../../constants/postInput.constant";
6 | import { PostCategoryInputBoxProps } from "../../types/@props";
7 | import { dateTimeData } from "../../assets/data";
8 |
9 | const ProjectInputBox = ({
10 | handleChange,
11 | postData,
12 | }: PostCategoryInputBoxProps) => {
13 | return (
14 | <>
15 |
22 |
23 | {dateTimeData.map(({ name, dataName, label }) => (
24 |
25 | {label}
26 |
32 |
33 | ))}
34 |
35 | >
36 | );
37 | };
38 |
39 | const TitleInputLabelText = styled.h1`
40 | ${font.p3};
41 | `;
42 |
43 | const DateInput = styled.input`
44 | padding: 10px 14px;
45 | background-color: ${theme.white};
46 | ${font.caption};
47 | `;
48 |
49 | export default ProjectInputBox;
50 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/components/TitleInputBox.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Input } from "@/components/atoms";
3 | import { PostCategoryInputBoxProps } from "../../types/@props";
4 | import { POST_INPUT } from "../../constants";
5 |
6 | const TitleInputBox = ({
7 | handleChange,
8 | postData,
9 | }: PostCategoryInputBoxProps) => {
10 | return (
11 |
18 | );
19 | };
20 |
21 | export default TitleInputBox;
22 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/components/index.ts:
--------------------------------------------------------------------------------
1 | export { default as CodeReviewInputBox } from "./CodeReviewInputBox";
2 | export { default as ContentInputBox } from "./ContentInputBox";
3 | export { default as FoundInputBox } from "./FoundInputBox";
4 | export { default as LostFoundInputBox } from "./LostFoundInputBox";
5 | export { default as ProjectInputBox } from "./ProjectInputBox";
6 | export { default as TitleInputBox } from "./TitleInputBox";
7 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/detail/CommentStylesheet.tsx:
--------------------------------------------------------------------------------
1 | import { theme, font } from "@/styles";
2 | import styled from "styled-components";
3 |
4 | export const CommentWriter = styled.span`
5 | ${font.caption};
6 | font-weight: 600;
7 | color: ${theme.gray};
8 | `;
9 |
10 | export const CommentCreatedAt = styled.span`
11 | ${font.caption};
12 | color: ${theme.gray};
13 |
14 | @media screen and (max-width: 541px) {
15 | display: none;
16 | }
17 | `;
18 |
19 | export const CommentTextArea = styled.textarea`
20 | border: 2px solid ${theme.on_tertiary};
21 | border-radius: 4px;
22 | padding: 6px 12px;
23 | margin: 6px 0;
24 | ${font.p3};
25 | `;
26 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/detail/PostCategoryInformationBox.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { font } from "@/styles";
3 | import { PostCategoryType } from "../../types";
4 | import CategoryArrow from "../../assets/icons/CategoryArrow";
5 | import { get카테고리명ByCategory } from "../../helpers";
6 |
7 | interface IPostCategoryProps {
8 | postType: PostCategoryType;
9 | }
10 |
11 | const PostCategoryInformationBox = ({ postType }: IPostCategoryProps) => {
12 | return (
13 |
14 | 커뮤니티
15 |
16 | {get카테고리명ByCategory(postType)}
17 |
18 | );
19 | };
20 |
21 | const Container = styled.div`
22 | display: flex;
23 | align-items: center;
24 | gap: 6px;
25 | `;
26 |
27 | const PostType = styled.h1`
28 | ${font.p3};
29 | font-weight: 600;
30 | `;
31 |
32 | const CategoryType = styled.h1`
33 | ${font.p3};
34 | font-weight: 600;
35 | margin-left: -2px;
36 | `;
37 |
38 | export default PostCategoryInformationBox;
39 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/detail/PostLikeCountBox.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { Row } from "@/components/Flex";
3 | import { flex, font } from "@/styles";
4 | import { CommentIcon } from "../../assets/icons";
5 | import ReactiveLikeIcon from "../../assets/icons/ReactiveLikeIcon";
6 | import { useLike } from "../../hooks";
7 | import { PostCountBoxProps } from "../../types/@props";
8 |
9 | const PostLikeCountBox = ({
10 | likeCount,
11 | commentCount,
12 | doesLike,
13 | id,
14 | }: PostCountBoxProps) => {
15 | const { currentLikeCount, isLike, handleUpdatePostLikeButtonClick } = useLike(
16 | {
17 | likeCount,
18 | doesLike,
19 | },
20 | );
21 |
22 | return (
23 |
24 | handleUpdatePostLikeButtonClick(id)}>
25 |
26 | {currentLikeCount}
27 |
28 |
29 |
30 | {commentCount}
31 |
32 |
33 | );
34 | };
35 |
36 | const LikeBox = styled.div`
37 | ${flex.HORIZONTAL};
38 | gap: 4px;
39 | cursor: pointer;
40 | `;
41 |
42 | const InformationText = styled.span`
43 | ${font.p1};
44 | `;
45 |
46 | export default PostLikeCountBox;
47 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/detail/comment/CommentContentBox.tsx:
--------------------------------------------------------------------------------
1 | import { Column } from "@/components/Flex";
2 | import { theme, font } from "@/styles";
3 | import { getTextDepthCount, getTextIsOverflow } from "@/templates/post/helpers";
4 | import { CommentContentBoxProps } from "@/templates/post/types/@props";
5 | import React from "react";
6 | import styled from "styled-components";
7 |
8 | const CommentContentBox = ({
9 | isDetailMode,
10 | handleDetailModeChange,
11 | content,
12 | commentInput,
13 | }: CommentContentBoxProps) => {
14 | return (
15 |
16 |
17 | {isDetailMode ? content : getTextIsOverflow(content)}
18 |
19 | {getTextDepthCount(commentInput) > 4 && (
20 |
21 | {isDetailMode ? "간략히" : "자세히 보기"}
22 |
23 | )}
24 |
25 | );
26 | };
27 |
28 | const DetailViewButton = styled.button`
29 | border: none;
30 | width: fit-content;
31 | color: ${theme.gray};
32 | ${font.caption};
33 | border-radius: 999px;
34 |
35 | &:hover {
36 | text-decoration: underline;
37 | }
38 | `;
39 |
40 | const CommentDetail = styled.p`
41 | ${font.p3};
42 | white-space: pre-wrap;
43 | `;
44 |
45 | export default CommentContentBox;
46 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/detail/comment/CommentList.tsx:
--------------------------------------------------------------------------------
1 | import { theme, flex } from "@/styles";
2 | import React from "react";
3 | import styled from "styled-components";
4 | import { useInfiniteScroll } from "@/hooks";
5 | import { useCommentListQuery } from "@/templates/post/services/comment/query.service";
6 | import { Comment } from "@/templates/post/types";
7 | import { PostDetailParamsProps } from "@/templates/post/types/@props";
8 | import CommentListItem from "./CommentListItem";
9 |
10 | const CommentList = ({ id }: PostDetailParamsProps) => {
11 | const { commentList, fetchNextPage } = useCommentListQuery(id);
12 | useInfiniteScroll(fetchNextPage);
13 |
14 | return (
15 |
16 | {commentList?.map((comments) => (
17 |
18 | {comments.entity.map((comment: Comment) => (
19 |
20 | ))}
21 |
22 | ))}
23 |
24 | );
25 | };
26 |
27 | const Container = styled.div`
28 | width: 100%;
29 | height: 100%;
30 | background-color: ${theme.white};
31 | `;
32 |
33 | const CommentListBox = styled.div`
34 | width: 100%;
35 | height: 100%;
36 | ${flex.COLUMN_FLEX};
37 | `;
38 |
39 | export default CommentList;
40 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/detail/comment/CreateCommentBox.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { theme, flex, font } from "@/styles";
4 | import { Button } from "@/components/atoms";
5 | import { useComment } from "@/templates/post/hooks";
6 | import { PostDetailParamsProps } from "@/templates/post/types/@props";
7 |
8 | const CreateCommentBox = ({ id }: PostDetailParamsProps) => {
9 | const { commentInput, handleCommentInputChange, handleCreateCommentClick } =
10 | useComment(id);
11 |
12 | return (
13 |
14 |
18 |
19 |
22 |
23 |
24 | );
25 | };
26 |
27 | const Container = styled.div`
28 | ${flex.COLUMN_FLEX};
29 | width: 100%;
30 | gap: 8px;
31 | `;
32 |
33 | const CommentTextArea = styled.textarea`
34 | width: 100%;
35 | height: 100px;
36 | border: 2px solid ${theme.on_tertiary};
37 | padding: 12px;
38 | ${font.p3};
39 | `;
40 |
41 | const CommentToolBox = styled.div`
42 | ${flex.HORIZONTAL};
43 | gap: 16px;
44 | margin-left: auto;
45 | position: relative;
46 | `;
47 |
48 | export default CreateCommentBox;
49 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/detail/comment/RecommentViewButton.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { theme, flex, font } from "@/styles";
3 | import { RecommentViewButtonProps } from "@/templates/post/types/@props";
4 | import { ArrowIcon } from "@/assets/icons";
5 | import { DIRECTION } from "@/constants";
6 |
7 | const RecommentViewButton = ({
8 | handleViewRecommentModeChange,
9 | isViewRecommentMode,
10 | recommentCount,
11 | }: RecommentViewButtonProps) => {
12 | return (
13 |
14 |
20 | 답글 {recommentCount}개
21 |
22 | );
23 | };
24 |
25 | const Container = styled.button`
26 | width: fit-content;
27 | ${flex.CENTER};
28 | gap: 6px;
29 | margin-top: 6px;
30 | padding: 8px 10px 4px 10px;
31 | border-radius: 999px;
32 |
33 | &:hover {
34 | background-color: ${theme.on_tertiary};
35 | }
36 | `;
37 |
38 | const RecommentViewCountText = styled.span`
39 | color: ${theme.primary_blue};
40 | ${font.caption};
41 | font-weight: 600;
42 | margin-top: -4px;
43 | `;
44 |
45 | export default RecommentViewButton;
46 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/detail/recomment/RecommentContentBox.tsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import { Column } from "@/components/Flex";
3 | import { theme, font } from "@/styles";
4 | import { getTextDepthCount, getTextIsOverflow } from "@/templates/post/helpers";
5 | import { CommentContentBoxProps } from "@/templates/post/types/@props";
6 |
7 | const RecommentContentBox = ({
8 | isDetailMode,
9 | handleDetailModeChange,
10 | content,
11 | commentInput,
12 | }: CommentContentBoxProps) => {
13 | return (
14 |
15 |
16 | {isDetailMode ? content : getTextIsOverflow(content)}
17 |
18 | {getTextDepthCount(commentInput) > 4 && (
19 |
20 | {isDetailMode ? "간략히" : "자세히 보기"}
21 |
22 | )}
23 |
24 | );
25 | };
26 |
27 | const CommentDetail = styled.p`
28 | ${font.p3};
29 | white-space: pre-wrap;
30 | `;
31 |
32 | const DetailViewButton = styled.button`
33 | border: none;
34 | width: fit-content;
35 | color: ${theme.gray};
36 | ${font.caption};
37 | border-radius: 999px;
38 |
39 | &:hover {
40 | text-decoration: underline;
41 | }
42 | `;
43 |
44 | export default RecommentContentBox;
45 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/detail/recomment/RecommentList.tsx:
--------------------------------------------------------------------------------
1 | import { theme, flex } from "@/styles";
2 | import React from "react";
3 | import styled from "styled-components";
4 | import Recomment from "@/templates/post/types/Recomment.type";
5 | import { useInfiniteScroll } from "@/hooks";
6 | import { PostDetailParamsProps } from "@/templates/post/types/@props";
7 | import { useRecommentListQuery } from "@/templates/post/services/recomment/query.service";
8 | import RecommentListItem from "./RecommentListItem";
9 |
10 | const RecommentList = ({ id }: PostDetailParamsProps) => {
11 | const { recommentList, fetchNextPage } = useRecommentListQuery(id);
12 | useInfiniteScroll(fetchNextPage);
13 |
14 | return (
15 |
16 | {recommentList?.map((recomments) => (
17 |
18 | {recomments.entity.map((recomment: Recomment) => (
19 |
20 | ))}
21 |
22 | ))}
23 |
24 | );
25 | };
26 |
27 | const Container = styled.div`
28 | width: 100%;
29 | height: 100%;
30 | background-color: ${theme.white};
31 | `;
32 |
33 | const RecommentListBox = styled.div`
34 | width: 100%;
35 | height: 100%;
36 | ${flex.COLUMN_FLEX};
37 | `;
38 |
39 | export default RecommentList;
40 |
--------------------------------------------------------------------------------
/src/templates/post/layouts/list/PostListItemInformationBar.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Row } from "@/components/Flex";
3 | import styled from "styled-components";
4 | import { theme, font } from "@/styles";
5 | import { CommentIcon, LikeIcon, TimeIcon } from "../../assets/icons";
6 | import { usePost } from "../../hooks";
7 | import { PostListItemInformationBarProps } from "../../types/@props";
8 |
9 | const PostListItemInformationBar = ({
10 | likeCount,
11 | commentCount,
12 | createdAt,
13 | }: PostListItemInformationBarProps) => {
14 | const { formatPostCreatedDate } = usePost();
15 | return (
16 |
17 |
18 |
19 | {commentCount}
20 |
21 |
22 |
23 | {likeCount}
24 |
25 |
26 |
27 | {formatPostCreatedDate(createdAt)}
28 |
29 |
30 | );
31 | };
32 |
33 | const InformationBox = styled.div`
34 | display: flex;
35 | align-items: center;
36 | gap: 3px;
37 |
38 | @media screen and (max-width: 340px) {
39 | display: none;
40 | }
41 | `;
42 |
43 | const InformationText = styled.span`
44 | ${font.p3};
45 | color: ${theme.gray};
46 | `;
47 |
48 | export default PostListItemInformationBar;
49 |
--------------------------------------------------------------------------------
/src/templates/post/services/comment/api.service.ts:
--------------------------------------------------------------------------------
1 | import httpClient from "@/apis/httpClient";
2 | import { GetCommentListProps, PostCommentProps } from "../../types/@props";
3 |
4 | export const getCommentList = async ({
5 | id,
6 | pageParam: page,
7 | }: GetCommentListProps) => {
8 | const { data } = await httpClient.comment.getById({ params: { id, page } });
9 | return data;
10 | };
11 |
12 | export const createComment = async ({ id, detail }: PostCommentProps) => {
13 | const { data } = await httpClient.comment.postById(
14 | { detail },
15 | { params: { id } },
16 | );
17 | return data;
18 | };
19 |
20 | export const updateComment = async (comment: PostCommentProps) => {
21 | const { data } = await httpClient.comment.put(comment);
22 | return data;
23 | };
24 |
25 | export const deleteComment = async (id: number) => {
26 | const { data } = await httpClient.comment.deleteById({
27 | params: { id },
28 | });
29 | return data;
30 | };
31 |
--------------------------------------------------------------------------------
/src/templates/post/services/comment/mutation.service.ts:
--------------------------------------------------------------------------------
1 | import { useMutation, useQueryClient } from "@tanstack/react-query";
2 | import { toast } from "react-toastify";
3 | import { KEY } from "@/constants";
4 | import { createComment, deleteComment, updateComment } from "./api.service";
5 | import { PostCommentProps } from "../../types/@props";
6 |
7 | export const useCreateCommentMutation = () => {
8 | const queryClient = useQueryClient();
9 |
10 | return useMutation((comment: PostCommentProps) => createComment(comment), {
11 | onSuccess: () => {
12 | toast.success("댓글을 작성했어요!");
13 | queryClient.invalidateQueries([KEY.COMMENT]);
14 | },
15 | });
16 | };
17 |
18 | export const useUpdateCommentMutation = () => {
19 | const queryClient = useQueryClient();
20 |
21 | return useMutation((comment: PostCommentProps) => updateComment(comment), {
22 | onSuccess: () => {
23 | toast.success("댓글을 수정했어요!");
24 | queryClient.invalidateQueries([KEY.COMMENT]);
25 | },
26 | });
27 | };
28 |
29 | export const useDeleteCommentMutation = () => {
30 | const queryClient = useQueryClient();
31 |
32 | return useMutation((id: number) => deleteComment(id), {
33 | onSuccess: () => {
34 | toast.success("댓글을 삭제했어요!");
35 | queryClient.invalidateQueries([KEY.COMMENT]);
36 | },
37 | });
38 | };
39 |
--------------------------------------------------------------------------------
/src/templates/post/services/comment/query.service.ts:
--------------------------------------------------------------------------------
1 | import { useInfiniteQuery } from "@tanstack/react-query";
2 | import { KEY } from "@/constants";
3 | import { getCommentList } from "./api.service";
4 |
5 | export const useCommentListQuery = (id: number) => {
6 | const { data, hasNextPage, ...queryRest } = useInfiniteQuery({
7 | queryKey: [KEY.COMMENT, id],
8 | queryFn: ({ pageParam = 0 }) => getCommentList({ id, pageParam }),
9 | getNextPageParam: ({ currentPage, totalPage }) => {
10 | if (currentPage !== totalPage) return currentPage + 1;
11 | return undefined;
12 | },
13 | });
14 |
15 | return { commentList: data?.pages, ...queryRest };
16 | };
17 |
--------------------------------------------------------------------------------
/src/templates/post/services/graphql/data.graphql.ts:
--------------------------------------------------------------------------------
1 | const DEFAULT_POST = `
2 | id
3 | category
4 | title
5 | content
6 | createdAt
7 | likeCount
8 | commentCount
9 | doesLike
10 | user {
11 | id
12 | nickName
13 | profileImage
14 | }
15 | `;
16 | const ALL_POST = `
17 | ${DEFAULT_POST}
18 | startTime
19 | endTime
20 | field
21 | prUrl
22 | isFinished
23 | lostThingImage
24 | place
25 | keepingPlace
26 | `;
27 |
28 | const LOST_FOUND_POST = `
29 | lostThingImage
30 | place
31 | foundUser {
32 | id
33 | nickName
34 | }
35 | `;
36 |
37 | export const posts: { [key: string]: string } = {
38 | COMMON: `
39 | ${DEFAULT_POST}
40 | `,
41 | NOTICE: `
42 | ${DEFAULT_POST}
43 | `,
44 | PROJECT: `
45 | ${DEFAULT_POST}
46 | startTime
47 | endTime
48 | field
49 | `,
50 | CODE_REVIEW: `
51 | ${DEFAULT_POST}
52 | prUrl
53 | isFinished
54 | `,
55 | LOST: `
56 | ${DEFAULT_POST}
57 | ${LOST_FOUND_POST}
58 | `,
59 | FOUND: `
60 | ${DEFAULT_POST}
61 | ${LOST_FOUND_POST}
62 | keepingPlace
63 | `,
64 | ALL: ALL_POST,
65 | };
66 |
--------------------------------------------------------------------------------
/src/templates/post/services/like/api.service.ts:
--------------------------------------------------------------------------------
1 | import httpClient from "@/apis/httpClient";
2 | import { LIKE } from "../../constants";
3 |
4 | export const updatePostLike = async (id: string) => {
5 | const { data } = await httpClient.like.put({
6 | type: LIKE.POST,
7 | partyId: id,
8 | });
9 | return data;
10 | };
11 |
12 | export const updateCommentLike = async (id: string) => {
13 | const { data } = await httpClient.like.put({
14 | type: LIKE.COMMENT,
15 | partyId: id,
16 | });
17 | return data;
18 | };
19 |
20 | export const updateRecommentLike = async (id: string) => {
21 | const { data } = await httpClient.like.put({
22 | type: LIKE.RECOMMENT,
23 | partyId: id,
24 | });
25 | return data;
26 | };
27 |
--------------------------------------------------------------------------------
/src/templates/post/services/like/mutation.service.ts:
--------------------------------------------------------------------------------
1 | import { useMutation } from "@tanstack/react-query";
2 | import {
3 | updateCommentLike,
4 | updatePostLike,
5 | updateRecommentLike,
6 | } from "./api.service";
7 |
8 | export const useUpdatePostLikeMutation = () => {
9 | return useMutation((id: string) => updatePostLike(id));
10 | };
11 |
12 | export const useUpdateCommentLikeMutation = () => {
13 | return useMutation((id: string) => updateCommentLike(id));
14 | };
15 |
16 | export const useUpdateRecommentLikeMutation = () => {
17 | return useMutation((id: string) => updateRecommentLike(id));
18 | };
19 |
--------------------------------------------------------------------------------
/src/templates/post/services/post/mutation.service.ts:
--------------------------------------------------------------------------------
1 | import { useMutation, useQueryClient } from "@tanstack/react-query";
2 | import { toast } from "react-toastify";
3 | import { useRouter } from "next/navigation";
4 | import { KEY, ROUTER } from "@/constants";
5 | import { createPost, deletePost, updatePost } from "./api.service";
6 | import { PostData } from "../../types";
7 |
8 | export const useUpdatePostMutation = () => {
9 | const queryClient = useQueryClient();
10 | const router = useRouter();
11 | return useMutation((post: PostData) => updatePost(post), {
12 | onSuccess: (id) => {
13 | toast.success("게시글이 수정되었어요!");
14 | queryClient.invalidateQueries([KEY.POST, id]);
15 | router.push(`${ROUTER.POST.DETAIL}/${id}`);
16 | },
17 | });
18 | };
19 |
20 | export const useCreatePostMutation = () => {
21 | const router = useRouter();
22 | return useMutation((post: PostData) => createPost(post), {
23 | onSuccess: (id) => {
24 | toast.success("게시글이 등록되었어요!");
25 | router.push(`${ROUTER.POST.DETAIL}/${id}`);
26 | },
27 | });
28 | };
29 |
30 | export const useDeletePostMutation = () => {
31 | const router = useRouter();
32 | return useMutation((id: number) => deletePost(id), {
33 | onSuccess: () => {
34 | toast.success("게시글이 삭제되었어요!");
35 | router.push(`${ROUTER.POST.LIST}`);
36 | },
37 | });
38 | };
39 |
--------------------------------------------------------------------------------
/src/templates/post/services/post/query.service.ts:
--------------------------------------------------------------------------------
1 | import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
2 | import { KEY } from "@/constants";
3 | import { getPostList, getPost } from "./api.service";
4 | import { PostCategoryType } from "../../types";
5 |
6 | export const usePostListQuery = (category: PostCategoryType) => {
7 | const { data, hasNextPage, ...queryRest } = useInfiniteQuery(
8 | [KEY.POST, category],
9 | ({ pageParam = 0 }) => getPostList({ category, page: pageParam }),
10 | {
11 | getNextPageParam: ({ currentPage, totalPage }) =>
12 | currentPage !== totalPage ? currentPage + 1 : undefined,
13 | },
14 | );
15 | return { postList: data?.pages, ...queryRest };
16 | };
17 |
18 | export const usePostQuery = (id: number) => {
19 | const { data, ...queryRest } = useQuery([KEY.POST, id], () => getPost(id));
20 | return { post: data, ...queryRest };
21 | };
22 |
--------------------------------------------------------------------------------
/src/templates/post/services/recomment/api.service.ts:
--------------------------------------------------------------------------------
1 | import httpClient from "@/apis/httpClient";
2 | import { GetCommentListProps, PostCommentProps } from "../../types/@props";
3 |
4 | export const getRecommentList = async ({
5 | id,
6 | pageParam: page,
7 | }: GetCommentListProps) => {
8 | const { data } = await httpClient.recomment.getById({ params: { id, page } });
9 | return data;
10 | };
11 |
12 | export const createRecomment = async ({ id, detail }: PostCommentProps) => {
13 | const { data } = await httpClient.recomment.postById(
14 | { detail },
15 | {
16 | params: { id },
17 | },
18 | );
19 | return data;
20 | };
21 |
22 | export const updateRecomment = async (comment: PostCommentProps) => {
23 | const { data } = await httpClient.recomment.put(comment);
24 | return data;
25 | };
26 |
27 | export const deleteRecomment = async (id: number) => {
28 | const { data } = await httpClient.recomment.deleteById({
29 | params: { id },
30 | });
31 | return data;
32 | };
33 |
--------------------------------------------------------------------------------
/src/templates/post/services/recomment/query.service.ts:
--------------------------------------------------------------------------------
1 | import { KEY } from "@/constants";
2 | import { useInfiniteQuery } from "@tanstack/react-query";
3 | import { getRecommentList } from "./api.service";
4 |
5 | export const useRecommentListQuery = (id: number) => {
6 | const { data, hasNextPage, ...queryRest } = useInfiniteQuery({
7 | queryKey: [KEY.RECOMMENT, id],
8 | queryFn: ({ pageParam = 0 }) => getRecommentList({ id, pageParam }),
9 | getNextPageParam: ({ currentPage, totalPage }) => {
10 | if (currentPage !== totalPage) return currentPage + 1;
11 | return undefined;
12 | },
13 | });
14 | return { recommentList: data?.pages, ...queryRest };
15 | };
16 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/CommentContentBoxProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface CommentContentBoxProps {
2 | isDetailMode: boolean;
3 | handleDetailModeChange: () => void;
4 | content: string;
5 | commentInput: string;
6 | }
7 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/CommentLikeInformationBoxProps.type.ts:
--------------------------------------------------------------------------------
1 | import Comment from "../Comment.type";
2 |
3 | export default interface CommentLikeInformationBoxProps {
4 | handleRecommentWriteModeChange: () => void;
5 | comment: Comment;
6 | }
7 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/CommentListItemProps.type.ts:
--------------------------------------------------------------------------------
1 | import Comment from "../Comment.type";
2 |
3 | export default interface CommentListItemProps {
4 | comment: Comment;
5 | }
6 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/CommentQueryProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface CommentQueryProps {
2 | detail: string;
3 | id: number;
4 | }
5 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/CreateRecommentBoxProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface CreateRecommentBoxProps {
2 | handleModeCancelClick: () => void;
3 | id: number;
4 | }
5 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/GetCommentListProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface GetCommentListProps {
2 | id: number;
3 | pageParam: number;
4 | }
5 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/GetPostListProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface GetPostListProps {
2 | category: string;
3 | page: number;
4 | }
5 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/LikeIconProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface LikeIconProps extends React.SVGProps {
2 | isLiked: boolean;
3 | }
4 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/PostCategoryBoxProps.type.ts:
--------------------------------------------------------------------------------
1 | import PostCategoryType from "../PostCategory.type";
2 |
3 | export default interface PostCategoryBoxProps {
4 | handleChangeCategory: (e: React.ChangeEvent) => void;
5 | currentCategory: PostCategoryType;
6 | }
7 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/PostCategoryInputBoxProps.type.ts:
--------------------------------------------------------------------------------
1 | import Post from "../Post.type";
2 |
3 | export default interface PostCategoryInputBoxProps {
4 | handleChange: (
5 | eventOrContent?: string | React.ChangeEvent,
6 | ) => void;
7 | postData: Post;
8 | lostImageUrl?: undefined;
9 | handleFileSelect?: (file?: File) => Promise;
10 | }
11 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/PostCommentProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface PostCommentProps {
2 | id: number;
3 | detail: string;
4 | }
5 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/PostCountBoxProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface PostCountBoxProps {
2 | commentCount: number;
3 | likeCount: number;
4 | doesLike: boolean;
5 | id: string;
6 | }
7 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/PostDetailParamsProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface PostDetailParamsProps {
2 | id: number;
3 | }
4 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/PostListItemInformationBarProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface PostListItemInformationBarProps {
2 | likeCount: number;
3 | commentCount: number;
4 | createdAt: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/PostProps.type.ts:
--------------------------------------------------------------------------------
1 | import Post from "../Post.type";
2 |
3 | export default interface PostProps {
4 | post: Post;
5 | }
6 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/PostSectionBoxProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface PostSectionBoxProps {
2 | title: string;
3 | content?: string;
4 | startTime?: string;
5 | endTime?: string;
6 | isProjectDate?: boolean;
7 | isContent?: boolean;
8 | isUrl?: boolean;
9 | isDefault?: boolean;
10 | isImage?: boolean;
11 | }
12 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/RecommentViewButtonProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface RecommentViewButtonProps {
2 | handleViewRecommentModeChange: () => void;
3 | isViewRecommentMode: boolean;
4 | recommentCount: number;
5 | }
6 |
--------------------------------------------------------------------------------
/src/templates/post/types/@props/UseLikeProps.type.ts:
--------------------------------------------------------------------------------
1 | export default interface UseLikeProps {
2 | doesLike: boolean;
3 | likeCount: number;
4 | }
5 |
--------------------------------------------------------------------------------
/src/templates/post/types/Comment.type.ts:
--------------------------------------------------------------------------------
1 | export default interface Comment {
2 | id: number;
3 | detail: string;
4 | reCommentCount: number;
5 | postId: number;
6 | likeCount: number;
7 | createdAt: string;
8 | doesLike: boolean;
9 | user: {
10 | id: number;
11 | nickName: string;
12 | profileImage: string;
13 | };
14 | }
15 |
--------------------------------------------------------------------------------
/src/templates/post/types/Post.type.ts:
--------------------------------------------------------------------------------
1 | import { PostCategoryType } from ".";
2 |
3 | export default interface Post {
4 | id: string;
5 | title: string;
6 | content: string;
7 | user: {
8 | id: number;
9 | nickName: string;
10 | profileImage: string;
11 | };
12 | category: PostCategoryType;
13 | createdAt: string;
14 | likeCount: number;
15 | commentCount: number;
16 | doesLike: boolean;
17 |
18 | // POST.PROJECT
19 | startTime?: string;
20 | endTime?: string;
21 | field?: string;
22 |
23 | // POST.CODE_REVIEW
24 | prUrl?: string;
25 | isFinished?: boolean;
26 |
27 | // POST.LOST & POST.FOUND
28 | lostThingImage?: string;
29 | place?: string;
30 | foundUser?: {
31 | id: number;
32 | nickName: string;
33 | };
34 |
35 | // POST.FOUND
36 | keepingPlace?: string;
37 | }
38 |
--------------------------------------------------------------------------------
/src/templates/post/types/PostCategory.type.ts:
--------------------------------------------------------------------------------
1 | type PostCategoryType =
2 | | "COMMON"
3 | | "NOTICE"
4 | | "PROJECT"
5 | | "CODE_REVIEW"
6 | | "LOST"
7 | | "FOUND";
8 |
9 | export default PostCategoryType;
10 |
--------------------------------------------------------------------------------
/src/templates/post/types/PostCreateQuery.type.ts:
--------------------------------------------------------------------------------
1 | export default interface PostCreateQuery {
2 | create: {
3 | id: number;
4 | };
5 | }
6 |
--------------------------------------------------------------------------------
/src/templates/post/types/PostData.type.ts:
--------------------------------------------------------------------------------
1 | import { PostCategoryType } from ".";
2 |
3 | export default interface PostData {
4 | id: string;
5 | title: string;
6 | category: PostCategoryType;
7 | content: string;
8 | prUrl: string;
9 | isFinished: boolean;
10 | lostThingImage: string;
11 | place: string;
12 | keepingPlace: string;
13 | startTime: Date | string;
14 | endTime: Date | string;
15 | field: string;
16 | }
17 |
--------------------------------------------------------------------------------
/src/templates/post/types/PostListProperty.type.ts:
--------------------------------------------------------------------------------
1 | import Post from "./Post.type";
2 |
3 | type PostListOmitType = "content";
4 | type PostListProperty = Omit;
5 |
6 | export default PostListProperty;
7 |
--------------------------------------------------------------------------------
/src/templates/post/types/PostListQuery.type.ts:
--------------------------------------------------------------------------------
1 | import PostListQueryProperty from "./PostListQueryProperty.type";
2 |
3 | export default interface PostListQuery {
4 | readByCategory: PostListQueryProperty;
5 | }
6 |
--------------------------------------------------------------------------------
/src/templates/post/types/PostListQueryProperty.type.ts:
--------------------------------------------------------------------------------
1 | import { PostListProperty } from ".";
2 |
3 | export default interface PostListQueryProperty {
4 | entity: Array;
5 | totalPage: number;
6 | currentPage: number;
7 | }
8 |
--------------------------------------------------------------------------------
/src/templates/post/types/PostQuery.type.ts:
--------------------------------------------------------------------------------
1 | import Post from "./Post.type";
2 |
3 | export default interface PostQuery {
4 | readOne: Post;
5 | }
6 |
--------------------------------------------------------------------------------
/src/templates/post/types/PostUpdateQuery.type.ts:
--------------------------------------------------------------------------------
1 | export default interface PostUpdateQuery {
2 | update: {
3 | id: number;
4 | };
5 | }
6 |
--------------------------------------------------------------------------------
/src/templates/post/types/Recomment.type.ts:
--------------------------------------------------------------------------------
1 | import Comment from "./Comment.type";
2 |
3 | export default interface Recomment extends Omit {
4 | commentId: number;
5 | }
6 |
--------------------------------------------------------------------------------
/src/templates/post/types/index.ts:
--------------------------------------------------------------------------------
1 | export type { default as PostListProperty } from "./PostListProperty.type";
2 | export type { default as PostListQuery } from "./PostListQuery.type";
3 | export type { default as PostListQueryProperty } from "./PostListQueryProperty.type";
4 | export type { default as Post } from "./Post.type";
5 | export type { default as PostQuery } from "./PostQuery.type";
6 | export type { default as PostData } from "./PostData.type";
7 | export type { default as PostUpdateQuery } from "./PostUpdateQuery.type";
8 | export type { default as PostCreateQuery } from "./PostCreateQuery.type";
9 | export type { default as Comment } from "./Comment.type";
10 | export type { default as Recomment } from "./Recomment.type";
11 | export type { default as PostCategoryType } from "./PostCategory.type";
12 |
--------------------------------------------------------------------------------
/src/templates/timetable/assets/data/defaultTimeTable.data.ts:
--------------------------------------------------------------------------------
1 | const defaultTimeTableData: Record = {
2 | SUNDAY: [{ subject: "" }],
3 | FRIDAY: [{ subject: "" }],
4 | SATURDAY: [{ subject: "" }],
5 | TUESDAY: [{ subject: "" }],
6 | THURSDAY: [{ subject: "" }],
7 | MONDAY: [{ subject: "" }],
8 | WEDNESDAY: [{ subject: "" }],
9 | };
10 |
11 | export default defaultTimeTableData;
12 |
--------------------------------------------------------------------------------
/src/templates/timetable/assets/data/index.ts:
--------------------------------------------------------------------------------
1 | export { default as defaultTimeTableData } from "./defaultTimeTable.data";
2 |
--------------------------------------------------------------------------------
/src/templates/timetable/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { default as useTimeTable } from "./useTimeTable";
2 |
--------------------------------------------------------------------------------
/src/templates/timetable/hooks/useTimeTable.ts:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useTimetableListQuery } from "../services/query.service";
3 | import { defaultTimeTableData } from "../assets/data";
4 |
5 | const useTimeTable = () => {
6 | const [dayTimeTable, setDayTimeTable] = React.useState(defaultTimeTableData);
7 | const { data, isSuccess } = useTimetableListQuery();
8 |
9 | React.useEffect(() => {
10 | if (isSuccess) setDayTimeTable(data);
11 | }, [isSuccess, data]);
12 |
13 | return { dayTimeTable };
14 | };
15 |
16 | export default useTimeTable;
17 |
--------------------------------------------------------------------------------
/src/templates/timetable/services/api.service.ts:
--------------------------------------------------------------------------------
1 | import httpClient from "@/apis/httpClient";
2 |
3 | export const getTimetable = async () => {
4 | const { data } = await httpClient.timetable.getById({
5 | params: { id: "table" },
6 | });
7 | return data;
8 | };
9 |
--------------------------------------------------------------------------------
/src/templates/timetable/services/query.service.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from "@tanstack/react-query";
2 | import { KEY } from "@/constants";
3 | import { getTimetable } from "./api.service";
4 |
5 | export const useTimetableListQuery = () => {
6 | const { data, ...queryRest } = useQuery([KEY.TIMETABLE], getTimetable);
7 | return { data: data?.timeTable, ...queryRest };
8 | };
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "types": ["jest"],
6 | "allowJs": true,
7 | "skipLibCheck": true,
8 | "strict": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "noEmit": true,
11 | "esModuleInterop": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "jsx": "preserve",
17 | "incremental": true,
18 | "baseUrl": "./",
19 | "paths": {
20 | "@/*": ["./src/*"]
21 | },
22 | "plugins": [
23 | {
24 | "name": "next"
25 | }
26 | ]
27 | },
28 | "include": [
29 | "next-env.d.ts",
30 | "**/*.ts",
31 | "**/*.tsx",
32 | "tailwind.config.js",
33 | ".eslintrc.js",
34 | ".next/types/**/*.ts",
35 | "public/mockServiceWorker.js",
36 | "jest.setup.js"
37 | ],
38 | "exclude": ["node_modules"]
39 | }
40 |
--------------------------------------------------------------------------------