reroute-js 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_/README.md +59 -0
- package/_/basic/package.json +23 -0
- package/_/basic/src/client/App.tsx +10 -0
- package/_/basic/src/client/components/Counter.tsx +15 -0
- package/_/basic/src/client/index.html +12 -0
- package/_/basic/src/client/index.tsx +5 -0
- package/_/basic/src/client/routes/[404].tsx +18 -0
- package/_/basic/src/client/routes/about.tsx +25 -0
- package/_/basic/src/client/routes/index.tsx +57 -0
- package/_/basic/src/index.ts +20 -0
- package/_/basic/tsconfig.json +26 -0
- package/_/blog/package.json +23 -0
- package/_/blog/src/client/App.tsx +10 -0
- package/_/blog/src/client/components/Counter.tsx +14 -0
- package/_/blog/src/client/components/RecentPosts.tsx +90 -0
- package/_/blog/src/client/index.html +13 -0
- package/_/blog/src/client/index.tsx +5 -0
- package/_/blog/src/client/routes/[404].tsx +21 -0
- package/_/blog/src/client/routes/about.tsx +31 -0
- package/_/blog/src/client/routes/blog/[404].tsx +21 -0
- package/_/blog/src/client/routes/blog/[layout].tsx +84 -0
- package/_/blog/src/client/routes/blog/[slug].tsx +11 -0
- package/_/blog/src/client/routes/blog/content/1-hello-world.tsx +27 -0
- package/_/blog/src/client/routes/blog/content/2-what-is-reroute.tsx +31 -0
- package/_/blog/src/client/routes/blog/index.tsx +70 -0
- package/_/blog/src/client/routes/index.tsx +63 -0
- package/_/blog/src/index.ts +20 -0
- package/_/blog/tsconfig.json +26 -0
- package/_/store/package.json +25 -0
- package/_/store/src/client/App.tsx +17 -0
- package/_/store/src/client/components/Header.tsx +40 -0
- package/_/store/src/client/components/ProductCard.tsx +51 -0
- package/_/store/src/client/index.html +17 -0
- package/_/store/src/client/index.tsx +7 -0
- package/_/store/src/client/lib/api.ts +153 -0
- package/_/store/src/client/routes/[404].tsx +63 -0
- package/_/store/src/client/routes/categories/[category].tsx +223 -0
- package/_/store/src/client/routes/categories/index.tsx +187 -0
- package/_/store/src/client/routes/index.tsx +126 -0
- package/_/store/src/client/routes/products/[id].tsx +233 -0
- package/_/store/src/client/routes/products/index.tsx +261 -0
- package/_/store/src/client/theme.css +306 -0
- package/_/store/src/index.ts +19 -0
- package/_/store/tsconfig.json +26 -0
- package/{packages/cli/bin.ts → cli/bin.d.ts} +1 -1
- package/cli/bin.d.ts.map +1 -0
- package/cli/bin.js +878 -0
- package/cli/bin.js.map +15 -0
- package/cli/index.d.ts +2 -0
- package/cli/index.d.ts.map +1 -0
- package/cli/index.js +147 -0
- package/cli/index.js.map +10 -0
- package/cli/src/cli.d.ts +8 -0
- package/cli/src/cli.d.ts.map +1 -0
- package/cli/src/commands/build.d.ts +8 -0
- package/cli/src/commands/build.d.ts.map +1 -0
- package/cli/src/commands/dev.d.ts +8 -0
- package/cli/src/commands/dev.d.ts.map +1 -0
- package/cli/src/commands/gen.d.ts +3 -0
- package/cli/src/commands/gen.d.ts.map +1 -0
- package/cli/src/commands/init.d.ts +8 -0
- package/cli/src/commands/init.d.ts.map +1 -0
- package/cli/src/libs/index.d.ts +2 -0
- package/cli/src/libs/index.d.ts.map +1 -0
- package/cli/src/libs/tailwind.d.ts +45 -0
- package/cli/src/libs/tailwind.d.ts.map +1 -0
- package/core/index.d.ts +2 -0
- package/core/index.d.ts.map +1 -0
- package/core/index.js +1117 -0
- package/core/index.js.map +25 -0
- package/core/src/bundler/hash.d.ts +2 -0
- package/core/src/bundler/hash.d.ts.map +1 -0
- package/core/src/bundler/index.d.ts +3 -0
- package/core/src/bundler/index.d.ts.map +1 -0
- package/core/src/bundler/transpile.d.ts +4 -0
- package/core/src/bundler/transpile.d.ts.map +1 -0
- package/core/src/content/discovery.d.ts +5 -0
- package/core/src/content/discovery.d.ts.map +1 -0
- package/core/src/content/index.d.ts +4 -0
- package/core/src/content/index.d.ts.map +1 -0
- package/core/src/content/metadata.d.ts +9 -0
- package/core/src/content/metadata.d.ts.map +1 -0
- package/core/src/content/registry.d.ts +2 -0
- package/core/src/content/registry.d.ts.map +1 -0
- package/core/src/index.d.ts +7 -0
- package/core/src/index.d.ts.map +1 -0
- package/core/src/ssr/data.d.ts +9 -0
- package/core/src/ssr/data.d.ts.map +1 -0
- package/core/src/ssr/index.d.ts +4 -0
- package/core/src/ssr/index.d.ts.map +1 -0
- package/core/src/ssr/modules.d.ts +8 -0
- package/core/src/ssr/modules.d.ts.map +1 -0
- package/core/src/ssr/render.d.ts +20 -0
- package/core/src/ssr/render.d.ts.map +1 -0
- package/core/src/ssr/seed.d.ts +2 -0
- package/core/src/ssr/seed.d.ts.map +1 -0
- package/core/src/template/html.d.ts +4 -0
- package/core/src/template/html.d.ts.map +1 -0
- package/core/src/template/index.d.ts +2 -0
- package/core/src/template/index.d.ts.map +1 -0
- package/core/src/types.d.ts +50 -0
- package/core/src/types.d.ts.map +1 -0
- package/core/src/utils/cache.d.ts +12 -0
- package/core/src/utils/cache.d.ts.map +1 -0
- package/core/src/utils/compression.d.ts +5 -0
- package/core/src/utils/compression.d.ts.map +1 -0
- package/core/src/utils/index.d.ts +5 -0
- package/core/src/utils/index.d.ts.map +1 -0
- package/core/src/utils/mime.d.ts +3 -0
- package/core/src/utils/mime.d.ts.map +1 -0
- package/core/src/utils/path.d.ts +6 -0
- package/core/src/utils/path.d.ts.map +1 -0
- package/elysia/index.d.ts +2 -0
- package/elysia/index.d.ts.map +1 -0
- package/elysia/index.js +1780 -0
- package/elysia/index.js.map +32 -0
- package/elysia/src/index.d.ts +3 -0
- package/elysia/src/index.d.ts.map +1 -0
- package/elysia/src/plugin.d.ts +32 -0
- package/elysia/src/plugin.d.ts.map +1 -0
- package/elysia/src/routes/artifacts.d.ts +3 -0
- package/elysia/src/routes/artifacts.d.ts.map +1 -0
- package/elysia/src/routes/content.d.ts +3 -0
- package/elysia/src/routes/content.d.ts.map +1 -0
- package/elysia/src/routes/dev.d.ts +7 -0
- package/elysia/src/routes/dev.d.ts.map +1 -0
- package/elysia/src/routes/ssr.d.ts +21 -0
- package/elysia/src/routes/ssr.d.ts.map +1 -0
- package/elysia/src/routes/static.d.ts +19 -0
- package/elysia/src/routes/static.d.ts.map +1 -0
- package/elysia/src/types.d.ts +31 -0
- package/elysia/src/types.d.ts.map +1 -0
- package/elysia/src/utils/http.d.ts +5 -0
- package/elysia/src/utils/http.d.ts.map +1 -0
- package/package.json +2 -2
- package/react/index.d.ts +2 -0
- package/react/index.d.ts.map +1 -0
- package/react/index.js +1152 -0
- package/react/index.js.map +23 -0
- package/react/src/components/ContentRoute.d.ts +13 -0
- package/react/src/components/ContentRoute.d.ts.map +1 -0
- package/react/src/components/Link.d.ts +8 -0
- package/react/src/components/Link.d.ts.map +1 -0
- package/react/src/components/Outlet.d.ts +7 -0
- package/react/src/components/Outlet.d.ts.map +1 -0
- package/react/src/components/index.d.ts +4 -0
- package/react/src/components/index.d.ts.map +1 -0
- package/react/src/hooks/index.d.ts +7 -0
- package/react/src/hooks/index.d.ts.map +1 -0
- package/react/src/hooks/useContent.d.ts +26 -0
- package/react/src/hooks/useContent.d.ts.map +1 -0
- package/react/src/hooks/useData.d.ts +10 -0
- package/react/src/hooks/useData.d.ts.map +1 -0
- package/react/src/hooks/useNavigate.d.ts +6 -0
- package/react/src/hooks/useNavigate.d.ts.map +1 -0
- package/react/src/hooks/useParams.d.ts +6 -0
- package/react/src/hooks/useParams.d.ts.map +1 -0
- package/react/src/hooks/useRouter.d.ts +7 -0
- package/react/src/hooks/useRouter.d.ts.map +1 -0
- package/react/src/hooks/useSearchParams.d.ts +6 -0
- package/react/src/hooks/useSearchParams.d.ts.map +1 -0
- package/react/src/index.d.ts +6 -0
- package/react/src/index.d.ts.map +1 -0
- package/react/src/providers/ContentProvider.d.ts +35 -0
- package/react/src/providers/ContentProvider.d.ts.map +1 -0
- package/react/src/providers/RerouteProvider.d.ts +25 -0
- package/react/src/providers/RerouteProvider.d.ts.map +1 -0
- package/react/src/providers/RouterProvider.d.ts +23 -0
- package/react/src/providers/RouterProvider.d.ts.map +1 -0
- package/react/src/providers/index.d.ts +4 -0
- package/react/src/providers/index.d.ts.map +1 -0
- package/react/src/types/any.d.ts +3 -0
- package/react/src/types/any.d.ts.map +1 -0
- package/react/src/types/index.d.ts +3 -0
- package/react/src/types/index.d.ts.map +1 -0
- package/react/src/types/router.d.ts +32 -0
- package/react/src/types/router.d.ts.map +1 -0
- package/react/src/utils/content.d.ts +8 -0
- package/react/src/utils/content.d.ts.map +1 -0
- package/react/src/utils/head.d.ts +6 -0
- package/react/src/utils/head.d.ts.map +1 -0
- package/react/src/utils/index.d.ts +3 -0
- package/react/src/utils/index.d.ts.map +1 -0
- package/CHANGELOG.md +0 -29
- package/packages/cli/README.md +0 -264
- package/packages/core/README.md +0 -90
- package/packages/elysia/README.md +0 -250
- package/packages/react/README.md +0 -3
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Link, useContent } from 'reroute-js/react';
|
|
2
|
+
|
|
3
|
+
const meta = {
|
|
4
|
+
title: 'Blog Example',
|
|
5
|
+
description:
|
|
6
|
+
'See how easy it is to build a blog with custom file-based routing!',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function BlogIndex() {
|
|
10
|
+
const { items } = useContent({
|
|
11
|
+
collection: 'blog',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div style={{ padding: '2rem' }}>
|
|
16
|
+
<h1>Latest Posts</h1>
|
|
17
|
+
<p>Welcome to our blog! Here are our latest posts:</p>
|
|
18
|
+
|
|
19
|
+
<div
|
|
20
|
+
style={{
|
|
21
|
+
marginTop: '2rem',
|
|
22
|
+
display: 'flex',
|
|
23
|
+
flexDirection: 'column',
|
|
24
|
+
gap: '1.5rem',
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
{items.map((post) => (
|
|
28
|
+
<article
|
|
29
|
+
key={post.slug}
|
|
30
|
+
style={{
|
|
31
|
+
padding: '1.5rem',
|
|
32
|
+
border: '1px solid #e0e0e0',
|
|
33
|
+
borderRadius: '8px',
|
|
34
|
+
background: '#fff',
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<h2 style={{ margin: '0 0 0.5rem 0' }}>
|
|
38
|
+
<Link
|
|
39
|
+
to={post.href}
|
|
40
|
+
style={{ textDecoration: 'none', color: '#0066cc' }}
|
|
41
|
+
>
|
|
42
|
+
{String(post.meta?.title || post.name)}
|
|
43
|
+
</Link>
|
|
44
|
+
</h2>
|
|
45
|
+
<time style={{ fontSize: '0.875rem', color: '#666' }}>
|
|
46
|
+
{post.meta?.date ? String(post.meta.date) : ''}
|
|
47
|
+
</time>
|
|
48
|
+
<p style={{ marginTop: '0.75rem', color: '#333' }}>
|
|
49
|
+
{String(post.meta?.excerpt || '')}
|
|
50
|
+
</p>
|
|
51
|
+
<Link
|
|
52
|
+
to={post.href}
|
|
53
|
+
style={{
|
|
54
|
+
display: 'inline-block',
|
|
55
|
+
marginTop: '0.5rem',
|
|
56
|
+
color: '#0066cc',
|
|
57
|
+
textDecoration: 'none',
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
Read more →
|
|
61
|
+
</Link>
|
|
62
|
+
</article>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default BlogIndex;
|
|
70
|
+
export { meta };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Link } from 'reroute-js/react';
|
|
2
|
+
|
|
3
|
+
export default function HomePage() {
|
|
4
|
+
return (
|
|
5
|
+
<main style={{ padding: '2rem', maxWidth: '1200px', margin: '0 auto' }}>
|
|
6
|
+
<h1>🏠 Welcome to Reroute!</h1>
|
|
7
|
+
<p>This is the home page of your custom file-based routing system.</p>
|
|
8
|
+
|
|
9
|
+
<section style={{ marginTop: '2rem' }}>
|
|
10
|
+
<h2>Navigation</h2>
|
|
11
|
+
<nav
|
|
12
|
+
style={{
|
|
13
|
+
display: 'flex',
|
|
14
|
+
gap: '1rem',
|
|
15
|
+
flexDirection: 'column',
|
|
16
|
+
maxWidth: '300px',
|
|
17
|
+
}}
|
|
18
|
+
>
|
|
19
|
+
<Link
|
|
20
|
+
to='/about'
|
|
21
|
+
style={{
|
|
22
|
+
padding: '0.5rem 1rem',
|
|
23
|
+
background: '#eee',
|
|
24
|
+
textDecoration: 'none',
|
|
25
|
+
borderRadius: '4px',
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
About
|
|
29
|
+
</Link>
|
|
30
|
+
<Link
|
|
31
|
+
to='/blog'
|
|
32
|
+
style={{
|
|
33
|
+
padding: '0.5rem 1rem',
|
|
34
|
+
background: '#eee',
|
|
35
|
+
textDecoration: 'none',
|
|
36
|
+
borderRadius: '4px',
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
Blog
|
|
40
|
+
</Link>
|
|
41
|
+
</nav>
|
|
42
|
+
</section>
|
|
43
|
+
|
|
44
|
+
<section
|
|
45
|
+
style={{
|
|
46
|
+
marginTop: '2rem',
|
|
47
|
+
padding: '1rem',
|
|
48
|
+
background: '#f5f5f5',
|
|
49
|
+
borderRadius: '8px',
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<h3>✨ Features</h3>
|
|
53
|
+
<ul>
|
|
54
|
+
<li>Zero-dependency file-based routing</li>
|
|
55
|
+
<li>Client-side navigation (SPA)</li>
|
|
56
|
+
<li>Server-side rendering (SSR)</li>
|
|
57
|
+
<li>Type-safe route params</li>
|
|
58
|
+
<li>Automatic route generation</li>
|
|
59
|
+
</ul>
|
|
60
|
+
</section>
|
|
61
|
+
</main>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import { createElement } from 'react';
|
|
3
|
+
import { reroute } from 'reroute-js/elysia';
|
|
4
|
+
import App from './client/App';
|
|
5
|
+
|
|
6
|
+
const IS_PRODUCTION = Bun.env.NODE_ENV === 'production';
|
|
7
|
+
|
|
8
|
+
const app = new Elysia()
|
|
9
|
+
.use(
|
|
10
|
+
reroute({
|
|
11
|
+
app: createElement(App),
|
|
12
|
+
minify: IS_PRODUCTION,
|
|
13
|
+
}),
|
|
14
|
+
)
|
|
15
|
+
.get('/message', () => ({ message: 'Hello from server' }))
|
|
16
|
+
.listen(Number(Bun.env.PORT || '3000'));
|
|
17
|
+
|
|
18
|
+
console.log(
|
|
19
|
+
`🦊 Elysia Reroute is running at ${app.server?.hostname}:${app.server?.port} on ${Bun.env.NODE_ENV || 'development'}`,
|
|
20
|
+
);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"isolatedModules": true,
|
|
10
|
+
"baseUrl": ".",
|
|
11
|
+
"rootDir": ".",
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"outDir": "dist/types",
|
|
15
|
+
"emitDeclarationOnly": true,
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
"jsx": "react-jsx",
|
|
21
|
+
"types": ["bun", "node"],
|
|
22
|
+
"paths": {}
|
|
23
|
+
},
|
|
24
|
+
"include": ["packages/**/*.ts", "packages/**/*.tsx", "test/**/*.d.ts"],
|
|
25
|
+
"exclude": ["node_modules", "dist", "test"]
|
|
26
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "A Reroute store application with Tailwind CSS v4",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "reroute gen && bun src/index.ts",
|
|
8
|
+
"dev": "reroute gen --watch & bun --watch src/index.ts",
|
|
9
|
+
"build": "reroute gen && bun build src/index.ts --target bun --compile --outfile ./dist/app"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"reroute-js": "latest",
|
|
13
|
+
"elysia": "^1.4.12",
|
|
14
|
+
"react": "^19.2.0",
|
|
15
|
+
"react-dom": "^19.2.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/bun": "latest",
|
|
19
|
+
"@types/react": "^19.2.2",
|
|
20
|
+
"@types/react-dom": "^19.2.2",
|
|
21
|
+
"@tailwindcss/cli": "^4.1.16",
|
|
22
|
+
"tailwindcss": "^4.1.16",
|
|
23
|
+
"typescript": "^5"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RerouteProvider } from 'reroute-js/react';
|
|
2
|
+
import { artifacts } from '../../.reroute';
|
|
3
|
+
|
|
4
|
+
interface AppProps {
|
|
5
|
+
pathname?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function App({ pathname }: AppProps = {}) {
|
|
9
|
+
return (
|
|
10
|
+
<RerouteProvider
|
|
11
|
+
from={{
|
|
12
|
+
pathname,
|
|
13
|
+
...artifacts,
|
|
14
|
+
}}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Link } from 'reroute-js/react';
|
|
2
|
+
|
|
3
|
+
export default function Header() {
|
|
4
|
+
return (
|
|
5
|
+
<header className='sticky top-0 z-50 bg-white border-b border-gray-200 shadow-sm'>
|
|
6
|
+
<div className='container-custom'>
|
|
7
|
+
<div className='flex items-center justify-between h-16'>
|
|
8
|
+
<Link
|
|
9
|
+
to='/'
|
|
10
|
+
className='flex items-center gap-2 text-2xl font-bold text-primary-600 hover:text-primary-700 transition-colors'
|
|
11
|
+
>
|
|
12
|
+
<span className='text-3xl'>🛒</span>
|
|
13
|
+
<span>Reroute Store</span>
|
|
14
|
+
</Link>
|
|
15
|
+
|
|
16
|
+
<nav className='flex items-center gap-8'>
|
|
17
|
+
<Link
|
|
18
|
+
to='/'
|
|
19
|
+
className='link-muted font-medium hover:text-primary-600'
|
|
20
|
+
>
|
|
21
|
+
Home
|
|
22
|
+
</Link>
|
|
23
|
+
<Link
|
|
24
|
+
to='/products'
|
|
25
|
+
className='link-muted font-medium hover:text-primary-600'
|
|
26
|
+
>
|
|
27
|
+
Products
|
|
28
|
+
</Link>
|
|
29
|
+
<Link
|
|
30
|
+
to='/categories'
|
|
31
|
+
className='link-muted font-medium hover:text-primary-600'
|
|
32
|
+
>
|
|
33
|
+
Categories
|
|
34
|
+
</Link>
|
|
35
|
+
</nav>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</header>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** biome-ignore-all lint/a11y/noStaticElementInteractions: who cares, this is just an example */
|
|
2
|
+
import { Link } from 'reroute-js/react';
|
|
3
|
+
import type { Product } from '../lib/api';
|
|
4
|
+
|
|
5
|
+
interface ProductCardProps {
|
|
6
|
+
product: Product;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function ProductCard({ product }: ProductCardProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div className='card card-hover group'>
|
|
12
|
+
<Link
|
|
13
|
+
to={`/products/${product.id}`}
|
|
14
|
+
className='block h-full no-underline text-inherit'
|
|
15
|
+
>
|
|
16
|
+
<div className='w-full h-60 bg-gray-50 flex items-center justify-center p-4'>
|
|
17
|
+
<img
|
|
18
|
+
src={product.image}
|
|
19
|
+
alt={product.title}
|
|
20
|
+
className='max-w-full max-h-full object-contain'
|
|
21
|
+
loading='lazy'
|
|
22
|
+
style={{ viewTransitionName: `product-image-${product.id}` }}
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div className='p-4 flex flex-col gap-2 flex-1'>
|
|
27
|
+
<div className='badge-primary'>{product.category}</div>
|
|
28
|
+
|
|
29
|
+
<h3 className='text-base font-semibold text-gray-900 leading-normal line-clamp-2 min-h-[3em] group-hover:text-primary-600 transition-colors'>
|
|
30
|
+
{product.title}
|
|
31
|
+
</h3>
|
|
32
|
+
|
|
33
|
+
<div className='flex justify-between items-center mt-auto pt-2'>
|
|
34
|
+
<span className='text-xl font-bold text-primary-600'>
|
|
35
|
+
${product.price.toFixed(2)}
|
|
36
|
+
</span>
|
|
37
|
+
|
|
38
|
+
{product.rating && (
|
|
39
|
+
<div className='flex items-center gap-1 text-sm text-gray-600'>
|
|
40
|
+
<span>⭐</span>
|
|
41
|
+
<span>
|
|
42
|
+
{product.rating.rate} ({product.rating.count})
|
|
43
|
+
</span>
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</Link>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<title>{{PROJECT_NAME}}</title>
|
|
6
|
+
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
|
+
<meta
|
|
9
|
+
name="description"
|
|
10
|
+
content="E-commerce store built with Reroute and FakeStoreAPI"
|
|
11
|
+
/>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="root"></div>
|
|
15
|
+
<script type="module" src="./index.tsx"></script>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// API client for FakeStoreAPI with SSR support
|
|
2
|
+
// All responses are plain JSON objects to ensure serialization compatibility
|
|
3
|
+
|
|
4
|
+
const API_BASE_URL = 'https://fakestoreapi.com';
|
|
5
|
+
|
|
6
|
+
interface Product {
|
|
7
|
+
id: number;
|
|
8
|
+
title: string;
|
|
9
|
+
price: number;
|
|
10
|
+
description: string;
|
|
11
|
+
category: string;
|
|
12
|
+
image: string;
|
|
13
|
+
rating?: {
|
|
14
|
+
rate: number;
|
|
15
|
+
count: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface CartProduct {
|
|
20
|
+
productId: number;
|
|
21
|
+
quantity: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface Cart {
|
|
25
|
+
id: number;
|
|
26
|
+
userId: number;
|
|
27
|
+
date: string;
|
|
28
|
+
products: CartProduct[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface User {
|
|
32
|
+
id: number;
|
|
33
|
+
email: string;
|
|
34
|
+
username: string;
|
|
35
|
+
password: string;
|
|
36
|
+
name: {
|
|
37
|
+
firstname: string;
|
|
38
|
+
lastname: string;
|
|
39
|
+
};
|
|
40
|
+
address: {
|
|
41
|
+
city: string;
|
|
42
|
+
street: string;
|
|
43
|
+
number: number;
|
|
44
|
+
zipcode: string;
|
|
45
|
+
geolocation: {
|
|
46
|
+
lat: string;
|
|
47
|
+
long: string;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
phone: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface LoginCredentials {
|
|
54
|
+
username: string;
|
|
55
|
+
password: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface LoginResponse {
|
|
59
|
+
token: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Helper to ensure responses are serializable
|
|
63
|
+
function ensureSerializable<T>(data: T): T {
|
|
64
|
+
// Deep clone to break any circular references
|
|
65
|
+
return JSON.parse(JSON.stringify(data));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Generic fetch wrapper with error handling
|
|
69
|
+
async function apiFetch<T>(
|
|
70
|
+
endpoint: string,
|
|
71
|
+
options?: RequestInit,
|
|
72
|
+
): Promise<T> {
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
|
|
75
|
+
...options,
|
|
76
|
+
headers: {
|
|
77
|
+
'Content-Type': 'application/json',
|
|
78
|
+
...options?.headers,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const data = await response.json();
|
|
87
|
+
return ensureSerializable(data);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(`API request failed: ${endpoint}`, error);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Products API
|
|
95
|
+
export async function getAllProducts(): Promise<Product[]> {
|
|
96
|
+
return apiFetch<Product[]>('/products');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function getProduct(id: number): Promise<Product> {
|
|
100
|
+
return apiFetch<Product>(`/products/${id}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function getProductsByCategory(
|
|
104
|
+
category: string,
|
|
105
|
+
): Promise<Product[]> {
|
|
106
|
+
return apiFetch<Product[]>(`/products/category/${category}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function getCategories(): Promise<string[]> {
|
|
110
|
+
return apiFetch<string[]>('/products/categories');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Carts API
|
|
114
|
+
export async function getAllCarts(): Promise<Cart[]> {
|
|
115
|
+
return apiFetch<Cart[]>('/carts');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function getCart(id: number): Promise<Cart> {
|
|
119
|
+
return apiFetch<Cart>(`/carts/${id}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function getUserCarts(userId: number): Promise<Cart[]> {
|
|
123
|
+
return apiFetch<Cart[]>(`/carts/user/${userId}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Users API
|
|
127
|
+
export async function getAllUsers(): Promise<User[]> {
|
|
128
|
+
return apiFetch<User[]>('/users');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function getUser(id: number): Promise<User> {
|
|
132
|
+
return apiFetch<User>(`/users/${id}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Auth API
|
|
136
|
+
export async function login(
|
|
137
|
+
credentials: LoginCredentials,
|
|
138
|
+
): Promise<LoginResponse> {
|
|
139
|
+
return apiFetch<LoginResponse>('/auth/login', {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
body: JSON.stringify(credentials),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Export types
|
|
146
|
+
export type {
|
|
147
|
+
Cart,
|
|
148
|
+
CartProduct,
|
|
149
|
+
LoginCredentials,
|
|
150
|
+
LoginResponse,
|
|
151
|
+
Product,
|
|
152
|
+
User,
|
|
153
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Link } from 'reroute-js/react';
|
|
2
|
+
import Header from '../components/Header';
|
|
3
|
+
|
|
4
|
+
export default function NotFoundPage() {
|
|
5
|
+
return (
|
|
6
|
+
<>
|
|
7
|
+
<Header />
|
|
8
|
+
|
|
9
|
+
<main className='container-custom py-16'>
|
|
10
|
+
<div className='max-w-2xl mx-auto text-center'>
|
|
11
|
+
<div className='mb-8 animate-slide-up'>
|
|
12
|
+
<div className='text-9xl font-bold text-primary-600 mb-4'>404</div>
|
|
13
|
+
<h1 className='text-4xl font-bold text-gray-900 mb-4'>
|
|
14
|
+
Page Not Found
|
|
15
|
+
</h1>
|
|
16
|
+
<p className='text-xl text-gray-600 mb-8'>
|
|
17
|
+
Oops! The page you're looking for doesn't exist. It might have
|
|
18
|
+
been moved or deleted.
|
|
19
|
+
</p>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div className='flex flex-col sm:flex-row gap-4 justify-center mb-12 animate-in'>
|
|
23
|
+
<Link to='/' className='btn-primary btn-lg'>
|
|
24
|
+
🏠 Go Home
|
|
25
|
+
</Link>
|
|
26
|
+
<Link to='/products' className='btn-secondary btn-lg'>
|
|
27
|
+
🛍️ Browse Products
|
|
28
|
+
</Link>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<div className='grid grid-cols-1 md:grid-cols-3 gap-6 mt-16 animate-scale'>
|
|
32
|
+
<Link
|
|
33
|
+
to='/categories'
|
|
34
|
+
className='card card-hover p-6 no-underline text-gray-900'
|
|
35
|
+
>
|
|
36
|
+
<div className='text-4xl mb-3'>🏷️</div>
|
|
37
|
+
<h3 className='font-semibold mb-2'>Categories</h3>
|
|
38
|
+
<p className='text-sm text-gray-600'>Browse by category</p>
|
|
39
|
+
</Link>
|
|
40
|
+
|
|
41
|
+
<Link
|
|
42
|
+
to='/products'
|
|
43
|
+
className='card card-hover p-6 no-underline text-gray-900'
|
|
44
|
+
>
|
|
45
|
+
<div className='text-4xl mb-3'>📦</div>
|
|
46
|
+
<h3 className='font-semibold mb-2'>All Products</h3>
|
|
47
|
+
<p className='text-sm text-gray-600'>View our catalog</p>
|
|
48
|
+
</Link>
|
|
49
|
+
|
|
50
|
+
<Link
|
|
51
|
+
to='/'
|
|
52
|
+
className='card card-hover p-6 no-underline text-gray-900'
|
|
53
|
+
>
|
|
54
|
+
<div className='text-4xl mb-3'>⚡</div>
|
|
55
|
+
<h3 className='font-semibold mb-2'>Featured</h3>
|
|
56
|
+
<p className='text-sm text-gray-600'>See what's popular</p>
|
|
57
|
+
</Link>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</main>
|
|
61
|
+
</>
|
|
62
|
+
);
|
|
63
|
+
}
|