reroute-js 0.0.13 → 0.1.0
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 +17 -8
- package/_/blog/src/client/components/Counter.tsx +14 -0
- package/_/blog/src/client/components/RecentPosts.tsx +90 -0
- package/_/blog/src/client/index.html +10 -9
- package/_/blog/src/client/index.tsx +1 -1
- package/_/blog/src/client/routes/[404].tsx +15 -12
- package/_/blog/src/client/routes/about.tsx +10 -4
- package/_/blog/src/client/routes/blog/[404].tsx +15 -5
- package/_/blog/src/client/routes/blog/[layout].tsx +1 -1
- 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 +3 -2
- package/_/blog/src/client/routes/index.tsx +2 -14
- package/_/blog/src/index.ts +2 -2
- 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/cli/bin.js +45 -16
- package/cli/bin.js.map +4 -4
- package/cli/src/cli.d.ts.map +1 -1
- package/package.json +2 -2
- package/_/blog/src/client/components/.gitkeep +0 -0
- package/_/blog/src/client/routes/blog/content/getting-started.tsx +0 -30
- package/_/blog/src/client/routes/blog/content/hello-world.tsx +0 -29
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ A dead simple file-based routing framework for building fullstack React apps on
|
|
|
12
12
|
- 📦 **Zero Config** - Works out of the box with sensible defaults
|
|
13
13
|
|
|
14
14
|
### 🛠️ CLI Tools
|
|
15
|
-
- 🚀 **`reroute init`** - Scaffold new projects with templates (basic, blog)
|
|
15
|
+
- 🚀 **`reroute init`** - Scaffold new projects with templates (basic, blog, store)
|
|
16
16
|
- 🔨 **`reroute gen`** - Generate content registry and route artifacts
|
|
17
17
|
|
|
18
18
|
### 📄 Content Management
|
|
@@ -24,6 +24,7 @@ A dead simple file-based routing framework for building fullstack React apps on
|
|
|
24
24
|
- 📦 **Collection Chunking** - Individual module bundles per content item
|
|
25
25
|
|
|
26
26
|
### ⚛️ React Integration
|
|
27
|
+
- ⚡ **React 19** - Built with the latest React version for optimal performance
|
|
27
28
|
- 🪝 **Router Hooks**:
|
|
28
29
|
- `useNavigate()` - Programmatic navigation
|
|
29
30
|
- `useParams()` - Access route parameters
|
|
@@ -39,9 +40,9 @@ A dead simple file-based routing framework for building fullstack React apps on
|
|
|
39
40
|
- `<RouterProvider>` - Router context for the app
|
|
40
41
|
- `<ContentProvider>` - Content system context
|
|
41
42
|
- `<RerouteProvider>` - All-in-one provider wrapper
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
- 🗂️ **SSR Data**:
|
|
44
|
+
- `useData()` - Read route-level SSR data without loaders
|
|
45
|
+
- `export const ssr = { data() {} }` - Route data function executed on server
|
|
45
46
|
|
|
46
47
|
### 🚀 Performance Optimizations
|
|
47
48
|
- 💾 **Smart Caching** - LRU cache for files and bundles
|
|
@@ -51,8 +52,15 @@ A dead simple file-based routing framework for building fullstack React apps on
|
|
|
51
52
|
- 🎯 **SSR Module Seeding** - Pre-populate modules for instant hydration
|
|
52
53
|
- 📊 **Collection Inlining** - Inline collection data for zero-latency rendering
|
|
53
54
|
|
|
55
|
+
### 🎨 Styling & Theming
|
|
56
|
+
- 🎨 **Tailwind CSS v4 Support** - Automatic detection and compilation with CSS-first configuration
|
|
57
|
+
- ⚡ **Live CSS Reload** - Instant style updates in development mode
|
|
58
|
+
- 🎯 **Modern CSS Features** - `@theme` and `@utility` directives support
|
|
59
|
+
- 🚀 **Oxide Engine** - 5x faster builds with the new Tailwind compiler
|
|
60
|
+
- 🔧 **Zero Config** - Works out of the box when `@tailwindcss/cli` is installed
|
|
61
|
+
|
|
54
62
|
### 🎨 Developer Experience
|
|
55
|
-
- 📁 **Project Templates** - Quick start with `basic` or `
|
|
63
|
+
- 📁 **Project Templates** - Quick start with `basic`, `blog`, or `store` templates
|
|
56
64
|
- 🔄 **Live Reload** - Server-Sent Events (SSE) for instant browser updates
|
|
57
65
|
- 🎯 **TypeScript Support** - Full type safety throughout the stack
|
|
58
66
|
- 🗂️ **File System Watcher** - Automatic rebuilds on source changes
|
|
@@ -85,17 +93,18 @@ A dead simple file-based routing framework for building fullstack React apps on
|
|
|
85
93
|
- 🔧 **URL Prefix** - Deploy to subdirectories with custom prefixes
|
|
86
94
|
- 🚫 **Ignore Patterns** - Exclude files from static serving
|
|
87
95
|
- 🏷️ **Custom Headers** - Add headers to static file responses
|
|
88
|
-
|
|
96
|
+
- ⏰ **Cache Control** - Configurable max-age and cache directives for static files and `__reroute_data` JSON
|
|
89
97
|
- 🎨 **HTML Template** - Custom index.html with variable substitution
|
|
90
98
|
|
|
91
99
|
## 🚀 Quick Start
|
|
92
100
|
|
|
93
101
|
```bash
|
|
94
|
-
# Create a new project
|
|
102
|
+
# Create a new project (basic template)
|
|
95
103
|
bunx reroute-js init my-app
|
|
96
104
|
|
|
97
|
-
# Or with a template
|
|
105
|
+
# Or with a specific template
|
|
98
106
|
bunx reroute-js init my-blog --template blog
|
|
107
|
+
bunx reroute-js init my-store --template store
|
|
99
108
|
|
|
100
109
|
# Navigate to project
|
|
101
110
|
cd my-app
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export default function Counter() {
|
|
4
|
+
const [count, setCount] = useState(0);
|
|
5
|
+
const increase = () => setCount((c) => c + 1);
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<section>
|
|
9
|
+
<h2>Current count: {count}</h2>
|
|
10
|
+
{/* biome-ignore lint/a11y/useButtonType: shut up */}
|
|
11
|
+
<button onClick={increase}>Increase</button>
|
|
12
|
+
</section>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Link, useContent } from 'reroute-js/react';
|
|
2
|
+
|
|
3
|
+
interface RecentPostsProps {
|
|
4
|
+
limit?: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function RecentPosts({ limit = 3 }: RecentPostsProps) {
|
|
8
|
+
const { items } = useContent({
|
|
9
|
+
collection: 'blog',
|
|
10
|
+
limit,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const recentPosts = items;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div style={{ marginTop: '2rem' }}>
|
|
17
|
+
<h2>Recent Blog Posts</h2>
|
|
18
|
+
<div
|
|
19
|
+
style={{
|
|
20
|
+
marginTop: '1rem',
|
|
21
|
+
display: 'flex',
|
|
22
|
+
flexDirection: 'column',
|
|
23
|
+
gap: '1rem',
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
{recentPosts.map((post) => (
|
|
27
|
+
<article
|
|
28
|
+
key={post.slug}
|
|
29
|
+
style={{
|
|
30
|
+
padding: '1rem',
|
|
31
|
+
border: '1px solid #e0e0e0',
|
|
32
|
+
borderRadius: '8px',
|
|
33
|
+
background: '#fff',
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
<h3 style={{ margin: '0 0 0.5rem 0', fontSize: '1.25rem' }}>
|
|
37
|
+
<Link
|
|
38
|
+
to={post.href}
|
|
39
|
+
style={{ textDecoration: 'none', color: '#0066cc' }}
|
|
40
|
+
>
|
|
41
|
+
{String(post.meta?.title || post.name)}
|
|
42
|
+
</Link>
|
|
43
|
+
</h3>
|
|
44
|
+
{post.meta?.date && (
|
|
45
|
+
<time style={{ fontSize: '0.875rem', color: '#666' }}>
|
|
46
|
+
{String(post.meta.date)}
|
|
47
|
+
</time>
|
|
48
|
+
)}
|
|
49
|
+
{post.meta?.excerpt && (
|
|
50
|
+
<p
|
|
51
|
+
style={{
|
|
52
|
+
marginTop: '0.5rem',
|
|
53
|
+
color: '#333',
|
|
54
|
+
fontSize: '0.9rem',
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{String(post.meta.excerpt)}
|
|
58
|
+
</p>
|
|
59
|
+
)}
|
|
60
|
+
<Link
|
|
61
|
+
to={post.href}
|
|
62
|
+
style={{
|
|
63
|
+
display: 'inline-block',
|
|
64
|
+
marginTop: '0.5rem',
|
|
65
|
+
color: '#0066cc',
|
|
66
|
+
textDecoration: 'none',
|
|
67
|
+
fontSize: '0.9rem',
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
Read more →
|
|
71
|
+
</Link>
|
|
72
|
+
</article>
|
|
73
|
+
))}
|
|
74
|
+
</div>
|
|
75
|
+
<div style={{ marginTop: '1rem' }}>
|
|
76
|
+
<Link
|
|
77
|
+
to='/blog'
|
|
78
|
+
style={{
|
|
79
|
+
color: '#0066cc',
|
|
80
|
+
textDecoration: 'none',
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
View all posts →
|
|
84
|
+
</Link>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default RecentPosts;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="./index.tsx"></script>
|
|
12
|
+
</body>
|
|
12
13
|
</html>
|
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
const meta = {
|
|
2
|
+
title: '404',
|
|
3
|
+
description: 'Page not found',
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
function NotFound() {
|
|
2
7
|
return (
|
|
3
|
-
<div
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
maxWidth: '800px',
|
|
7
|
-
margin: '0 auto',
|
|
8
|
-
textAlign: 'center',
|
|
9
|
-
}}
|
|
10
|
-
>
|
|
11
|
-
<h1>404 - Page Not Found</h1>
|
|
12
|
-
<p>The page you're looking for doesn't exist.</p>
|
|
8
|
+
<div style={{ padding: '2rem', maxWidth: '800px', margin: '0 auto' }}>
|
|
9
|
+
<h1>404</h1>
|
|
10
|
+
<p>The page you are looking for does not exist.</p>
|
|
13
11
|
<nav style={{ marginTop: '2rem' }}>
|
|
14
|
-
<a href='/'
|
|
12
|
+
<a href='/' style={{ marginRight: '1rem' }}>
|
|
13
|
+
← Back to Home
|
|
14
|
+
</a>
|
|
15
15
|
</nav>
|
|
16
16
|
</div>
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
export default NotFound;
|
|
21
|
+
export { meta };
|
|
@@ -1,17 +1,23 @@
|
|
|
1
|
+
import RecentPosts from '../components/RecentPosts';
|
|
2
|
+
|
|
1
3
|
const meta = {
|
|
2
|
-
title: 'About',
|
|
3
|
-
description:
|
|
4
|
+
title: 'About Us',
|
|
5
|
+
description:
|
|
6
|
+
'Learn more about our application built with custom file-based routing!',
|
|
4
7
|
};
|
|
5
8
|
|
|
6
9
|
function About() {
|
|
7
10
|
return (
|
|
8
11
|
<div style={{ padding: '2rem', maxWidth: '800px', margin: '0 auto' }}>
|
|
9
|
-
<h1>About</h1>
|
|
10
|
-
<p>Welcome to
|
|
12
|
+
<h1>About Us</h1>
|
|
13
|
+
<p>Welcome to our application built with custom file-based routing!</p>
|
|
11
14
|
<p>
|
|
12
15
|
This page demonstrates a simple static route that maps from{' '}
|
|
13
16
|
<code>routes/about.tsx</code> to <code>/about</code>.
|
|
14
17
|
</p>
|
|
18
|
+
|
|
19
|
+
<RecentPosts limit={1} />
|
|
20
|
+
|
|
15
21
|
<nav style={{ marginTop: '2rem' }}>
|
|
16
22
|
<a href='/' style={{ marginRight: '1rem' }}>
|
|
17
23
|
← Back to Home
|
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
const meta = {
|
|
2
|
+
title: '404',
|
|
3
|
+
description: 'Page not found',
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
function NotFound() {
|
|
2
7
|
return (
|
|
3
|
-
<div style={{ padding: '2rem',
|
|
4
|
-
<h1>
|
|
5
|
-
<p>The blog
|
|
8
|
+
<div style={{ padding: '2rem', maxWidth: '800px', margin: '0 auto' }}>
|
|
9
|
+
<h1>404</h1>
|
|
10
|
+
<p>The blog content you are looking for does not exist.</p>
|
|
6
11
|
<nav style={{ marginTop: '2rem' }}>
|
|
7
|
-
<a href='/
|
|
12
|
+
<a href='/' style={{ marginRight: '1rem' }}>
|
|
13
|
+
← Back to Home
|
|
14
|
+
</a>
|
|
8
15
|
</nav>
|
|
9
16
|
</div>
|
|
10
17
|
);
|
|
11
18
|
}
|
|
19
|
+
|
|
20
|
+
export default NotFound;
|
|
21
|
+
export { meta };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Page metadata and optional SSR extras. Reroute SSR will use these
|
|
2
|
+
// to generate <title>, <meta name="description"> and append any custom head.
|
|
3
|
+
const meta = {
|
|
4
|
+
title: 'Hello World',
|
|
5
|
+
description: 'This is the first post using Reroute 🎉',
|
|
6
|
+
excerpt: 'This is a great post',
|
|
7
|
+
date: '2025-10-20',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const ssr = {
|
|
11
|
+
head: [
|
|
12
|
+
'<meta property="og:type" content="article" />',
|
|
13
|
+
'<meta name="twitter:card" content="summary_large_image" />',
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function BlogPost() {
|
|
18
|
+
return (
|
|
19
|
+
<div>
|
|
20
|
+
<h1>Hello World</h1>
|
|
21
|
+
<p>Welcome to my blog!!</p>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { meta, ssr };
|
|
27
|
+
export default BlogPost;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const meta = {
|
|
2
|
+
title: 'What is Reroute?',
|
|
3
|
+
description:
|
|
4
|
+
'A dead simple file-based routing framework for building fullstack React apps on Bun and Elysia.',
|
|
5
|
+
date: '2025-10-22',
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
function BlogPost() {
|
|
9
|
+
return (
|
|
10
|
+
<div>
|
|
11
|
+
<h1>What is Reroute?</h1>
|
|
12
|
+
<p>
|
|
13
|
+
Reroute is a dead simple file-based routing framework for building
|
|
14
|
+
fullstack applications using React that runs on Bun and Elysia.
|
|
15
|
+
</p>
|
|
16
|
+
<p>
|
|
17
|
+
It features server-side rendering (SSR), automatic route generation from
|
|
18
|
+
your file structure, live reload in development, and zero configuration
|
|
19
|
+
needed to get started.
|
|
20
|
+
</p>
|
|
21
|
+
<p>
|
|
22
|
+
With built-in content collections, smart caching, and powerful React
|
|
23
|
+
hooks like useNavigate(), useData(), and useContent(), Reroute makes it
|
|
24
|
+
easy to build fast, SEO-friendly web applications with minimal setup.
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default BlogPost;
|
|
31
|
+
export { meta };
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Link, useContent } from 'reroute-js/react';
|
|
2
2
|
|
|
3
3
|
const meta = {
|
|
4
|
-
title: 'Blog',
|
|
5
|
-
description:
|
|
4
|
+
title: 'Blog Example',
|
|
5
|
+
description:
|
|
6
|
+
'See how easy it is to build a blog with custom file-based routing!',
|
|
6
7
|
};
|
|
7
8
|
|
|
8
9
|
function BlogIndex() {
|
|
@@ -4,7 +4,7 @@ export default function HomePage() {
|
|
|
4
4
|
return (
|
|
5
5
|
<main style={{ padding: '2rem', maxWidth: '1200px', margin: '0 auto' }}>
|
|
6
6
|
<h1>🏠 Welcome to Reroute!</h1>
|
|
7
|
-
<p>This is your
|
|
7
|
+
<p>This is the home page of your custom file-based routing system.</p>
|
|
8
8
|
|
|
9
9
|
<section style={{ marginTop: '2rem' }}>
|
|
10
10
|
<h2>Navigation</h2>
|
|
@@ -38,17 +38,6 @@ export default function HomePage() {
|
|
|
38
38
|
>
|
|
39
39
|
Blog
|
|
40
40
|
</Link>
|
|
41
|
-
<Link
|
|
42
|
-
to='/blog/hello-world'
|
|
43
|
-
style={{
|
|
44
|
-
padding: '0.5rem 1rem',
|
|
45
|
-
background: '#eee',
|
|
46
|
-
textDecoration: 'none',
|
|
47
|
-
borderRadius: '4px',
|
|
48
|
-
}}
|
|
49
|
-
>
|
|
50
|
-
First Blog Post
|
|
51
|
-
</Link>
|
|
52
41
|
</nav>
|
|
53
42
|
</section>
|
|
54
43
|
|
|
@@ -62,12 +51,11 @@ export default function HomePage() {
|
|
|
62
51
|
>
|
|
63
52
|
<h3>✨ Features</h3>
|
|
64
53
|
<ul>
|
|
65
|
-
<li>
|
|
54
|
+
<li>Zero-dependency file-based routing</li>
|
|
66
55
|
<li>Client-side navigation (SPA)</li>
|
|
67
56
|
<li>Server-side rendering (SSR)</li>
|
|
68
57
|
<li>Type-safe route params</li>
|
|
69
58
|
<li>Automatic route generation</li>
|
|
70
|
-
<li>Content collections</li>
|
|
71
59
|
</ul>
|
|
72
60
|
</section>
|
|
73
61
|
</main>
|
package/_/blog/src/index.ts
CHANGED
|
@@ -12,9 +12,9 @@ const app = new Elysia()
|
|
|
12
12
|
minify: IS_PRODUCTION,
|
|
13
13
|
}),
|
|
14
14
|
)
|
|
15
|
-
.get('/
|
|
15
|
+
.get('/message', () => ({ message: 'Hello from server' }))
|
|
16
16
|
.listen(Number(Bun.env.PORT || '3000'));
|
|
17
17
|
|
|
18
18
|
console.log(
|
|
19
|
-
`🦊 Reroute is running at ${app.server?.hostname}:${app.server?.port}`,
|
|
19
|
+
`🦊 Elysia Reroute is running at ${app.server?.hostname}:${app.server?.port} on ${Bun.env.NODE_ENV || 'development'}`,
|
|
20
20
|
);
|
|
@@ -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>
|