Skip to content

Commit efa5e78

Browse files
authored
feat: add routing (#6)
1 parent d85cd23 commit efa5e78

20 files changed

Lines changed: 243 additions & 153 deletions

File tree

package-lock.json

Lines changed: 59 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"@mui/icons-material": "^7.3.9",
2121
"@mui/material": "^7.3.9",
2222
"react": "^19.2.0",
23-
"react-dom": "^19.2.0"
23+
"react-dom": "^19.2.0",
24+
"react-router-dom": "^7.13.1"
2425
},
2526
"devDependencies": {
2627
"@eslint/js": "^9.39.1",
@@ -29,11 +30,11 @@
2930
"@types/react-dom": "^19.2.3",
3031
"@vitejs/plugin-react": "^5.1.1",
3132
"eslint": "^9.39.1",
32-
"husky": "^9.1.7",
33-
"lint-staged": "^16.3.2",
3433
"eslint-plugin-react-hooks": "^7.0.1",
3534
"eslint-plugin-react-refresh": "^0.4.24",
3635
"globals": "^16.5.0",
36+
"husky": "^9.1.7",
37+
"lint-staged": "^16.3.2",
3738
"prettier": "^3.8.1",
3839
"typescript": "~5.9.3",
3940
"typescript-eslint": "^8.48.0",

src/App.tsx

Lines changed: 13 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,18 @@
1-
import { useEffect, useMemo, useState } from 'react';
2-
import { CssBaseline, ThemeProvider, Box } from '@mui/material';
3-
import type { PaletteMode } from '@mui/material';
4-
import { Header } from './components/Header';
5-
import { Hero } from './components/Hero';
6-
import { Features } from './components/Features';
7-
import { Footer } from './components/Footer';
8-
import { getTheme } from './theme';
1+
import { Suspense } from 'react';
2+
import { useRoutes } from 'react-router-dom';
3+
import { Box, CircularProgress } from '@mui/material';
4+
import { routes } from './app/router';
95

10-
const STORAGE_KEY = 'dfsync-theme-mode';
11-
12-
function getInitialMode(): PaletteMode {
13-
const saved = localStorage.getItem(STORAGE_KEY);
14-
if (saved === 'light' || saved === 'dark') {
15-
return saved;
16-
}
17-
18-
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
19-
}
6+
const Loader = () => {
7+
return (
8+
<Box display="flex" justifyContent="center" alignItems="center" minHeight="100vh">
9+
<CircularProgress />
10+
</Box>
11+
);
12+
};
2013

2114
export default function App() {
22-
const [mode, setMode] = useState<PaletteMode>(getInitialMode);
23-
24-
useEffect(() => {
25-
localStorage.setItem(STORAGE_KEY, mode);
26-
}, [mode]);
15+
const element = useRoutes(routes);
2716

28-
const theme = useMemo(() => getTheme(mode), [mode]);
29-
30-
const handleToggleMode = () => {
31-
setMode((prev) => (prev === 'dark' ? 'light' : 'dark'));
32-
};
33-
34-
return (
35-
<ThemeProvider theme={theme}>
36-
<CssBaseline />
37-
38-
<Box
39-
sx={{
40-
minHeight: '100vh',
41-
background:
42-
mode === 'dark'
43-
? 'radial-gradient(circle at top, rgba(56,189,248,0.12), transparent 30%)'
44-
: 'radial-gradient(circle at top, rgba(37,99,235,0.10), transparent 30%)',
45-
}}
46-
>
47-
<Header mode={mode} onToggleMode={handleToggleMode} />
48-
<Hero />
49-
<Features />
50-
<Footer />
51-
</Box>
52-
</ThemeProvider>
53-
);
17+
return <Suspense fallback={<Loader />}>{element}</Suspense>;
5418
}

src/app/AppProviders.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { ThemeProvider, CssBaseline, type PaletteMode } from '@mui/material';
2+
import { getTheme } from '../theme';
3+
import { useEffect, useMemo, useState } from 'react';
4+
import { ThemeModeContext } from './themeContext';
5+
6+
type Props = {
7+
children: React.ReactNode;
8+
};
9+
10+
const STORAGE_KEY = 'dfsync-theme-mode';
11+
12+
function getInitialMode(): PaletteMode {
13+
const saved = localStorage.getItem(STORAGE_KEY);
14+
if (saved === 'light' || saved === 'dark') {
15+
return saved;
16+
}
17+
18+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
19+
}
20+
21+
export const AppProviders = ({ children }: Props) => {
22+
const [mode, setMode] = useState<PaletteMode>(getInitialMode);
23+
24+
useEffect(() => {
25+
localStorage.setItem(STORAGE_KEY, mode);
26+
}, [mode]);
27+
28+
const theme = useMemo(() => getTheme(mode), [mode]);
29+
30+
const toggleTheme = () => {
31+
setMode((prev) => (prev === 'dark' ? 'light' : 'dark'));
32+
};
33+
34+
return (
35+
<ThemeModeContext.Provider value={{ toggleTheme, mode }}>
36+
<ThemeProvider theme={theme}>
37+
<CssBaseline />
38+
{children}
39+
</ThemeProvider>
40+
</ThemeModeContext.Provider>
41+
);
42+
};

src/app/router.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { lazy } from 'react';
2+
import type { RouteObject } from 'react-router-dom';
3+
4+
const HomePage = lazy(() => import('../pages/Home/HomePage'));
5+
const NotFoundPage = lazy(() => import('../pages/NotFound/NotFoundPage'));
6+
7+
export const routes: RouteObject[] = [
8+
{
9+
path: '/',
10+
element: <HomePage />,
11+
},
12+
13+
{
14+
path: '*',
15+
element: <NotFoundPage />,
16+
},
17+
];

src/app/themeContext.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { createContext } from 'react';
2+
3+
export const ThemeModeContext = createContext({
4+
toggleTheme: () => {},
5+
mode: 'light',
6+
});
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Typography } from '@mui/material';
2+
import { Link as RouterLink } from 'react-router-dom';
23

3-
export function Brand() {
4+
export const Brand = () => {
45
return (
56
<Typography
6-
component="a"
7-
href="/"
7+
component={RouterLink}
8+
to="/"
89
sx={{
910
textDecoration: 'none',
1011
color: 'inherit',
@@ -14,4 +15,4 @@ export function Brand() {
1415
dfsync
1516
</Typography>
1617
);
17-
}
18+
};
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const items = [
3131
},
3232
];
3333

34-
export function Features() {
34+
export const Features = () => {
3535
return (
3636
<Container maxWidth="lg" sx={{ pb: { xs: 8, md: 12 } }}>
3737
<Stack spacing={2} sx={{ mb: 5 }}>
@@ -61,4 +61,4 @@ export function Features() {
6161
</Grid>
6262
</Container>
6363
);
64-
}
64+
};
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Box, Container, Link, Stack, Typography } from '@mui/material';
22

3-
export function Footer() {
3+
export const Footer = () => {
44
return (
55
<Box
66
component="footer"
@@ -43,4 +43,4 @@ export function Footer() {
4343
</Container>
4444
</Box>
4545
);
46-
}
46+
};
Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
import GitHubIcon from '@mui/icons-material/GitHub';
2-
import LightModeIcon from '@mui/icons-material/LightMode';
3-
import DarkModeIcon from '@mui/icons-material/DarkMode';
4-
import { AppBar, Box, Button, Container, IconButton, Toolbar } from '@mui/material';
5-
import type { PaletteMode } from '@mui/material';
6-
import { Brand } from './Brand';
2+
import { AppBar, Box, Button, Container, Toolbar } from '@mui/material';
3+
import { Brand } from '../Brand/Brand';
4+
import { ThemeToggle } from '../ThemeToggle/ThemeToggle.tsx';
75

8-
type HeaderProps = {
9-
mode: PaletteMode;
10-
onToggleMode: () => void;
11-
};
12-
13-
export function Header({ mode, onToggleMode }: HeaderProps) {
6+
export const Header = () => {
147
return (
158
<AppBar
169
position="sticky"
@@ -51,14 +44,10 @@ export function Header({ mode, onToggleMode }: HeaderProps) {
5144
>
5245
GitHub
5346
</Button>
54-
{/*<Box>*/}
55-
<IconButton color="inherit" onClick={onToggleMode} aria-label="toggle theme">
56-
{mode === 'dark' ? <LightModeIcon /> : <DarkModeIcon />}
57-
</IconButton>
58-
{/*</Box>*/}
47+
<ThemeToggle />
5948
</Box>
6049
</Toolbar>
6150
</Container>
6251
</AppBar>
6352
);
64-
}
53+
};

0 commit comments

Comments
 (0)