Skip to content

Commit d4b9ea5

Browse files
Merge pull request #204 from sinyroom/React-문지영-sprint6
[문지영] sprint6
2 parents 1269a88 + 9fe9a81 commit d4b9ea5

File tree

15 files changed

+478
-35
lines changed

15 files changed

+478
-35
lines changed

src/App.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Header from './components/Layout/Header';
66
import HomePage from './components/pages/HomePage';
77
import LoginPage from './components/pages/LoginPage';
88
import MarketPage from './components/pages/MarketPage/MarketPage';
9-
import AddItemPage from './components/pages/AddItemPage';
9+
import AddItemPage from './components/pages/AddItemPage/AddItemPage';
1010
import CommunityFeedPage from './components/pages/CommunityFeedPage';
1111

1212
function App() {

src/assets/images/icons/ic_X.svg

Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading
97.9 KB
Loading

src/components/Layout/Header.jsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NavLink, Link } from 'react-router-dom';
1+
import { NavLink, Link, useLocation } from 'react-router-dom';
22
import styled from 'styled-components';
33

44
import { FontTypes, ColorTypes } from '../../styles/theme';
@@ -8,6 +8,9 @@ import textLogo from '../../assets/images/logo/textlogo.svg';
88
import profile from '../../assets/images/icons/ic_profile.png';
99

1010
function Header() {
11+
const location = useLocation();
12+
const isMarketActive = location.pathname === '/items' || location.pathname === '/additem';
13+
1114
return (
1215
<HeaderContainer>
1316
<HeaderLeft>
@@ -31,7 +34,12 @@ function Header() {
3134
<StNavLink to="/community">자유게시판</StNavLink>
3235
</Li>
3336
<Li>
34-
<StNavLink to="/items">중고마켓</StNavLink>
37+
<StNavLink
38+
to="/items"
39+
$isActive={isMarketActive}
40+
>
41+
중고마켓
42+
</StNavLink>
3543
</Li>
3644
</Ul>
3745
</nav>
@@ -88,6 +96,9 @@ const Li = styled.li`
8896
`;
8997

9098
const StNavLink = styled(NavLink)`
99+
color: ${({ $isActive, theme }) =>
100+
$isActive ? theme.colors[ColorTypes.PRIMARY_100] : theme.colors[ColorTypes.SECONDARY_GRAY_600]};
101+
91102
&.active {
92103
color: ${({ theme }) => theme.colors[ColorTypes.PRIMARY_100]};
93104
}

src/components/UI/DropdownList.jsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,18 @@ const DropdownListContainer = styled.div`
6464
}
6565
`;
6666

67+
const StSortButton = styled.div`
68+
width: 100%;
69+
height: 100%;
70+
display: flex;
71+
align-items: center;
72+
justify-content: center;
73+
`;
74+
6775
const StSortIcon = styled.img`
6876
width: 26px;
6977
height: 26px;
70-
`;
71-
72-
const StSortButton = styled.button`
73-
width: 100%;
74-
height: 100%;
78+
display: inline-block;
7579
`;
7680

7781
const SortWrapper = styled.div`

src/components/UI/ImageUpload.jsx

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { useState, useEffect } from 'react';
2+
import styled from 'styled-components';
3+
4+
import { ColorTypes, FontTypes } from '../../styles/theme';
5+
import { applyFontStyles } from '../../styles/mixins';
6+
7+
import PlusIcon from '../../assets/images/icons/ic_plus.svg';
8+
import XIcon from '../../assets/images/icons/ic_X.svg';
9+
10+
function ImageUpload() {
11+
const [previewUrl, setPreviewUrl] = useState(null);
12+
const [error, setError] = useState('');
13+
14+
const handleImageUpload = (e) => {
15+
const file = e.target.files?.[0];
16+
if (!file) return;
17+
18+
const preview = URL.createObjectURL(file);
19+
setPreviewUrl(preview);
20+
21+
if (previewUrl) {
22+
setError('*이미지 등록은 최대 1개까지 가능합니다.');
23+
e.target.value = '';
24+
return;
25+
}
26+
27+
setError('');
28+
};
29+
30+
const handleImageRemove = () => {
31+
setPreviewUrl(null);
32+
};
33+
34+
useEffect(() => {
35+
return () => {
36+
if (previewUrl) {
37+
URL.revokeObjectURL(previewUrl);
38+
}
39+
};
40+
}, [previewUrl]);
41+
42+
return (
43+
<Container>
44+
<label>상품 이미지</label>
45+
46+
<Wrapper>
47+
<StInput
48+
type="file"
49+
placeholder="이미지를 추가해주세요"
50+
accept="image/*"
51+
id="file-input"
52+
onChange={handleImageUpload}
53+
/>
54+
<ImageWrapper>
55+
<StLabel htmlFor="file-input">
56+
<img
57+
src={PlusIcon}
58+
alt="plus"
59+
width={28}
60+
height={28}
61+
/>
62+
이미지 등록
63+
</StLabel>
64+
</ImageWrapper>
65+
66+
{previewUrl && (
67+
<PreviewImage>
68+
<StXIcon
69+
src={XIcon}
70+
alt="x"
71+
width={20}
72+
height={20}
73+
onClick={handleImageRemove}
74+
/>
75+
<StImage
76+
src={previewUrl}
77+
alt="샘플이미지"
78+
/>
79+
</PreviewImage>
80+
)}
81+
</Wrapper>
82+
83+
{error && <ErrorMessage>{error}</ErrorMessage>}
84+
</Container>
85+
);
86+
}
87+
88+
export default ImageUpload;
89+
90+
const Container = styled.div`
91+
display: flex;
92+
flex-direction: column;
93+
gap: 16px;
94+
`;
95+
96+
const Wrapper = styled.div`
97+
position: relative;
98+
display: flex;
99+
gap: 24px;
100+
`;
101+
102+
const ImageWrapper = styled.div`
103+
display: flex;
104+
`;
105+
106+
const StInput = styled.input`
107+
position: absolute;
108+
opacity: 0;
109+
width: 0;
110+
height: 0;
111+
overflow: hidden;
112+
`;
113+
114+
const StLabel = styled.label`
115+
display: flex;
116+
align-items: center;
117+
justify-content: center;
118+
flex-direction: column;
119+
gap: 8px;
120+
121+
width: 168px;
122+
height: 168px;
123+
border-radius: 12px;
124+
background-color: ${({ theme }) => theme.colors[ColorTypes.SECONDARY_GRAY_100]};
125+
${applyFontStyles(FontTypes.REGULAR16, ColorTypes.SECONDARY_GRAY_400)}
126+
cursor: pointer;
127+
transition: all 0.3s ease;
128+
129+
&:hover {
130+
background-color: ${({ theme }) => theme.colors[ColorTypes.SECONDARY_GRAY_200]};
131+
}
132+
`;
133+
134+
const PreviewImage = styled.div`
135+
position: relative;
136+
`;
137+
138+
const StImage = styled.img`
139+
width: 168px;
140+
height: 168px;
141+
border-radius: 12px;
142+
`;
143+
144+
const StXIcon = styled.img`
145+
position: absolute;
146+
top: 14px;
147+
right: 13px;
148+
`;
149+
150+
const ErrorMessage = styled.span`
151+
${applyFontStyles(FontTypes.REGULAR16, ColorTypes.ERROR)}
152+
`;

src/components/UI/InputField.jsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import styled from 'styled-components';
2+
3+
function InputField({ label, type, placeholder, isTextArea, value, onChange }) {
4+
return isTextArea ? (
5+
<Container>
6+
<label htmlFor="textarea">{label}</label>
7+
<textarea
8+
id="textarea"
9+
placeholder={placeholder}
10+
value={value}
11+
onChange={onChange}
12+
rows={10}
13+
/>
14+
</Container>
15+
) : (
16+
<Container>
17+
<label htmlFor="input">{label}</label>
18+
<input
19+
id="input"
20+
type={type}
21+
placeholder={placeholder}
22+
value={value}
23+
onChange={onChange}
24+
/>
25+
</Container>
26+
);
27+
}
28+
29+
export default InputField;
30+
31+
const Container = styled.div`
32+
display: flex;
33+
flex-direction: column;
34+
gap: 16px;
35+
`;

src/components/UI/Taginput.jsx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import styled from 'styled-components';
2+
import { useState } from 'react';
3+
4+
import { applyFontStyles } from '../../styles/mixins';
5+
import { ColorTypes, FontTypes } from '../../styles/theme';
6+
import XIcon from '../../assets/images/icons/IC_X.svg';
7+
8+
function TagInput({ tags, setTags }) {
9+
const [inputValue, setInputValue] = useState('');
10+
11+
const handleTagChange = (e) => {
12+
if (e.key === 'Enter') {
13+
const trimmed = inputValue.trim();
14+
if (trimmed === '') return;
15+
16+
const newTag = `#${trimmed}`;
17+
if (tags.includes(newTag)) return;
18+
19+
setTags([...tags, newTag]);
20+
setInputValue('');
21+
}
22+
};
23+
24+
const handleTagRemove = (removeTag) => {
25+
setTags(tags.filter((tag) => tag !== removeTag));
26+
};
27+
28+
return (
29+
<Container>
30+
<label htmlFor="tag">태그</label>
31+
32+
<Wrapper>
33+
<input
34+
id="tag"
35+
type="text"
36+
placeholder="태그를 입력해주세요"
37+
onKeyUp={handleTagChange}
38+
value={inputValue}
39+
onChange={(e) => setInputValue(e.target.value)}
40+
/>
41+
42+
<TagList>
43+
{tags.map((tag) => (
44+
<TagWrapper key={tag}>
45+
<span>{tag}</span>
46+
<img
47+
src={XIcon}
48+
alt="x"
49+
width={22}
50+
height={24}
51+
onClick={() => handleTagRemove(tag)}
52+
/>
53+
</TagWrapper>
54+
))}
55+
</TagList>
56+
</Wrapper>
57+
</Container>
58+
);
59+
}
60+
61+
export default TagInput;
62+
63+
const Container = styled.div`
64+
display: flex;
65+
flex-direction: column;
66+
gap: 16px;
67+
`;
68+
69+
const Wrapper = styled.div`
70+
display: flex;
71+
flex-direction: column;
72+
justify-content: center;
73+
gap: 14px;
74+
`;
75+
76+
const TagList = styled.div`
77+
display: flex;
78+
flex-wrap: wrap;
79+
gap: 10px;
80+
`;
81+
82+
const TagWrapper = styled.div`
83+
display: flex;
84+
justify-content: center;
85+
gap: 10px;
86+
87+
max-width: 93px;
88+
width: fit-content;
89+
max-width: 100%;
90+
height: 36px;
91+
background-color: ${({ theme }) => theme.colors[ColorTypes.SECONDARY_GRAY_100]};
92+
border-radius: 26px;
93+
padding: 6px 12px;
94+
95+
span {
96+
${applyFontStyles(FontTypes.REGULAR16, ColorTypes.SECONDARY_GRAY_800)}
97+
}
98+
`;

src/components/pages/AddItemPage.jsx

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)