Skip to content

Commit 3f9bd7c

Browse files
authored
Merge pull request #17 from Team-Cherry-Pick/task/RP-5
RP-5 메인 페이지 퍼블리싱
2 parents 45ad936 + 34630fc commit 3f9bd7c

24 files changed

+1202
-98
lines changed

package-lock.json

Lines changed: 469 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@
1212
"build-storybook": "storybook build"
1313
},
1414
"dependencies": {
15+
"@tanstack/react-query": "^5.74.4",
16+
"@tanstack/react-query-devtools": "^5.74.6",
1517
"axios": "^1.8.4",
1618
"jotai": "^2.12.3",
1719
"lucide-react": "^0.501.0",
1820
"path": "^0.12.7",
1921
"react": "^18.2.0",
2022
"react-dom": "^18.2.0",
23+
"react-icons": "^5.5.0",
2124
"react-router-dom": "^7.5.0",
2225
"styled-components": "^6.1.17"
2326
},
@@ -56,6 +59,7 @@
5659
"typescript": "~5.7.2",
5760
"typescript-eslint": "^8.24.1",
5861
"vite": "^6.2.0",
62+
"vite-plugin-svgr": "^4.3.0",
5963
"vitest": "^3.1.1"
6064
},
6165
"eslintConfig": {

src/App.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,32 @@ import { useEffect } from 'react';
22
import { useAtom } from 'jotai';
33
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
44
import { ThemeProvider } from 'styled-components';
5-
import GlobalStyle from './styles/global';
5+
6+
import GlobalStyle from '@/styles/global';
67
import { themeAtom } from '@/store/theme';
8+
import { lightTheme, darkTheme } from '@/styles/theme';
9+
import useSystemTheme from '@/hooks/useSystemTheme';
10+
711
import MainPage from '@/pages/main/MainPage';
812
import LoginPage from '@/pages/login/LoginPage';
913
// import UserPage from '@/pages/user/UserPage';
1014
import ProductDetailPage from '@/pages/product-detail/ProductDetailPage';
1115
import ProductUploadPage from '@/pages/product-upload/ProductUploadPage';
1216
import ErrorPage from '@/pages/error/ErrorPage';
13-
import { ErrorModal } from '@/components/common/Modal/ErrorModal';
14-
import { lightTheme, darkTheme } from '@/styles/theme';
1517
import TestPage from '@/test/TestPage';
1618

17-
function App() {
19+
import { ErrorModal } from '@/components/common/Modal/ErrorModal';
20+
21+
const App = () => {
22+
const prefersDark = useSystemTheme();
1823
const [theme, setTheme] = useAtom(themeAtom);
1924

20-
// 다크모드 초기 설정
25+
// 초기 테마 설정
2126
useEffect(() => {
2227
const stored = localStorage.getItem('theme') as 'light' | 'dark' | null;
2328
if (stored) {
2429
setTheme(stored);
2530
} else {
26-
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
2731
setTheme(prefersDark ? 'dark' : 'light');
2832
}
2933
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -47,6 +51,6 @@ function App() {
4751
</Router>
4852
</ThemeProvider>
4953
);
50-
}
54+
};
5155

5256
export default App;

src/assets/icons/search-Icon.svg

Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 70 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,95 @@
11
import styled from 'styled-components';
2-
import { typography } from '@/styles/global/typography';
32

43
export const CardWrapper = styled.div`
5-
width: 272px;
6-
height: 422px;
7-
border-radius: 16px;
8-
overflow: hidden;
9-
background-color: white;
10-
border: 1px solid ${({ theme }) => theme.colors.neutral[50]};
11-
`;
4+
width: 100%;
5+
border-radius: ${({ theme }) => theme.radius[3]};
6+
overflow: hidden;
7+
background-color: ${({ theme }) => theme.colors.background.card};
8+
border: 1px solid ${({ theme }) => theme.colors.border.card};
9+
display: flex;
10+
flex-direction: column;
11+
`;
1212

1313
export const ImageBox = styled.div`
14-
width: 272px;
15-
height: 272px;
16-
aspect-ratio: 1 / 1; // 1대 1 비율 유지
17-
background-color: ${({ theme }) => theme.colors.neutral[50]};
18-
`;
14+
width: 100%;
15+
aspect-ratio: 1 / 1;
16+
background-color: ${({ theme }) => theme.colors.neutral[50]};
17+
`;
1918

2019
export const InfoBox = styled.div`
21-
height: calc(100% - 272px);
22-
padding: 16px;
23-
display: flex;
24-
flex-direction: column;
25-
justify-content: space-between;
26-
`;
20+
flex: 1;
21+
padding: ${({ theme }) => theme.spacing[4]};
22+
display: flex;
23+
flex-direction: column;
24+
justify-content: space-between;
25+
`;
2726

2827
export const Title = styled.div`
29-
color: ${({ theme }) => theme.colors.content.main};
30-
font-size: ${typography.size.base};
31-
font-weight: ${typography.weight.semibold};
32-
margin-bottom: 8px;
33-
`;
28+
color: ${({ theme }) => theme.colors.content.main};
29+
font-size: ${({ theme }) => theme.typography.size.base};
30+
font-weight: ${({ theme }) => theme.typography.weight.semibold};
31+
line-height: ${({ theme }) => theme.typography.lineHeight.base};
32+
margin-bottom: ${({ theme }) => theme.spacing[2]};
33+
`;
3434

3535
export const TagRow = styled.div`
36-
display: flex;
37-
align-items: center;
38-
justify-content: flex-start;
39-
`;
36+
display: flex;
37+
align-items: center;
38+
gap: ${({ theme }) => theme.spacing[2]};
39+
`;
4040

4141
export const Store = styled.span`
42-
color: ${({ theme }) => theme.colors.content.sub};
43-
font-size: ${typography.size.sm};
44-
font-weight: ${typography.weight.semibold};
45-
margin-right: 9px;
46-
`;
42+
color: ${({ theme }) => theme.colors.content.sub};
43+
font-size: ${({ theme }) => theme.typography.size.sm};
44+
font-weight: ${({ theme }) => theme.typography.weight.semibold};
45+
`;
4746

4847
export const Tags = styled.span`
49-
color: ${({ theme }) => theme.colors.content.sub};
50-
font-size: ${typography.size.sm};
51-
font-weight: ${typography.weight.regular};
52-
margin-left: 8px;
53-
white-space: nowrap;
54-
overflow: hidden;
55-
text-overflow: ellipsis;
56-
`;
48+
color: ${({ theme }) => theme.colors.content.sub};
49+
font-size: ${({ theme }) => theme.typography.size.sm};
50+
font-weight: ${({ theme }) => theme.typography.weight.regular};
51+
white-space: nowrap;
52+
overflow: hidden;
53+
text-overflow: ellipsis;
54+
`;
5755

5856
export const PriceRow = styled.div`
59-
display: flex;
60-
margin: 10px 0 4px 0;
61-
gap: 8px;
62-
align-items: flex-end;
63-
justify-content: flex-end;
64-
`;
57+
display: flex;
58+
justify-content: flex-end;
59+
align-items: flex-end;
60+
gap: ${({ theme }) => theme.spacing[2]};
61+
margin: ${({ theme }) => `${theme.spacing[2]} 0 ${theme.spacing[1]} 0`};
62+
`;
6563

6664
export const Percent = styled.span`
67-
color: ${({ theme }) => theme.colors.primary};
68-
font-size: ${typography.size.lg};
69-
font-weight: ${typography.weight.semibold};
70-
`;
65+
color: ${({ theme }) => theme.colors.primary};
66+
font-size: ${({ theme }) => theme.typography.size.lg};
67+
font-weight: ${({ theme }) => theme.typography.weight.semibold};
68+
`;
7169

7270
export const Price = styled.span`
73-
color: ${({ theme }) => theme.colors.content.main};
74-
font-size: ${typography.size.lg};
75-
font-weight: ${typography.weight.semibold};
76-
`;
71+
color: ${({ theme }) => theme.colors.content.main};
72+
font-size: ${({ theme }) => theme.typography.size.lg};
73+
font-weight: ${({ theme }) => theme.typography.weight.semibold};
74+
`;
7775

7876
export const Meta = styled.div`
79-
display: flex;
80-
justify-content: flex-end;
81-
align-items: center;
82-
gap: 4px;
83-
font-size: ${typography.size.xs};
84-
color: ${({ theme }) => theme.colors.content.sub};
77+
display: flex;
78+
justify-content: flex-end;
79+
align-items: center;
80+
gap: ${({ theme }) => theme.spacing[1]};
81+
font-size: ${({ theme }) => theme.typography.size.xs};
82+
color: ${({ theme }) => theme.colors.content.sub};
8583
86-
svg {
87-
color: ${({ theme }) => theme.colors.content.tertiary};
88-
width: 10px;
89-
height: 10px;
90-
margin-right: 4px;
91-
vertical-align: middle;
92-
}
84+
svg {
85+
color: ${({ theme }) => theme.colors.content.tertiary};
86+
width: 10px;
87+
height: 10px;
88+
vertical-align: middle;
89+
}
9390
94-
.divider {
95-
color: ${({ theme }) => theme.colors.content.tertiary}; // ✅ 이게 핵심
96-
margin: 0 4px;
97-
}
98-
`;
91+
.divider {
92+
margin: 0 ${({ theme }) => theme.spacing[1]};
93+
color: ${({ theme }) => theme.colors.content.tertiary};
94+
}
95+
`;

src/hooks/useFilter.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { useAtom } from 'jotai';
2+
import { filterAtom } from '@/store/filter';
3+
4+
export const useFilter = () => {
5+
const [filter, setFilter] = useAtom(filterAtom);
6+
7+
const toggleFilter = (key: keyof typeof filter) => {
8+
setFilter(prev => ({ ...prev, [key]: !prev[key] }));
9+
};
10+
11+
const resetFilter = () => {
12+
setFilter({ soldOut: false, freeShipping: false, overseas: false });
13+
};
14+
15+
return {
16+
filter,
17+
toggleFilter,
18+
resetFilter,
19+
};
20+
};

src/hooks/useInfiniteDeals.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// hooks/useInfiniteDeals.ts
2+
import { useInfiniteQuery } from '@tanstack/react-query';
3+
import { fetchDeals } from '@/store/deals';
4+
5+
export const useInfiniteDeals = () => {
6+
return useInfiniteQuery({
7+
queryKey: ['deals'],
8+
queryFn: ({ pageParam = 1 }) => fetchDeals(pageParam),
9+
initialPageParam: 1,
10+
getNextPageParam: (lastPage, pages) => {
11+
return lastPage.hasMore ? pages.length + 1 : undefined;
12+
},
13+
});
14+
};

src/hooks/useSystemTheme.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useEffect, useState } from 'react';
2+
3+
const useSystemTheme = () => {
4+
const [isDarkMode, setIsDarkMode] = useState<boolean>(
5+
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
6+
);
7+
8+
useEffect(() => {
9+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
10+
11+
const handleChange = (e: MediaQueryListEvent) => {
12+
setIsDarkMode(e.matches);
13+
};
14+
15+
mediaQuery.addEventListener('change', handleChange);
16+
17+
return () => {
18+
mediaQuery.removeEventListener('change', handleChange);
19+
};
20+
}, []);
21+
22+
return isDarkMode;
23+
};
24+
25+
export default useSystemTheme;

src/main.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@ import GlobalStyle from '@/styles/global';
66
import { ThemeProvider } from 'styled-components';
77
import { lightTheme } from '@/styles/theme';
88

9+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
10+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
11+
12+
const queryClient = new QueryClient();
13+
914
ReactDOM.createRoot(document.getElementById('root')!).render(
1015
<React.StrictMode>
11-
<ThemeProvider theme={lightTheme}>
12-
<FontStyle />
13-
<GlobalStyle />
14-
<App />
15-
</ThemeProvider>
16+
<QueryClientProvider client={queryClient}>
17+
<ThemeProvider theme={lightTheme}>
18+
<FontStyle />
19+
<GlobalStyle />
20+
<App />
21+
<ReactQueryDevtools initialIsOpen={false} />
22+
</ThemeProvider>
23+
</QueryClientProvider>
1624
</React.StrictMode>
1725
);

0 commit comments

Comments
 (0)