Skip to content

Commit de58f47

Browse files
authored
Merge pull request #260 from boostcampwm2023/feature/project-create
feat: 프로젝트 생성 API 연결 및 파비콘, 폰트 적용
2 parents 5b4707d + b73cfbe commit de58f47

16 files changed

+130
-128
lines changed

frontend/index.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
5-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
5+
<link rel="icon" type="image/svg+xml" href="/LesserLogo.png" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>Vite + React + TS</title>
7+
<title>Lesser</title>
88
</head>
99
<body>
1010
<div id="root"></div>

frontend/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"test": "jest --coverage"
1212
},
1313
"dependencies": {
14+
"@tanstack/react-query": "^5.28.14",
1415
"axios": "^1.6.7",
1516
"react": "^18.2.0",
1617
"react-dom": "^18.2.0",

frontend/public/LesserLogo.png

89.7 KB
Loading

frontend/src/App.tsx

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
import { ModalProvider } from "./hooks/common/modal/ModalProvider";
22
import AppRouter from "./AppRouter";
3+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
34

4-
const App = () => {
5-
return (
5+
const queryClient = new QueryClient({
6+
defaultOptions: {
7+
queries: {
8+
refetchOnWindowFocus: false,
9+
},
10+
},
11+
});
12+
13+
const App = () => (
14+
<QueryClientProvider client={queryClient}>
615
<ModalProvider>
716
<AppRouter />
817
</ModalProvider>
9-
);
10-
};
18+
</QueryClientProvider>
19+
);
1120

1221
export default App;

frontend/src/apis/api/projectAPI.ts

+8
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,11 @@ export const getProjects = async () => {
88
return response.data.projects;
99
}
1010
};
11+
12+
export const postCreateProject = async (body: {
13+
title: string;
14+
subject: string;
15+
}) => {
16+
const response = await authAPI.post(API_URL.PROJECT, body);
17+
return response.data;
18+
};
Binary file not shown.

frontend/src/components/projects/ProjectCard.tsx

+14-8
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,37 @@ interface ProjectCardProps {
88
const ProjectCard = ({ project }: ProjectCardProps) => {
99
const { title, currentSprint } = project;
1010
return (
11-
<div className="w-[21.25rem] p-9 bg-gradient-to-bl from-white-transparent to-90% bg-light-green">
11+
<div className="w-[21.25rem] p-9 bg-gradient-to-bl from-white-transparent to-95% bg-middle-green">
1212
<p className="mb-[1.125rem] text-xl font-bold text-white">{title}</p>
1313
<div className="h-[3.9rem]">
1414
<p className="mb-2.5 text-xs font-bold text-white">
1515
{currentSprint && currentSprint.title}
1616
</p>
1717
<p className="text-xs text-white">
1818
{currentSprint &&
19-
`${formatDate(currentSprint.startDate)} - ${formatDate(currentSprint.endDate)}`}
19+
`${formatDate(currentSprint.startDate)} - ${formatDate(
20+
currentSprint.endDate
21+
)}`}
2022
</p>
2123
</div>
2224
<div className="flex items-center min-h-[7.625rem] mt-[1.125rem] px-9 py-6 gap-12 bg-white">
2325
{currentSprint ? (
2426
<>
25-
<div className="flex-col items-center gap-3 font-bold text-middle-green">
27+
<div className="flex flex-col items-center gap-3 font-bold text-middle-green">
2628
<p className="text-m">
27-
<span className="text-[2.25rem]">{currentSprint.progressPercentage}</span> %
29+
<span className="text-[2.25rem]">
30+
{currentSprint.progressPercentage.toString().padStart(2, '0')}
31+
</span>%
2832
</p>
29-
<p className="">진행률</p>
33+
<p>진행률</p>
3034
</div>
31-
<div className="flex-col items-center gap-3 font-bold text-middle-green">
35+
<div className="flex flex-col items-center gap-3 font-bold text-middle-green">
3236
<p className="text-m">
33-
<span className="text-[2.25rem]">{currentSprint.myTasksLeft}</span>
37+
<span className="text-[2.25rem]">
38+
{currentSprint.myTasksLeft.toString().padStart(2, '0')}
39+
</span>
3440
</p>
35-
<p className="">내 업무</p>
41+
<p>내 업무</p>
3642
</div>
3743
</>
3844
) : (

frontend/src/components/projects/ProjectCreateInput.tsx

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { useRef, useState } from "react";
22
import NextStepButton from "../common/NextStepButton";
3-
import {
4-
PROJECT_CREATE_STEP,
5-
PROJECT_NAME_INPUT_ID,
6-
} from "../../constants/projects";
3+
import { PROJECT_NAME_INPUT_ID } from "../../constants/projects";
74
import { Step } from "../../types/common";
85

96
interface ProjectCreateInputProps {
@@ -20,7 +17,6 @@ interface ProjectCreateInputProps {
2017
const ProjectCreateInput = ({
2118
elementId,
2219
targetStepIsCurrentStep,
23-
setCurrentStep,
2420
onNextButtonClick,
2521
label,
2622
inputRef,
@@ -55,7 +51,7 @@ const ProjectCreateInput = ({
5551

5652
const handleEnterDown = ({ key }: React.KeyboardEvent) => {
5753
if (key === "Enter" && valid) {
58-
setCurrentStep(PROJECT_CREATE_STEP.STEP2);
54+
handleNextButtonClick();
5955
}
6056
};
6157

frontend/src/components/projects/ProjectCreateMainSection.tsx

+21-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
PROJECT_SUBJECT_INPUT_ID,
77
} from "../../constants/projects";
88
import { Step } from "../../types/common";
9+
import usePostCreateProject from "../../hooks/queries/project/usePostCreateProject";
910

1011
interface ProjectCreateMainSectionProps {
1112
currentStep: Step;
@@ -16,8 +17,9 @@ const ProjectCreateMainSection = ({
1617
currentStep,
1718
setCurrentStep,
1819
}: ProjectCreateMainSectionProps) => {
19-
const projectNameRef = useRef<string>("");
20+
const projectTitleRef = useRef<string>("");
2021
const projectSubjectRef = useRef<string>("");
22+
const mutation = usePostCreateProject();
2123

2224
const handlePrevStepAreaClick = () => {
2325
setCurrentStep((prevStep) => {
@@ -29,25 +31,37 @@ const ProjectCreateMainSection = ({
2931
});
3032
};
3133

32-
const handleCreateButtonClick = async () => {};
34+
const handleCreateButtonClick = async () => {
35+
mutation.mutate({
36+
title: projectTitleRef.current,
37+
subject: projectSubjectRef.current,
38+
});
39+
};
3340

3441
const handleNextButtonClick = () => {
3542
setCurrentStep(PROJECT_CREATE_STEP.STEP2);
3643
};
3744

3845
useEffect(() => {
39-
const nicknameInput = document.getElementById(PROJECT_NAME_INPUT_ID);
40-
const nicknameInputElement = document.getElementById(
46+
const projectTitleInputElement = document.getElementById(
47+
PROJECT_NAME_INPUT_ID
48+
);
49+
const projectTitleInputBox = document.getElementById(
4150
`${PROJECT_NAME_INPUT_ID}-input-box`
4251
);
4352

4453
switch (currentStep.NUMBER) {
4554
case PROJECT_CREATE_STEP.STEP1.NUMBER:
46-
nicknameInput?.scrollIntoView({ behavior: "smooth", block: "center" });
55+
projectTitleInputElement?.focus();
56+
projectTitleInputElement?.scrollIntoView({
57+
behavior: "smooth",
58+
block: "center",
59+
});
4760
break;
4861

4962
case PROJECT_CREATE_STEP.STEP2.NUMBER:
50-
nicknameInputElement?.scrollIntoView({
63+
document.getElementById(PROJECT_SUBJECT_INPUT_ID)?.focus();
64+
projectTitleInputBox?.scrollIntoView({
5165
behavior: "smooth",
5266
block: "start",
5367
});
@@ -65,7 +79,7 @@ const ProjectCreateMainSection = ({
6579
></div>
6680
<section className="h-[100%] overflow-y-hidden">
6781
<ProjectCreateInput
68-
inputRef={projectNameRef}
82+
inputRef={projectTitleRef}
6983
targetStepIsCurrentStep={currentStep === PROJECT_CREATE_STEP.STEP1}
7084
setCurrentStep={setCurrentStep}
7185
onNextButtonClick={handleNextButtonClick}

frontend/src/components/projects/ProjectList.tsx

+26-17
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,47 @@
1-
import { useEffect, useMemo, useState } from "react";
1+
import { useMemo } from "react";
22
import useDropdown from "../../hooks/common/dropdown/useDropdown";
33
import { ProjectCard } from ".";
44
import { PROJECT_SORT_OPTION } from "../../constants/projects";
55
import plus from "../../assets/icons/plus.svg";
66
import { ProjectDTO } from "../../types/projectDTO";
7-
import { getProjects } from "../../apis/api/projectAPI";
87
import projectSortByOption from "../../utils/projectSortByOption";
8+
import { useGetProjects } from "../../hooks/queries/project";
9+
import { useNavigate } from "react-router-dom";
10+
import { ROUTER_URL } from "../../constants/path";
911

1012
const ProjectList = () => {
11-
const [projects, setProjects] = useState<ProjectDTO[]>([]);
13+
const { data: projects, error } = useGetProjects();
1214
const { Dropdown, selectedOption } = useDropdown({
1315
placeholder: "",
1416
options: [PROJECT_SORT_OPTION.UPDATE, PROJECT_SORT_OPTION.RECENT],
1517
defaultOption: PROJECT_SORT_OPTION.UPDATE,
1618
});
19+
const navigate = useNavigate();
20+
21+
const handleCreateButtonClick = () => {
22+
navigate(`/${ROUTER_URL.PROJECTS_CREATE}`);
23+
};
1724

1825
const projectList = useMemo<ProjectDTO[]>(() => {
1926
const earliest = -new Date();
2027

21-
return structuredClone(projects).sort((projectA, projectB) =>
22-
projectSortByOption({
23-
projectA,
24-
projectB,
25-
option: selectedOption,
26-
earliest,
27-
})
28-
);
28+
if (Array.isArray(projects)) {
29+
return structuredClone(projects).sort((projectA, projectB) =>
30+
projectSortByOption({
31+
projectA,
32+
projectB,
33+
option: selectedOption,
34+
earliest,
35+
})
36+
);
37+
}
38+
39+
return [];
2940
}, [projects, selectedOption]);
3041

31-
useEffect(() => {
32-
(async () => {
33-
const projectList = await getProjects();
34-
setProjects(projectList);
35-
})();
36-
}, []);
42+
if (error) {
43+
throw error;
44+
}
3745

3846
return (
3947
<section className="min-w-[720px] min-h-[40.5rem] flex flex-col gap-6">
@@ -48,6 +56,7 @@ const ProjectList = () => {
4856
<button
4957
type="button"
5058
className="flex items-center justify-center w-[10.45rem] h-[2.5rem] py-2 pl-3 pr-9 text-white text-xs bg-middle-green gap-3 rounded-[0.375rem] shadow-box"
59+
onClick={handleCreateButtonClick}
5160
>
5261
<img src={plus} alt="더하기" className="w-7" />
5362
추가하기

frontend/src/components/projects/ProjectsSideBar.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ const ProjectsSideBar = () => {
2323
<p className="mb-8 font-semibold text-m text-dark-gray">프로젝트를 생성하고</p>
2424
<p className="mb-8 font-semibold text-m text-dark-gray">관리하고</p>
2525
<p className="mb-[9.625rem] font-semibold text-m text-dark-gray">확인해 보세요</p>
26-
<div className="w-[23.375rem] py-6 px-[1.875rem] flex items-center gap-6 bg-gradient-to-bl from-white-transparent to-90% bg-light-green rounded-[1.125rem] text-white">
27-
<div className="rounded-full w-16 h-16 overflow-hidden">
28-
<img src={userData.imageUrl} alt="프로필 사진" className="w-16 h-16 object-contain" />
26+
<div className="w-[23.375rem] py-6 px-[1.875rem] flex items-center gap-6 bg-gradient-to-bl from-white-transparent to-90% bg-middle-green rounded-[1.125rem] text-white">
27+
<div className="w-16 h-16 overflow-hidden rounded-full">
28+
<img src={userData.imageUrl} alt="프로필 사진" className="object-contain w-16 h-16" />
2929
</div>
3030
<div className="flex flex-col gap-1">
3131
<p className="font-semibold text-m">{userData.username}</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as useGetProjects } from "./useGetProjects";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import { getProjects } from "../../../apis/api/projectAPI";
3+
import { ProjectDTO } from "../../../types/projectDTO";
4+
5+
const useGetProjects = () =>
6+
useQuery<ProjectDTO[]>({
7+
queryKey: ["projects"],
8+
queryFn: getProjects,
9+
});
10+
11+
export default useGetProjects;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useMutation } from "@tanstack/react-query";
2+
import { postCreateProject } from "../../../apis/api/projectAPI";
3+
import { useNavigate } from "react-router-dom";
4+
import { ROUTER_URL } from "../../../constants/path";
5+
6+
const usePostCreateProject = () => {
7+
const navigate = useNavigate();
8+
9+
return useMutation({
10+
mutationFn: (body: { title: string; subject: string }) =>
11+
postCreateProject(body),
12+
onSuccess: () => {
13+
navigate(ROUTER_URL.PROJECTS);
14+
},
15+
});
16+
};
17+
18+
export default usePostCreateProject;

frontend/src/index.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
font-family: "pretendard";
77
font-style: normal;
88
font-weight: normal;
9-
src: url("./assets/fonts/PretendardVariable.woff2") format("font-woff2");
9+
src: url("./assets/fonts/PretendardVariable.woff2") format("woff2");
1010
}
1111

1212
html,

0 commit comments

Comments
 (0)