Skip to content

Commit 10d8308

Browse files
authored
Merge pull request #211 from boostcampwm-2021/feature/fe/user-info-api
๋Œ€์‹œ๋ณด๋“œ ์Šคํƒ ๋ชจ๋‹ฌ ๊ตฌํ˜„
2 parents acdc6f1 + 357bdb1 commit 10d8308

File tree

10 files changed

+287
-53
lines changed

10 files changed

+287
-53
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { useState } from 'react';
2+
import { IoSettingsOutline } from 'react-icons/io5';
3+
4+
import DashboardSettingCommon from 'src/components/buttons/dashboardSettings/Common';
5+
import DashboardTechStacksSettingModal from 'src/components/modals/DashboardTechStacksSettingModal';
6+
7+
function DashboardTechStacksSettingButton() {
8+
const [isModalShow, setIsModalShow] = useState(false);
9+
10+
const switchIsModalShow = () => {
11+
setIsModalShow((prevState) => !prevState);
12+
};
13+
14+
return (
15+
<DashboardSettingCommon
16+
icon={<IoSettingsOutline />}
17+
isModalShow={isModalShow}
18+
modal={<DashboardTechStacksSettingModal onClose={switchIsModalShow} />}
19+
onClick={switchIsModalShow}
20+
/>
21+
);
22+
}
23+
24+
export default DashboardTechStacksSettingButton;

โ€Žfrontend/src/components/cards/DashboardStackCard/index.tsx

Lines changed: 0 additions & 49 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { useRecoilValue } from 'recoil';
2+
3+
import CardCommon from 'src/components/cards/Common';
4+
import DashboardTechStacksSettingButton from 'src/components/buttons/dashboardSettings/DashboardTechStacksSettingButton';
5+
import { Col, Row } from 'src/components/Grid';
6+
7+
import {
8+
DASHBOARD_LEFT_SECTION_CARD_WIDTH,
9+
DASHBOARD_STACK_GRID_PADDING,
10+
} from 'src/globals/constants';
11+
12+
import { dashboardTechStacksSelector } from 'src/recoil/dashboardUserInfo';
13+
14+
import { Title, Stack, Field } from './style';
15+
16+
function DashboardTechStacksCard() {
17+
const dashboardTechStacks = useRecoilValue(dashboardTechStacksSelector);
18+
const fields = Object.keys(dashboardTechStacks);
19+
20+
return (
21+
<CardCommon width={DASHBOARD_LEFT_SECTION_CARD_WIDTH}>
22+
<Row justifyContent='space-between'>
23+
<Col padding={DASHBOARD_STACK_GRID_PADDING}>
24+
{fields.map((field) => (
25+
<Field key={field}>
26+
<Title>{field}</Title>
27+
<Row>
28+
{dashboardTechStacks[field].map((stack) => (
29+
<Stack color={stack.color!} key={stack.techStack}>
30+
{stack.techStack}
31+
</Stack>
32+
))}
33+
</Row>
34+
</Field>
35+
))}
36+
</Col>
37+
<DashboardTechStacksSettingButton />
38+
</Row>
39+
</CardCommon>
40+
);
41+
}
42+
43+
export default DashboardTechStacksCard;

โ€Žfrontend/src/components/cards/DashboardStackCard/style.ts renamed to โ€Žfrontend/src/components/cards/DashboardTechStacksCard/style.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@ const Title = styled.p`
1111
`;
1212

1313
const Stack = styled.p<Props>`
14+
margin: 4px;
1415
background: ${({ color }) => color};
1516
`;
1617

17-
export { Title, Stack };
18+
const Field = styled.div`
19+
display: flex;
20+
flex-direction: column;
21+
margin: 8px;
22+
`;
23+
24+
export { Title, Stack, Field };
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { useState } from 'react';
2+
import { useMutation } from 'react-query';
3+
import { useRecoilState, useRecoilValue } from 'recoil';
4+
import { IoMdSearch } from 'react-icons/io';
5+
import PropTypes from 'prop-types';
6+
7+
import ModalCommon from 'src/components/modals/Common';
8+
import InputCommon from 'src/components/inputs/Common';
9+
import IconButton from 'src/components/buttons/IconButton';
10+
import SearchedTechStacksModal from 'src/components/modals/SearchedTechStacksModal';
11+
import { Row, Col } from 'src/components/Grid';
12+
13+
import userAtom from 'src/recoil/user';
14+
import dashboardUserInfoAtom from 'src/recoil/dashboardUserInfo';
15+
16+
import { StackType } from 'src/types';
17+
18+
import { Fetcher } from 'src/utils';
19+
20+
import { Title, Label, Content } from './style';
21+
22+
interface Props {
23+
onClose: () => void;
24+
}
25+
26+
function DashboardTechStacksSettingModal({ onClose }: Props) {
27+
const user = useRecoilValue(userAtom);
28+
const [dashboardUserInfo, setDashboardUserInfo] = useRecoilState(dashboardUserInfoAtom);
29+
const [techStacks, setTechStacks] = useState<{ [field: string]: StackType[] }>(
30+
dashboardUserInfo.techStacks ?? {},
31+
);
32+
const [newField, setNewField] = useState('');
33+
const [techStack, setTechStack] = useState('');
34+
const [isModalShow, setIsModalShow] = useState(false);
35+
const fields = Object.keys(techStacks);
36+
const { mutate } = useMutation(
37+
() => Fetcher.putDashboardUserInfo(user, { ...dashboardUserInfo, techStacks }),
38+
{
39+
onSuccess: (dashboard) => {
40+
setDashboardUserInfo(dashboard);
41+
onClose();
42+
},
43+
},
44+
);
45+
46+
const handleConfirm = () => {
47+
mutate();
48+
};
49+
50+
const switchIsModalShow = () => {
51+
setIsModalShow((prevState) => !prevState);
52+
};
53+
54+
const handleSelect = (newTechStack: StackType) => {
55+
const newTechStacks = { ...techStacks };
56+
if (newTechStacks[newField]) {
57+
newTechStacks[newField] = [...newTechStacks[newField], newTechStack];
58+
} else {
59+
newTechStacks[newField] = [newTechStack];
60+
}
61+
setTechStack(newTechStack.techStack!);
62+
setTechStacks(newTechStacks);
63+
};
64+
65+
return (
66+
<ModalCommon
67+
width={800}
68+
onConfirm={handleConfirm}
69+
onClose={onClose}
70+
confirm='์ €์žฅ'
71+
close='์ทจ์†Œ'
72+
>
73+
<Row>
74+
<Col>
75+
<Row alignItems='center'>
76+
<Label>field</Label>
77+
<InputCommon bind={[newField, setNewField]} placeholder='' width={150} />
78+
</Row>
79+
<Row alignItems='center'>
80+
<Label>tech stack</Label>
81+
<InputCommon bind={[techStack, setTechStack]} placeholder='' width={150} />
82+
<IconButton onClick={switchIsModalShow}>
83+
<IoMdSearch />
84+
</IconButton>
85+
{isModalShow && (
86+
<SearchedTechStacksModal
87+
techStack={techStack}
88+
onClose={switchIsModalShow}
89+
onSelect={handleSelect}
90+
/>
91+
)}
92+
</Row>
93+
</Col>
94+
<Col>
95+
<Title>Tech Stacks</Title>
96+
<Col>
97+
{fields.map((field) => (
98+
<Col key={field} alignItems='center'>
99+
<p>{field}</p>
100+
<Row>
101+
{techStacks[field].map((stack) => (
102+
<Content color={stack.color!} key={stack.techStack}>
103+
{stack.techStack}
104+
</Content>
105+
))}
106+
</Row>
107+
</Col>
108+
))}
109+
</Col>
110+
</Col>
111+
</Row>
112+
</ModalCommon>
113+
);
114+
}
115+
116+
DashboardTechStacksSettingModal.propTypes = {
117+
onClose: PropTypes.func.isRequired,
118+
};
119+
120+
export default DashboardTechStacksSettingModal;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import styled from '@emotion/styled';
2+
3+
interface Props {
4+
color: string;
5+
}
6+
7+
const Label = styled.div`
8+
text-align: right;
9+
width: 164px;
10+
font-size: 24px;
11+
padding: 0 24px;
12+
margin: 8px;
13+
`;
14+
15+
const Title = styled.p`
16+
text-align: center;
17+
width: 200px;
18+
`;
19+
20+
const Content = styled.p<Props>`
21+
margin: 4px;
22+
background: ${({ color }) => color};
23+
`;
24+
25+
export { Label, Title, Content };
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { useState } from 'react';
2+
import { useQuery } from 'react-query';
3+
import PropTypes from 'prop-types';
4+
5+
import ModalCommon from 'src/components/modals/Common';
6+
import ButtonCommon from 'src/components/buttons/Common';
7+
import { Col } from 'src/components/Grid';
8+
9+
import { StackType } from 'src/types';
10+
11+
import Fetcher from 'src/utils/Fetcher';
12+
13+
interface Props {
14+
techStack: string;
15+
onClose: () => void;
16+
// eslint-disable-next-line no-unused-vars
17+
onSelect: (techStack: StackType) => void;
18+
}
19+
20+
function SearchedTechStacksModal({ techStack, onClose, onSelect }: Props) {
21+
const [selectedIndex, setSelectedIndex] = useState(-1);
22+
23+
const { data: searchedTechStacks } = useQuery(['dashboard', techStack], () =>
24+
Fetcher.getTechStacksSearch(techStack),
25+
);
26+
const handleClick = (index: number) => {
27+
if (index === selectedIndex) {
28+
setSelectedIndex(-1);
29+
} else {
30+
setSelectedIndex(index);
31+
}
32+
};
33+
const handleClose = () => {
34+
setSelectedIndex(-1);
35+
onClose();
36+
};
37+
const handleConfirm = () => {
38+
onSelect(searchedTechStacks![selectedIndex]);
39+
onClose();
40+
};
41+
42+
return (
43+
<ModalCommon onClose={handleClose} onConfirm={handleConfirm} confirm='์ถ”๊ฐ€' close='์ทจ์†Œ'>
44+
<Col>
45+
{(searchedTechStacks ?? [])!.map((searchedTechStack, index) => (
46+
<ButtonCommon onClick={() => handleClick(index)} clicked={index === selectedIndex}>
47+
{searchedTechStack.searchString}
48+
</ButtonCommon>
49+
))}
50+
</Col>
51+
</ModalCommon>
52+
);
53+
}
54+
SearchedTechStacksModal.propTypes = {
55+
techStack: PropTypes.string.isRequired,
56+
onClose: PropTypes.func.isRequired,
57+
onSelect: PropTypes.func.isRequired,
58+
};
59+
60+
export default SearchedTechStacksModal;

โ€Žfrontend/src/globals/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export const TIME_MIN_WIDTH = 60;
113113

114114
// DASHBOARD
115115
export const DASHBOARD_USER_INFO_COL_MARGIN = 8;
116-
export const DASHBOARD_STACK_GRID_PADDING = 8;
116+
export const DASHBOARD_STACK_GRID_PADDING = 16;
117117
export const DASHBOARD_LINK_ROW_PADDING = 24;
118118
export const DASHBOARD_HISTORY_MARGIN_LEFT = 50;
119119
export const DASHBOARD_STACK_TITLE_FONT_SIZE = 16;

โ€Žfrontend/src/pages/users/[username]/dashboard.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Header from 'src/components/Header';
44
import DashboardBasicCard from 'src/components/cards/DashboardBasicCard';
55
import DashboardHistoryCard from 'src/components/cards/DashboardHistoryCard';
66
import DashboardLinkCard from 'src/components/cards/DashboardLinkCard';
7+
import DashboardTeckStacksCard from 'src/components/cards/DashboardTechStacksCard';
78
import { Row, Col } from 'src/components/Grid';
89

910
import { DASHBOARD_DESCRIPTION } from 'src/globals/descriptions';
@@ -28,6 +29,9 @@ function Dashboard() {
2829
<DashboardLinkCard />
2930
</Row>
3031
<Row>
32+
<Col>
33+
<DashboardTeckStacksCard />
34+
</Col>
3135
<DashboardHistoryCard />
3236
</Row>
3337
</Col>

โ€Žfrontend/src/utils/Fetcher.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,9 @@ class Fetcher {
212212
return result.data;
213213
}
214214

215-
static async getTechStacksSearch(query: string): Promise<ReturnType<StackType[]>> {
215+
static async getTechStacksSearch(query: string): Promise<StackType[]> {
216216
const result = await axios.get(`${baseURL}/${version}/techStacks/search?query=${query}`);
217-
return result.data;
217+
return result.data.data;
218218
}
219219

220220
static async postPost(

0 commit comments

Comments
ย (0)