reroute-js 0.1.0 → 0.2.1
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/CHANGELOG.md +23 -0
- package/README.md +1 -1
- package/package.json +8 -6
- package/packages/cli/README.md +264 -0
- package/{cli/bin.d.ts → packages/cli/bin.ts} +1 -1
- package/packages/core/README.md +90 -0
- package/packages/elysia/README.md +250 -0
- package/packages/react/README.md +3 -0
- package/_/README.md +0 -59
- package/_/basic/package.json +0 -23
- package/_/basic/src/client/App.tsx +0 -10
- package/_/basic/src/client/components/Counter.tsx +0 -15
- package/_/basic/src/client/index.html +0 -12
- package/_/basic/src/client/index.tsx +0 -5
- package/_/basic/src/client/routes/[404].tsx +0 -18
- package/_/basic/src/client/routes/about.tsx +0 -25
- package/_/basic/src/client/routes/index.tsx +0 -57
- package/_/basic/src/index.ts +0 -20
- package/_/basic/tsconfig.json +0 -26
- package/_/blog/package.json +0 -23
- package/_/blog/src/client/App.tsx +0 -10
- package/_/blog/src/client/components/Counter.tsx +0 -14
- package/_/blog/src/client/components/RecentPosts.tsx +0 -90
- package/_/blog/src/client/index.html +0 -13
- package/_/blog/src/client/index.tsx +0 -5
- package/_/blog/src/client/routes/[404].tsx +0 -21
- package/_/blog/src/client/routes/about.tsx +0 -31
- package/_/blog/src/client/routes/blog/[404].tsx +0 -21
- package/_/blog/src/client/routes/blog/[layout].tsx +0 -84
- package/_/blog/src/client/routes/blog/[slug].tsx +0 -11
- package/_/blog/src/client/routes/blog/content/1-hello-world.tsx +0 -27
- package/_/blog/src/client/routes/blog/content/2-what-is-reroute.tsx +0 -31
- package/_/blog/src/client/routes/blog/index.tsx +0 -70
- package/_/blog/src/client/routes/index.tsx +0 -63
- package/_/blog/src/index.ts +0 -20
- package/_/blog/tsconfig.json +0 -26
- package/_/store/package.json +0 -25
- package/_/store/src/client/App.tsx +0 -17
- package/_/store/src/client/components/Header.tsx +0 -40
- package/_/store/src/client/components/ProductCard.tsx +0 -51
- package/_/store/src/client/index.html +0 -17
- package/_/store/src/client/index.tsx +0 -7
- package/_/store/src/client/lib/api.ts +0 -153
- package/_/store/src/client/routes/[404].tsx +0 -63
- package/_/store/src/client/routes/categories/[category].tsx +0 -223
- package/_/store/src/client/routes/categories/index.tsx +0 -187
- package/_/store/src/client/routes/index.tsx +0 -126
- package/_/store/src/client/routes/products/[id].tsx +0 -233
- package/_/store/src/client/routes/products/index.tsx +0 -261
- package/_/store/src/client/theme.css +0 -306
- package/_/store/src/index.ts +0 -19
- package/_/store/tsconfig.json +0 -26
- package/cli/bin.d.ts.map +0 -1
- package/cli/bin.js +0 -878
- package/cli/bin.js.map +0 -15
- package/cli/index.d.ts +0 -2
- package/cli/index.d.ts.map +0 -1
- package/cli/index.js +0 -147
- package/cli/index.js.map +0 -10
- package/cli/src/cli.d.ts +0 -8
- package/cli/src/cli.d.ts.map +0 -1
- package/cli/src/commands/build.d.ts +0 -8
- package/cli/src/commands/build.d.ts.map +0 -1
- package/cli/src/commands/dev.d.ts +0 -8
- package/cli/src/commands/dev.d.ts.map +0 -1
- package/cli/src/commands/gen.d.ts +0 -3
- package/cli/src/commands/gen.d.ts.map +0 -1
- package/cli/src/commands/init.d.ts +0 -8
- package/cli/src/commands/init.d.ts.map +0 -1
- package/cli/src/libs/index.d.ts +0 -2
- package/cli/src/libs/index.d.ts.map +0 -1
- package/cli/src/libs/tailwind.d.ts +0 -45
- package/cli/src/libs/tailwind.d.ts.map +0 -1
- package/core/index.d.ts +0 -2
- package/core/index.d.ts.map +0 -1
- package/core/index.js +0 -1117
- package/core/index.js.map +0 -25
- package/core/src/bundler/hash.d.ts +0 -2
- package/core/src/bundler/hash.d.ts.map +0 -1
- package/core/src/bundler/index.d.ts +0 -3
- package/core/src/bundler/index.d.ts.map +0 -1
- package/core/src/bundler/transpile.d.ts +0 -4
- package/core/src/bundler/transpile.d.ts.map +0 -1
- package/core/src/content/discovery.d.ts +0 -5
- package/core/src/content/discovery.d.ts.map +0 -1
- package/core/src/content/index.d.ts +0 -4
- package/core/src/content/index.d.ts.map +0 -1
- package/core/src/content/metadata.d.ts +0 -9
- package/core/src/content/metadata.d.ts.map +0 -1
- package/core/src/content/registry.d.ts +0 -2
- package/core/src/content/registry.d.ts.map +0 -1
- package/core/src/index.d.ts +0 -7
- package/core/src/index.d.ts.map +0 -1
- package/core/src/ssr/data.d.ts +0 -9
- package/core/src/ssr/data.d.ts.map +0 -1
- package/core/src/ssr/index.d.ts +0 -4
- package/core/src/ssr/index.d.ts.map +0 -1
- package/core/src/ssr/modules.d.ts +0 -8
- package/core/src/ssr/modules.d.ts.map +0 -1
- package/core/src/ssr/render.d.ts +0 -20
- package/core/src/ssr/render.d.ts.map +0 -1
- package/core/src/ssr/seed.d.ts +0 -2
- package/core/src/ssr/seed.d.ts.map +0 -1
- package/core/src/template/html.d.ts +0 -4
- package/core/src/template/html.d.ts.map +0 -1
- package/core/src/template/index.d.ts +0 -2
- package/core/src/template/index.d.ts.map +0 -1
- package/core/src/types.d.ts +0 -50
- package/core/src/types.d.ts.map +0 -1
- package/core/src/utils/cache.d.ts +0 -12
- package/core/src/utils/cache.d.ts.map +0 -1
- package/core/src/utils/compression.d.ts +0 -5
- package/core/src/utils/compression.d.ts.map +0 -1
- package/core/src/utils/index.d.ts +0 -5
- package/core/src/utils/index.d.ts.map +0 -1
- package/core/src/utils/mime.d.ts +0 -3
- package/core/src/utils/mime.d.ts.map +0 -1
- package/core/src/utils/path.d.ts +0 -6
- package/core/src/utils/path.d.ts.map +0 -1
- package/elysia/index.d.ts +0 -2
- package/elysia/index.d.ts.map +0 -1
- package/elysia/index.js +0 -1762
- package/elysia/index.js.map +0 -32
- package/elysia/src/index.d.ts +0 -3
- package/elysia/src/index.d.ts.map +0 -1
- package/elysia/src/plugin.d.ts +0 -32
- package/elysia/src/plugin.d.ts.map +0 -1
- package/elysia/src/routes/artifacts.d.ts +0 -3
- package/elysia/src/routes/artifacts.d.ts.map +0 -1
- package/elysia/src/routes/content.d.ts +0 -3
- package/elysia/src/routes/content.d.ts.map +0 -1
- package/elysia/src/routes/dev.d.ts +0 -7
- package/elysia/src/routes/dev.d.ts.map +0 -1
- package/elysia/src/routes/ssr.d.ts +0 -19
- package/elysia/src/routes/ssr.d.ts.map +0 -1
- package/elysia/src/routes/static.d.ts +0 -19
- package/elysia/src/routes/static.d.ts.map +0 -1
- package/elysia/src/types.d.ts +0 -31
- package/elysia/src/types.d.ts.map +0 -1
- package/elysia/src/utils/http.d.ts +0 -5
- package/elysia/src/utils/http.d.ts.map +0 -1
- package/react/index.d.ts +0 -2
- package/react/index.d.ts.map +0 -1
- package/react/index.js +0 -1152
- package/react/index.js.map +0 -23
- package/react/src/components/ContentRoute.d.ts +0 -13
- package/react/src/components/ContentRoute.d.ts.map +0 -1
- package/react/src/components/Link.d.ts +0 -8
- package/react/src/components/Link.d.ts.map +0 -1
- package/react/src/components/Outlet.d.ts +0 -7
- package/react/src/components/Outlet.d.ts.map +0 -1
- package/react/src/components/index.d.ts +0 -4
- package/react/src/components/index.d.ts.map +0 -1
- package/react/src/hooks/index.d.ts +0 -7
- package/react/src/hooks/index.d.ts.map +0 -1
- package/react/src/hooks/useContent.d.ts +0 -26
- package/react/src/hooks/useContent.d.ts.map +0 -1
- package/react/src/hooks/useData.d.ts +0 -10
- package/react/src/hooks/useData.d.ts.map +0 -1
- package/react/src/hooks/useNavigate.d.ts +0 -6
- package/react/src/hooks/useNavigate.d.ts.map +0 -1
- package/react/src/hooks/useParams.d.ts +0 -6
- package/react/src/hooks/useParams.d.ts.map +0 -1
- package/react/src/hooks/useRouter.d.ts +0 -7
- package/react/src/hooks/useRouter.d.ts.map +0 -1
- package/react/src/hooks/useSearchParams.d.ts +0 -6
- package/react/src/hooks/useSearchParams.d.ts.map +0 -1
- package/react/src/index.d.ts +0 -6
- package/react/src/index.d.ts.map +0 -1
- package/react/src/providers/ContentProvider.d.ts +0 -35
- package/react/src/providers/ContentProvider.d.ts.map +0 -1
- package/react/src/providers/RerouteProvider.d.ts +0 -25
- package/react/src/providers/RerouteProvider.d.ts.map +0 -1
- package/react/src/providers/RouterProvider.d.ts +0 -23
- package/react/src/providers/RouterProvider.d.ts.map +0 -1
- package/react/src/providers/index.d.ts +0 -4
- package/react/src/providers/index.d.ts.map +0 -1
- package/react/src/types/any.d.ts +0 -3
- package/react/src/types/any.d.ts.map +0 -1
- package/react/src/types/index.d.ts +0 -3
- package/react/src/types/index.d.ts.map +0 -1
- package/react/src/types/router.d.ts +0 -32
- package/react/src/types/router.d.ts.map +0 -1
- package/react/src/utils/content.d.ts +0 -8
- package/react/src/utils/content.d.ts.map +0 -1
- package/react/src/utils/head.d.ts +0 -6
- package/react/src/utils/head.d.ts.map +0 -1
- package/react/src/utils/index.d.ts +0 -3
- package/react/src/utils/index.d.ts.map +0 -1
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
import { Link, useData, useParams } from 'reroute-js/react';
|
|
3
|
-
import Header from '../../components/Header';
|
|
4
|
-
import ProductCard from '../../components/ProductCard';
|
|
5
|
-
import { getProductsByCategory, type Product } from '../../lib/api';
|
|
6
|
-
|
|
7
|
-
const categoryIcons: Record<string, string> = {
|
|
8
|
-
electronics: '💻',
|
|
9
|
-
jewelery: '💎',
|
|
10
|
-
"men's clothing": '👔',
|
|
11
|
-
"women's clothing": '👗',
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const formatCategoryLabel = (category: string) =>
|
|
15
|
-
category
|
|
16
|
-
.split(' ')
|
|
17
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
18
|
-
.join(' ');
|
|
19
|
-
|
|
20
|
-
type SortOption = 'default' | 'price-asc' | 'price-desc' | 'name-asc';
|
|
21
|
-
|
|
22
|
-
const ssr = {
|
|
23
|
-
async data({ params }: { params: { category?: string } }) {
|
|
24
|
-
try {
|
|
25
|
-
const cat = params?.category;
|
|
26
|
-
if (!cat) {
|
|
27
|
-
return {
|
|
28
|
-
products: [],
|
|
29
|
-
error: 'No category specified',
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
const products = await getProductsByCategory(cat);
|
|
33
|
-
return { products } as { products: Product[] };
|
|
34
|
-
} catch (e) {
|
|
35
|
-
console.error('ssr.data error for category:', e);
|
|
36
|
-
return {
|
|
37
|
-
products: [],
|
|
38
|
-
error: 'Failed to load products for this category.',
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
function CategoryPage() {
|
|
45
|
-
const params = useParams<{ category: string }>();
|
|
46
|
-
const category = params.category || '';
|
|
47
|
-
|
|
48
|
-
const routeData = useData<{ products: Product[]; error?: string }>();
|
|
49
|
-
const [products, setProducts] = useState<Product[]>(
|
|
50
|
-
routeData?.products || [],
|
|
51
|
-
);
|
|
52
|
-
const [filteredProducts, setFilteredProducts] = useState<Product[]>(
|
|
53
|
-
routeData?.products || [],
|
|
54
|
-
);
|
|
55
|
-
const [searchQuery, setSearchQuery] = useState<string>('');
|
|
56
|
-
const [sortBy, setSortBy] = useState<SortOption>('default');
|
|
57
|
-
const [loading, setLoading] = useState(!routeData);
|
|
58
|
-
const [error, setError] = useState<string | null>(routeData?.error || null);
|
|
59
|
-
|
|
60
|
-
// Load products on mount if not from SSR
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
if (products.length > 0) {
|
|
63
|
-
setLoading(false);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (!category) {
|
|
67
|
-
setError('No category specified');
|
|
68
|
-
setLoading(false);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
setLoading(true);
|
|
73
|
-
setError(null);
|
|
74
|
-
|
|
75
|
-
getProductsByCategory(category)
|
|
76
|
-
.then((data) => {
|
|
77
|
-
setProducts(data);
|
|
78
|
-
setFilteredProducts(data);
|
|
79
|
-
})
|
|
80
|
-
.catch((err) => {
|
|
81
|
-
console.error('Failed to load products:', err);
|
|
82
|
-
setError('Failed to load products. Please try again later.');
|
|
83
|
-
})
|
|
84
|
-
.finally(() => {
|
|
85
|
-
setLoading(false);
|
|
86
|
-
});
|
|
87
|
-
}, [category, products.length]);
|
|
88
|
-
|
|
89
|
-
// Filter and sort products
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
let result = [...products];
|
|
92
|
-
|
|
93
|
-
// Apply search filter
|
|
94
|
-
if (searchQuery.trim()) {
|
|
95
|
-
const query = searchQuery.toLowerCase();
|
|
96
|
-
result = result.filter(
|
|
97
|
-
(product) =>
|
|
98
|
-
product.title.toLowerCase().includes(query) ||
|
|
99
|
-
product.description.toLowerCase().includes(query),
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Apply sorting
|
|
104
|
-
switch (sortBy) {
|
|
105
|
-
case 'price-asc':
|
|
106
|
-
result.sort((a, b) => a.price - b.price);
|
|
107
|
-
break;
|
|
108
|
-
case 'price-desc':
|
|
109
|
-
result.sort((a, b) => b.price - a.price);
|
|
110
|
-
break;
|
|
111
|
-
case 'name-asc':
|
|
112
|
-
result.sort((a, b) => a.title.localeCompare(b.title));
|
|
113
|
-
break;
|
|
114
|
-
default:
|
|
115
|
-
// Keep default order
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
setFilteredProducts(result);
|
|
120
|
-
}, [products, searchQuery, sortBy]);
|
|
121
|
-
|
|
122
|
-
const categoryLabel = formatCategoryLabel(category);
|
|
123
|
-
const icon = categoryIcons[category] || '🏷️';
|
|
124
|
-
|
|
125
|
-
return (
|
|
126
|
-
<>
|
|
127
|
-
<Header />
|
|
128
|
-
|
|
129
|
-
<main className='container-custom py-8'>
|
|
130
|
-
{/* Breadcrumbs */}
|
|
131
|
-
<nav className='flex items-center gap-2 text-sm text-gray-600 mb-8'>
|
|
132
|
-
<Link to='/' className='link'>
|
|
133
|
-
Home
|
|
134
|
-
</Link>
|
|
135
|
-
<span>/</span>
|
|
136
|
-
<Link to='/categories' className='link'>
|
|
137
|
-
Categories
|
|
138
|
-
</Link>
|
|
139
|
-
<span>/</span>
|
|
140
|
-
<span className='text-gray-900'>{categoryLabel}</span>
|
|
141
|
-
</nav>
|
|
142
|
-
|
|
143
|
-
{/* Header */}
|
|
144
|
-
<div className='mb-8'>
|
|
145
|
-
<div className='flex items-center gap-3 mb-2'>
|
|
146
|
-
<span className='text-5xl'>{icon}</span>
|
|
147
|
-
<h1 className='section-heading mb-0'>{categoryLabel}</h1>
|
|
148
|
-
</div>
|
|
149
|
-
<p className='section-subheading'>
|
|
150
|
-
{products.length} products in this category
|
|
151
|
-
</p>
|
|
152
|
-
</div>
|
|
153
|
-
|
|
154
|
-
{/* Controls */}
|
|
155
|
-
<div className='flex flex-wrap items-center gap-4 mb-8 p-6 bg-white rounded-xl shadow-card'>
|
|
156
|
-
<input
|
|
157
|
-
type='search'
|
|
158
|
-
placeholder='Search in this category...'
|
|
159
|
-
className='input flex-1 min-w-[250px]'
|
|
160
|
-
value={searchQuery}
|
|
161
|
-
onChange={(e) => setSearchQuery(e.target.value)}
|
|
162
|
-
/>
|
|
163
|
-
|
|
164
|
-
<select
|
|
165
|
-
className='select min-w-[200px]'
|
|
166
|
-
value={sortBy}
|
|
167
|
-
onChange={(e) => setSortBy(e.target.value as SortOption)}
|
|
168
|
-
>
|
|
169
|
-
<option value='default'>Sort: Default</option>
|
|
170
|
-
<option value='price-asc'>Price: Low to High</option>
|
|
171
|
-
<option value='price-desc'>Price: High to Low</option>
|
|
172
|
-
<option value='name-asc'>Name: A-Z</option>
|
|
173
|
-
</select>
|
|
174
|
-
|
|
175
|
-
{!loading && (
|
|
176
|
-
<div className='ml-auto text-sm text-gray-600'>
|
|
177
|
-
Showing {filteredProducts.length} of {products.length} products
|
|
178
|
-
</div>
|
|
179
|
-
)}
|
|
180
|
-
</div>
|
|
181
|
-
|
|
182
|
-
{/* Products Grid */}
|
|
183
|
-
{loading ? (
|
|
184
|
-
<div className='text-center py-16 text-gray-600 text-lg'>
|
|
185
|
-
<div className='animate-pulse'>Loading products...</div>
|
|
186
|
-
</div>
|
|
187
|
-
) : error ? (
|
|
188
|
-
<div className='text-center py-16 bg-red-50 text-red-600 text-lg rounded-xl'>
|
|
189
|
-
{error}
|
|
190
|
-
<div className='mt-4'>
|
|
191
|
-
<Link to='/categories' className='link font-semibold'>
|
|
192
|
-
← Back to Categories
|
|
193
|
-
</Link>
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
196
|
-
) : filteredProducts.length === 0 ? (
|
|
197
|
-
<div className='text-center py-16 text-gray-600 text-lg'>
|
|
198
|
-
<div className='text-6xl mb-4'>🔍</div>
|
|
199
|
-
<div className='mb-4'>No products found matching your criteria</div>
|
|
200
|
-
{searchQuery && (
|
|
201
|
-
<button
|
|
202
|
-
type='button'
|
|
203
|
-
className='btn-primary btn-sm'
|
|
204
|
-
onClick={() => setSearchQuery('')}
|
|
205
|
-
>
|
|
206
|
-
Clear Search
|
|
207
|
-
</button>
|
|
208
|
-
)}
|
|
209
|
-
</div>
|
|
210
|
-
) : (
|
|
211
|
-
<div className='product-grid animate-in'>
|
|
212
|
-
{filteredProducts.map((product) => (
|
|
213
|
-
<ProductCard key={product.id} product={product} />
|
|
214
|
-
))}
|
|
215
|
-
</div>
|
|
216
|
-
)}
|
|
217
|
-
</main>
|
|
218
|
-
</>
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export default CategoryPage;
|
|
223
|
-
export { ssr };
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
import { Link, useData } from 'reroute-js/react';
|
|
3
|
-
import Header from '../../components/Header';
|
|
4
|
-
import { getCategories } from '../../lib/api';
|
|
5
|
-
|
|
6
|
-
const categoryIcons: Record<string, string> = {
|
|
7
|
-
electronics: '💻',
|
|
8
|
-
jewelery: '💎',
|
|
9
|
-
"men's clothing": '👔',
|
|
10
|
-
"women's clothing": '👗',
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const categoryDescriptions: Record<string, string> = {
|
|
14
|
-
electronics: 'Explore the latest in technology and gadgets',
|
|
15
|
-
jewelery: 'Discover beautiful jewelry and accessories',
|
|
16
|
-
"men's clothing": 'Shop stylish clothing for men',
|
|
17
|
-
"women's clothing": 'Find fashionable apparel for women',
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const formatCategoryLabel = (category: string) =>
|
|
21
|
-
category
|
|
22
|
-
.split(' ')
|
|
23
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
24
|
-
.join(' ');
|
|
25
|
-
|
|
26
|
-
const ssr = {
|
|
27
|
-
async data() {
|
|
28
|
-
try {
|
|
29
|
-
const categories = await getCategories();
|
|
30
|
-
return { categories } as { categories: string[] };
|
|
31
|
-
} catch (e) {
|
|
32
|
-
console.error('ssr.data error for categories:', e);
|
|
33
|
-
return { categories: [], error: 'Failed to load categories.' };
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
function CategoriesIndexPage() {
|
|
39
|
-
const routeData = useData<{ categories: string[]; error?: string }>();
|
|
40
|
-
const [categories, setCategories] = useState(routeData?.categories ?? []);
|
|
41
|
-
const [loading, setLoading] = useState(!routeData);
|
|
42
|
-
const [error, setError] = useState<string | null>(routeData?.error ?? null);
|
|
43
|
-
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
if (routeData?.categories?.length) {
|
|
46
|
-
setCategories(routeData.categories);
|
|
47
|
-
setLoading(false);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
let cancelled = false;
|
|
52
|
-
setLoading(true);
|
|
53
|
-
setError(null);
|
|
54
|
-
|
|
55
|
-
getCategories()
|
|
56
|
-
.then((data) => {
|
|
57
|
-
if (!cancelled) {
|
|
58
|
-
setCategories(data);
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
.catch((err) => {
|
|
62
|
-
console.error('Failed to load categories:', err);
|
|
63
|
-
if (!cancelled) {
|
|
64
|
-
setError('Failed to load categories. Please try again later.');
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
.finally(() => {
|
|
68
|
-
if (!cancelled) {
|
|
69
|
-
setLoading(false);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
return () => {
|
|
74
|
-
cancelled = true;
|
|
75
|
-
};
|
|
76
|
-
}, [routeData]);
|
|
77
|
-
|
|
78
|
-
if (loading) {
|
|
79
|
-
return (
|
|
80
|
-
<>
|
|
81
|
-
<Header />
|
|
82
|
-
<main className='container-custom py-8'>
|
|
83
|
-
<div className='text-center py-16 text-gray-600 text-lg'>
|
|
84
|
-
<div className='animate-pulse'>Loading categories...</div>
|
|
85
|
-
</div>
|
|
86
|
-
</main>
|
|
87
|
-
</>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (error) {
|
|
92
|
-
return (
|
|
93
|
-
<>
|
|
94
|
-
<Header />
|
|
95
|
-
<main className='container-custom py-8'>
|
|
96
|
-
<div className='text-center py-16 bg-red-50 text-red-600 text-lg rounded-xl'>
|
|
97
|
-
{error}
|
|
98
|
-
</div>
|
|
99
|
-
</main>
|
|
100
|
-
</>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<>
|
|
106
|
-
<Header />
|
|
107
|
-
|
|
108
|
-
<main className='container-custom py-8'>
|
|
109
|
-
<div className='mb-12'>
|
|
110
|
-
<h1 className='section-heading'>Shop by Category</h1>
|
|
111
|
-
<p className='section-subheading'>
|
|
112
|
-
Browse curated collections and jump straight into your favorite
|
|
113
|
-
category.
|
|
114
|
-
</p>
|
|
115
|
-
</div>
|
|
116
|
-
|
|
117
|
-
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8 mb-12'>
|
|
118
|
-
{categories.map((category) => {
|
|
119
|
-
const label = formatCategoryLabel(category);
|
|
120
|
-
const description =
|
|
121
|
-
categoryDescriptions[category] || `Explore our ${label} picks`;
|
|
122
|
-
const icon = categoryIcons[category] || '🏷️';
|
|
123
|
-
const to = `/categories/${encodeURIComponent(category)}`;
|
|
124
|
-
|
|
125
|
-
return (
|
|
126
|
-
<Link
|
|
127
|
-
key={category}
|
|
128
|
-
to={to}
|
|
129
|
-
className='card card-hover group p-8 no-underline text-gray-900 block'
|
|
130
|
-
>
|
|
131
|
-
<div className='text-5xl mb-4'>{icon}</div>
|
|
132
|
-
<h2 className='text-2xl font-bold mb-2 capitalize group-hover:text-primary-600 transition-colors'>
|
|
133
|
-
{label}
|
|
134
|
-
</h2>
|
|
135
|
-
<p className='text-gray-600 text-sm mb-4'>{description}</p>
|
|
136
|
-
<span className='inline-flex items-center gap-2 btn-primary btn-sm'>
|
|
137
|
-
View Products <span aria-hidden='true'>→</span>
|
|
138
|
-
</span>
|
|
139
|
-
</Link>
|
|
140
|
-
);
|
|
141
|
-
})}
|
|
142
|
-
</div>
|
|
143
|
-
|
|
144
|
-
<div className='mt-16 p-8 bg-gray-50 rounded-xl text-center'>
|
|
145
|
-
<h3 className='text-2xl font-bold mb-6 text-gray-900'>
|
|
146
|
-
Why Shop With Us?
|
|
147
|
-
</h3>
|
|
148
|
-
<div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8'>
|
|
149
|
-
{[
|
|
150
|
-
{
|
|
151
|
-
icon: '🚚',
|
|
152
|
-
title: 'Fast Shipping',
|
|
153
|
-
desc: 'Free delivery on orders over $50',
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
icon: '🔒',
|
|
157
|
-
title: 'Secure Payment',
|
|
158
|
-
desc: 'Your data is always protected',
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
icon: '↩️',
|
|
162
|
-
title: 'Easy Returns',
|
|
163
|
-
desc: '30-day return policy',
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
icon: '⭐',
|
|
167
|
-
title: 'Quality Products',
|
|
168
|
-
desc: 'Verified and rated by customers',
|
|
169
|
-
},
|
|
170
|
-
].map((item) => (
|
|
171
|
-
<div key={item.title}>
|
|
172
|
-
<div className='text-4xl mb-2'>{item.icon}</div>
|
|
173
|
-
<div className='font-semibold text-gray-700 mb-1'>
|
|
174
|
-
{item.title}
|
|
175
|
-
</div>
|
|
176
|
-
<div className='text-sm text-gray-600'>{item.desc}</div>
|
|
177
|
-
</div>
|
|
178
|
-
))}
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
</main>
|
|
182
|
-
</>
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export default CategoriesIndexPage;
|
|
187
|
-
export { ssr };
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { Link, useData } from 'reroute-js/react';
|
|
2
|
-
import Header from '../components/Header';
|
|
3
|
-
import ProductCard from '../components/ProductCard';
|
|
4
|
-
import { getAllProducts, type Product } from '../lib/api';
|
|
5
|
-
|
|
6
|
-
const ssr = {
|
|
7
|
-
async data() {
|
|
8
|
-
try {
|
|
9
|
-
const products = await getAllProducts();
|
|
10
|
-
const shuffled = [...products].sort(() => Math.random() - 0.5);
|
|
11
|
-
return { featuredProducts: shuffled.slice(0, 4) } as {
|
|
12
|
-
featuredProducts: Product[];
|
|
13
|
-
};
|
|
14
|
-
} catch (e) {
|
|
15
|
-
console.error('ssr.data error for home:', e);
|
|
16
|
-
return {
|
|
17
|
-
featuredProducts: [],
|
|
18
|
-
error: 'Failed to load featured products.',
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
function HomePage() {
|
|
25
|
-
const data = useData<{ featuredProducts: Product[]; error?: string }>();
|
|
26
|
-
const featuredProducts = data?.featuredProducts || [];
|
|
27
|
-
const error = data?.error || null;
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<>
|
|
31
|
-
<Header />
|
|
32
|
-
|
|
33
|
-
<main className='container-custom py-8'>
|
|
34
|
-
{/* Hero Section */}
|
|
35
|
-
<section className='text-center py-12 mb-12 animate-slide-up'>
|
|
36
|
-
<h1 className='text-5xl md:text-6xl font-bold text-gray-900 mb-4 leading-tight'>
|
|
37
|
-
Welcome to Reroute Store
|
|
38
|
-
</h1>
|
|
39
|
-
<p className='text-xl text-gray-600 mb-8 max-w-2xl mx-auto'>
|
|
40
|
-
A modern e-commerce demo built with Reroute, showcasing file-based
|
|
41
|
-
routing, SSR, and seamless API integration with FakeStoreAPI
|
|
42
|
-
</p>
|
|
43
|
-
<div className='flex gap-4 justify-center flex-wrap'>
|
|
44
|
-
<Link to='/products' className='btn-primary btn-lg'>
|
|
45
|
-
Shop Now
|
|
46
|
-
</Link>
|
|
47
|
-
<Link to='/categories' className='btn-secondary btn-lg'>
|
|
48
|
-
Browse Categories
|
|
49
|
-
</Link>
|
|
50
|
-
</div>
|
|
51
|
-
</section>
|
|
52
|
-
|
|
53
|
-
{/* Featured Products */}
|
|
54
|
-
<section className='mb-16'>
|
|
55
|
-
<div className='flex justify-between items-center mb-6'>
|
|
56
|
-
<h2 className='section-heading mb-0'>Featured Products</h2>
|
|
57
|
-
<Link to='/products' className='link font-semibold text-lg'>
|
|
58
|
-
View All →
|
|
59
|
-
</Link>
|
|
60
|
-
</div>
|
|
61
|
-
|
|
62
|
-
{error ? (
|
|
63
|
-
<div className='text-center py-12 bg-red-50 text-red-600 text-lg rounded-xl'>
|
|
64
|
-
{error}
|
|
65
|
-
</div>
|
|
66
|
-
) : (
|
|
67
|
-
<div className='product-grid'>
|
|
68
|
-
{featuredProducts.map((product) => (
|
|
69
|
-
<ProductCard key={product.id} product={product} />
|
|
70
|
-
))}
|
|
71
|
-
</div>
|
|
72
|
-
)}
|
|
73
|
-
</section>
|
|
74
|
-
|
|
75
|
-
{/* Features */}
|
|
76
|
-
<section className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 mt-12 pt-8'>
|
|
77
|
-
<div className='feature-card'>
|
|
78
|
-
<div className='text-5xl mb-4'>⚡</div>
|
|
79
|
-
<h3 className='text-xl font-semibold mb-2 text-gray-900'>
|
|
80
|
-
Lightning Fast
|
|
81
|
-
</h3>
|
|
82
|
-
<p className='text-gray-600 leading-relaxed'>
|
|
83
|
-
Built on Bun for blazing fast performance and server-side
|
|
84
|
-
rendering
|
|
85
|
-
</p>
|
|
86
|
-
</div>
|
|
87
|
-
|
|
88
|
-
<div className='feature-card'>
|
|
89
|
-
<div className='text-5xl mb-4'>🎯</div>
|
|
90
|
-
<h3 className='text-xl font-semibold mb-2 text-gray-900'>
|
|
91
|
-
File-Based Routing
|
|
92
|
-
</h3>
|
|
93
|
-
<p className='text-gray-600 leading-relaxed'>
|
|
94
|
-
Automatic route generation from your file structure with zero
|
|
95
|
-
configuration
|
|
96
|
-
</p>
|
|
97
|
-
</div>
|
|
98
|
-
|
|
99
|
-
<div className='feature-card'>
|
|
100
|
-
<div className='text-5xl mb-4'>🔄</div>
|
|
101
|
-
<h3 className='text-xl font-semibold mb-2 text-gray-900'>
|
|
102
|
-
SSR Support
|
|
103
|
-
</h3>
|
|
104
|
-
<p className='text-gray-600 leading-relaxed'>
|
|
105
|
-
SEO-friendly server-side rendering with automatic hydration
|
|
106
|
-
</p>
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
<div className='feature-card'>
|
|
110
|
-
<div className='text-5xl mb-4'>📦</div>
|
|
111
|
-
<h3 className='text-xl font-semibold mb-2 text-gray-900'>
|
|
112
|
-
API Integration
|
|
113
|
-
</h3>
|
|
114
|
-
<p className='text-gray-600 leading-relaxed'>
|
|
115
|
-
Seamless integration with external APIs using serializable
|
|
116
|
-
responses
|
|
117
|
-
</p>
|
|
118
|
-
</div>
|
|
119
|
-
</section>
|
|
120
|
-
</main>
|
|
121
|
-
</>
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export default HomePage;
|
|
126
|
-
export { ssr };
|