Skip to content

Commit 3afd6e3

Browse files
committed
Merge branch 'develop' into release
2 parents 0b372c9 + 091bd7f commit 3afd6e3

File tree

38 files changed

+347
-357
lines changed

38 files changed

+347
-357
lines changed

README.md

+7-7
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
[**리액트와 불변성**](https://oasis-pocket-331.notion.site/Immer-js-f6c3936bf589468db0aa4fa06fc91c1d) by **[@rudy3091](https://github.com/rudy3091)**
3535

36-
**[Recoil로의 마이그레이션](https://oasis-pocket-331.notion.site/Recoil-c876906b6cb44e53b776dd7f592777c1)** by **[@haesoo9410](https://github.com/haesoo9410)**
36+
**[Recoil로의 마이그레이션](https://haesoo9410.tistory.com/330?category=974679)** by **[@haesoo9410](https://github.com/haesoo9410)**
3737

3838
**[Redis 도입 배경](https://oasis-pocket-331.notion.site/Redis-7ae574d796004381914a7334497e1a1b)** by **[@ChanHoLee275](https://github.com/ChanHoLee275)**
3939

@@ -64,21 +64,21 @@ HyupUp과 함께라면 신속하고 효율적인 협업으로 **칼퇴**를 하
6464
- 드래그 앤 드랍으로 진행기간을 조정
6565
- 하위 스토리 항목과 연동되어 진행상태를 시각적으로 확인가능
6666

67-
### 📬 메일링 서비스
68-
69-
- 조직의 구성원을 쉽게 초대하기 위한 서비스
70-
- 이메일에 포함된 링크를 클릭하면, 해당 조직으로 회원가입 진행
71-
7267
### 🗂️ 프로젝트 스토리 관리
7368

7469
- Drag and Drop API 를 활용한 스토리 진행 상황 관리
7570
- 소켓을 활용한 프로젝트 팀원간 진행 상황 공유
7671
- 스토리의 진행 상황을 반영한 에픽 상태 변경
7772

73+
### 📬 메일링 서비스
74+
75+
- 조직의 구성원을 쉽게 초대하기 위한 서비스
76+
- 이메일에 포함된 링크를 클릭하면, 해당 조직으로 회원가입 진행
77+
7878

7979

8080
## 기술 스택
81-
<img src="https://s3.us-west-2.amazonaws.com/secure.notion-static.com/5651fe79-310e-4847-b7da-25ee7e4583d4/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20211123%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20211123T101053Z&X-Amz-Expires=86400&X-Amz-Signature=21123c83ee6d3c6bb73fc8f5ad7b628e8ea83400f5b9ef89db9633840c1b7599&X-Amz-SignedHeaders=host&response-content-disposition=filename%20%3D%22Untitled.png%22&x-id=GetObject" />
81+
![image](https://user-images.githubusercontent.com/71266602/144960113-67c6269a-a7f9-4a85-ab92-a6755543ead6.png)
8282

8383
## Documents
8484
- [API](https://oasis-pocket-331.notion.site/API-4cdb0639248d4a13baa68d198248c99c)

client/package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@
55
"license": "MIT",
66
"dependencies": {
77
"axios": "^0.24.0",
8-
"axios-cache-adapter": "^2.7.3",
98
"dotenv-webpack": "^7.0.3",
109
"immer": "^9.0.6",
1110
"jwt-decode": "^3.1.2",
1211
"react": "^17.0.2",
1312
"react-dom": "^17.0.2",
14-
"react-intersection-observer": "^8.32.5",
15-
"react-router-dom": "^5.3.0",
13+
"react-intersection-observer": "^8.33.1",
14+
"react-router-dom": "^6.2.1",
1615
"react-toastify": "^8.1.0",
1716
"recoil": "^0.5.2",
1817
"socket.io-client": "^4.3.2",
@@ -65,6 +64,7 @@
6564
"react-recoil-hooks-testing-library": "^0.1.0",
6665
"style-loader": "^3.3.1",
6766
"webpack": "^5.61.0",
67+
"webpack-bundle-analyzer": "^4.5.0",
6868
"webpack-cli": "^4.9.1",
6969
"webpack-dev-server": "^4.4.0"
7070
}

client/src/Router.tsx

+14-22
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import React, { useEffect, useState, useMemo, Suspense } from 'react';
22
import { useRecoilState } from 'recoil';
3-
import { Route, Switch, Redirect, useLocation } from 'react-router-dom';
3+
import { Route, Routes, Navigate, useLocation } from 'react-router-dom';
44

55
import LandingPage from './pages/LandingPage';
66
import MainPage from './pages/MainPage';
7-
import { useSocketSendUser, useSocketSend } from '@/lib/hooks';
7+
import { useSocketSend } from '@/lib/hooks';
88
import userAtom from '@/recoil/user';
99
import AdminPage from './pages/AdminPage';
1010
import LogInPage from './pages/LogInPage';
@@ -13,13 +13,13 @@ import { getUser } from './lib/api/user';
1313
import { Spinner } from './lib/design';
1414
import { UserState } from './recoil/user/atom';
1515
import { Header } from './layers';
16-
16+
1717
const WorkPage = React.lazy(() => import('./pages/WorkPage'));
1818

1919
const Router = () => {
2020
const [userState, setUserState] = useRecoilState(userAtom);
2121
const [loading, setLoading] = useState(true);
22-
const emitLoginEvent = useSocketSendUser('LOGIN');
22+
const emitLoginEvent = useSocketSend('LOGIN');
2323

2424
useEffect(() => {
2525
if (!document.cookie.match('status')) {
@@ -52,48 +52,40 @@ const Router = () => {
5252
) : (
5353
<>
5454
{userState?.email && <Header />}
55-
<Switch>
56-
<Route
57-
exact
58-
path="/"
59-
render={() => (userState?.email ? <MainPage /> : <LandingPage />)}
60-
/>
55+
<Routes>
56+
<Route path="/" element={userState?.email ? <MainPage /> : <LandingPage />} />
6157
<Route
62-
exact
6358
path="/work"
64-
render={() =>
59+
element={
6560
userState?.email ? (
6661
<Suspense fallback={<Spinner widthLevel={12} />}>
6762
<WorkPage />
6863
</Suspense>
6964
) : (
70-
<Redirect to="/" />
65+
<Navigate to={'/'} />
7166
)
7267
}
7368
/>
7469
<Route
75-
exact
7670
path="/setting"
77-
render={() => (userState?.email ? <AdminPage /> : <Redirect to="/" />)}
71+
element={userState?.email ? <AdminPage /> : <Navigate to={'/'} />}
7872
/>
7973
<Route
80-
exact
8174
path="/login"
82-
render={() => (userState?.email ? <Redirect to="/" /> : <LogInPage />)}
75+
element={userState?.email ? <Navigate to={'/'} /> : <LogInPage />}
8376
/>
8477
<Route
85-
exact
8678
path="/signup"
87-
render={() =>
79+
element={
8880
userState?.email ? (
89-
<Redirect to="/" />
81+
<Navigate to={'/'} />
9082
) : (
9183
<SignUpPage token={query.get('token') ?? ''} />
9284
)
9385
}
9486
/>
95-
<Redirect from="*" to="/" />
96-
</Switch>
87+
<Route path="*" element={<Navigate to={'/'} />} />
88+
</Routes>
9789
</>
9890
)}
9991
</>

client/src/components/BackLogItem/index.tsx

+14-26
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
1-
import React, { useState } from 'react';
1+
import React, { useEffect, useState } from 'react';
2+
import { useRecoilRefresher_UNSTABLE } from 'recoil';
23
import S from './style';
34
import arrow from '@public/icons/chevron-down.svg';
4-
import BackLogTask from '../BackLogTask';
5-
import { getTasksByStoryId } from '@/lib/api/task';
6-
import { BackLogTaskProps } from '@/types/task';
7-
import * as avatar from '@/lib/common/avatar';
8-
import { ImageType } from '@/types/image';
5+
6+
import { tasksSelector } from '@/recoil/story';
7+
import BackLogTaskContainer from '../BackLogTaskContainer';
98

109
const BackLogItem = ({ name, id }: { name: string; id: number }) => {
1110
const [clicked, setClicked] = useState(false);
12-
const [tasks, setTasks] = useState<Array<BackLogTaskProps>>([]);
13-
const clickEventListener = async () => {
11+
12+
const refresh = useRecoilRefresher_UNSTABLE(tasksSelector(id));
13+
useEffect(() => {
14+
return refresh();
15+
}, [refresh]);
16+
17+
const clickEventListener = () => {
1418
const newClicked = !clicked;
1519
setClicked(newClicked);
1620
if (!newClicked) return;
17-
const newTasks = await getTasksByStoryId(id);
18-
setTasks(newTasks ?? []);
1921
};
22+
2023
return (
2124
<div>
2225
<S.ItemContainer>
@@ -25,22 +28,7 @@ const BackLogItem = ({ name, id }: { name: string; id: number }) => {
2528
<S.ToggleImg src={arrow} click={clicked} />
2629
</S.ToggleButton>
2730
</S.ItemContainer>
28-
<S.TaskContainer click={clicked}>
29-
{tasks.length ? (
30-
tasks.map((el) => (
31-
<BackLogTask
32-
key={el.id}
33-
name={el.user}
34-
imageURL={avatar[el.userImage as ImageType]}
35-
task={el.name}
36-
/>
37-
))
38-
) : (
39-
<S.UndefinedItemContainer>
40-
<S.UndefinedText>칸반보드의 스토리를 클릭하여 Task를 추가해보세요</S.UndefinedText>
41-
</S.UndefinedItemContainer>
42-
)}
43-
</S.TaskContainer>
31+
{clicked && <BackLogTaskContainer storyId={id} />}
4432
</div>
4533
);
4634
};

client/src/components/BackLogItem/style.ts

-36
Original file line numberDiff line numberDiff line change
@@ -38,45 +38,9 @@ const ToggleButton = styled.button`
3838
right: 10px;
3939
`;
4040

41-
const TaskContainer = styled.ul<IsClick>`
42-
display: ${({ click }) => (click ? 'block' : 'none')};
43-
animation-duration: 0.3s;
44-
animation-name: slidein;
45-
@keyframes slidein {
46-
from {
47-
opacity: 0;
48-
transform: translateY(-10px);
49-
}
50-
51-
to {
52-
opacity: 1;
53-
transform: translateY(0px);
54-
}
55-
}
56-
`;
57-
58-
const UndefinedItemContainer = styled.ul`
59-
width: 705px;
60-
height: 70px;
61-
62-
display: flex;
63-
flex-direction: row;
64-
align-items: center;
65-
justify-content: center;
66-
67-
background-color: ${({ theme }) => theme.color.gray200};
68-
`;
69-
70-
const UndefinedText = styled.span`
71-
font: ${({ theme }) => theme.font.body_regular};
72-
`;
73-
7441
export default {
7542
ItemContainer,
7643
StoryText,
7744
ToggleImg,
7845
ToggleButton,
79-
TaskContainer,
80-
UndefinedItemContainer,
81-
UndefinedText,
8246
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from 'react';
2+
import Styled from './style';
3+
import { useRecoilValue } from 'recoil';
4+
5+
import * as avatar from '@/lib/common/avatar';
6+
import { ImageType } from '@/types/image';
7+
import { tasksSelector } from '@/recoil/story';
8+
import BackLogTask from '@/components/BackLogTask';
9+
10+
const BackLogTaskContainer = ({ storyId }: { storyId: number }) => {
11+
const tasksState = useRecoilValue(tasksSelector(storyId));
12+
return (
13+
<Styled.TaskContainer>
14+
{tasksState.length ? (
15+
tasksState.map((el) => (
16+
<BackLogTask
17+
key={el.id}
18+
name={el.user}
19+
imageURL={avatar[el.userImage as ImageType]}
20+
task={el.name}
21+
/>
22+
))
23+
) : (
24+
<Styled.UndefinedItemContainer>
25+
<Styled.UndefinedText>
26+
칸반보드의 스토리를 클릭하여 Task를 추가해보세요
27+
</Styled.UndefinedText>
28+
</Styled.UndefinedItemContainer>
29+
)}
30+
</Styled.TaskContainer>
31+
);
32+
};
33+
34+
export default BackLogTaskContainer;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import styled from 'styled-components';
2+
3+
const Styled = {
4+
TaskContainer: styled.ul`
5+
animation-duration: 0.3s;
6+
animation-name: slidein;
7+
@keyframes slidein {
8+
from {
9+
opacity: 0;
10+
transform: translateY(-10px);
11+
}
12+
13+
to {
14+
opacity: 1;
15+
transform: translateY(0px);
16+
}
17+
}
18+
`,
19+
UndefinedItemContainer: styled.ul`
20+
width: 705px;
21+
height: 70px;
22+
23+
display: flex;
24+
flex-direction: row;
25+
align-items: center;
26+
justify-content: center;
27+
28+
background-color: ${({ theme }) => theme.color.gray200};
29+
`,
30+
31+
UndefinedText: styled.span`
32+
font: ${({ theme }) => theme.font.body_regular};
33+
`,
34+
};
35+
36+
export default Styled;

client/src/components/EpicEntryItem/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ const EpicEntryItem = ({ handleDragStart, handleDrop, epicData, isEmpty }: EpicE
6767
setShowEditModal(false);
6868

6969
await updateEpicById(epicData.id, updatedEpic);
70-
emitUpdateEpic(epicData.id);
70+
emitUpdateEpic(epicData.id, userState.currentProjectId);
7171
};
7272

7373
return (

client/src/components/KanbanColumn/KanbanAddBtn/index.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const KanbanAddBtn = () => {
3030
order: listLargestOrder,
3131
projectId: userState.currentProjectId,
3232
});
33+
if (storyId === undefined) return;
3334

3435
setStoryList((prev) =>
3536
produce(prev, (draft) => {
@@ -41,8 +42,6 @@ const KanbanAddBtn = () => {
4142
});
4243
}),
4344
);
44-
45-
emitNewStory(storyId, userState.currentProjectId);
4645
};
4746

4847
return (

0 commit comments

Comments
 (0)