@fecommunity/reactpress-template-hello-world 1.0.0-beta.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/LICENSE +21 -0
- package/README.md +297 -0
- package/bin/create-hello-world.js +139 -0
- package/components/Footer.tsx +35 -0
- package/components/Header.tsx +113 -0
- package/next-env.d.ts +5 -0
- package/next.config.js +10 -0
- package/package.json +27 -0
- package/pages/404.tsx +152 -0
- package/pages/about.tsx +295 -0
- package/pages/index.tsx +356 -0
- package/pages/toolkit-demo.tsx +490 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import { GetStaticProps } from 'next';
|
|
2
|
+
import Head from 'next/head';
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { types, utils, createApiInstance } from '@fecommunity/reactpress-toolkit';
|
|
5
|
+
import Header from '../components/Header';
|
|
6
|
+
import Footer from '../components/Footer';
|
|
7
|
+
|
|
8
|
+
// Create a custom API instance with the desired baseURL
|
|
9
|
+
const customApi = createApiInstance({
|
|
10
|
+
baseURL: 'https://api.gaoredu.com/',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Type definitions from the toolkit
|
|
14
|
+
type IArticle = types.IArticle;
|
|
15
|
+
type ICategory = types.ICategory;
|
|
16
|
+
type ITag = types.ITag;
|
|
17
|
+
|
|
18
|
+
interface ToolkitDemoProps {
|
|
19
|
+
articles: IArticle[];
|
|
20
|
+
categories: ICategory[];
|
|
21
|
+
tags: ITag[];
|
|
22
|
+
stats: {
|
|
23
|
+
articlesCount: number;
|
|
24
|
+
categoriesCount: number;
|
|
25
|
+
tagsCount: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function ToolkitDemo({ articles, categories, tags, stats }: ToolkitDemoProps) {
|
|
30
|
+
// Example usage of utils
|
|
31
|
+
const formatDate = (dateString: string) => {
|
|
32
|
+
const date = new Date(dateString);
|
|
33
|
+
return utils.formatDate(date, 'YYYY-MM-DD');
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const handleApiError = (error: any) => {
|
|
37
|
+
if (utils.ApiError.isInstance(error)) {
|
|
38
|
+
console.error(`API Error ${error.code}: ${error.message}`);
|
|
39
|
+
} else {
|
|
40
|
+
console.error('Unknown error:', error);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="container">
|
|
46
|
+
<Head>
|
|
47
|
+
<title>Toolkit Demo - Hello World Template</title>
|
|
48
|
+
<meta name="description" content="Demonstration of ReactPress Toolkit usage" />
|
|
49
|
+
<link rel="icon" href="/favicon.ico" />
|
|
50
|
+
</Head>
|
|
51
|
+
|
|
52
|
+
<Header />
|
|
53
|
+
|
|
54
|
+
<main className="main">
|
|
55
|
+
<div className="content-wrapper">
|
|
56
|
+
<div className="page-header">
|
|
57
|
+
<h1 className="page-title">ReactPress Toolkit Demo</h1>
|
|
58
|
+
<p className="page-description">
|
|
59
|
+
This page demonstrates how to use the ReactPress Toolkit to fetch data from the API.
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div className="demo-section">
|
|
64
|
+
<h2 className="section-title">Toolkit Features</h2>
|
|
65
|
+
<div className="features-grid">
|
|
66
|
+
<div className="feature-card">
|
|
67
|
+
<h3 className="feature-title">API Client</h3>
|
|
68
|
+
<p className="feature-description">Use createApiInstance() to create custom API clients</p>
|
|
69
|
+
</div>
|
|
70
|
+
<div className="feature-card">
|
|
71
|
+
<h3 className="feature-title">Types</h3>
|
|
72
|
+
<p className="feature-description">Import type definitions like IArticle, ICategory, ITag</p>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="feature-card">
|
|
75
|
+
<h3 className="feature-title">Utilities</h3>
|
|
76
|
+
<p className="feature-description">Use utility functions like formatDate, ApiError handling</p>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div className="stats-section">
|
|
82
|
+
<h2 className="section-title">Site Statistics</h2>
|
|
83
|
+
<div className="stats-grid">
|
|
84
|
+
<div className="stat-card">
|
|
85
|
+
<div className="stat-value">{stats.articlesCount}</div>
|
|
86
|
+
<div className="stat-label">Articles</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div className="stat-card">
|
|
89
|
+
<div className="stat-value">{stats.categoriesCount}</div>
|
|
90
|
+
<div className="stat-label">Categories</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div className="stat-card">
|
|
93
|
+
<div className="stat-value">{stats.tagsCount}</div>
|
|
94
|
+
<div className="stat-label">Tags</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div className="content-section">
|
|
100
|
+
<h2 className="section-title">Latest Articles</h2>
|
|
101
|
+
<div className="articles-list">
|
|
102
|
+
{articles.slice(0, 5).map((article) => (
|
|
103
|
+
<div key={article.id} className="article-item">
|
|
104
|
+
<h3 className="article-title">{article.title}</h3>
|
|
105
|
+
{article.summary && <p className="article-summary">{article.summary}</p>}
|
|
106
|
+
<div className="article-meta">
|
|
107
|
+
{article.category && <span className="article-category">{article.category.label}</span>}
|
|
108
|
+
<span className="publish-date">{formatDate(article.publishAt)}</span>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
))}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div className="content-section">
|
|
116
|
+
<h2 className="section-title">Categories</h2>
|
|
117
|
+
<div className="categories-grid">
|
|
118
|
+
{categories.map((category) => (
|
|
119
|
+
<div key={category.value} className="category-card">
|
|
120
|
+
<h3 className="category-name">{category.label}</h3>
|
|
121
|
+
<div className="category-count">{(category as any).articleCount || 0} articles</div>
|
|
122
|
+
</div>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div className="content-section">
|
|
128
|
+
<h2 className="section-title">Popular Tags</h2>
|
|
129
|
+
<div className="tags-list">
|
|
130
|
+
{tags.slice(0, 20).map((tag) => (
|
|
131
|
+
<span key={tag.value} className="tag-item">
|
|
132
|
+
{tag.label}
|
|
133
|
+
</span>
|
|
134
|
+
))}
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div className="cta-section">
|
|
139
|
+
<Link href="/">
|
|
140
|
+
<a className="back-link">← Back to Home</a>
|
|
141
|
+
</Link>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</main>
|
|
145
|
+
|
|
146
|
+
<Footer />
|
|
147
|
+
|
|
148
|
+
<style jsx>{`
|
|
149
|
+
.container {
|
|
150
|
+
min-height: 100vh;
|
|
151
|
+
display: flex;
|
|
152
|
+
flex-direction: column;
|
|
153
|
+
background-color: #f8f9fa;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.main {
|
|
157
|
+
flex: 1;
|
|
158
|
+
padding: 3rem 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.content-wrapper {
|
|
162
|
+
max-width: 1200px;
|
|
163
|
+
margin: 0 auto;
|
|
164
|
+
padding: 0 2rem;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.page-header {
|
|
168
|
+
text-align: center;
|
|
169
|
+
margin-bottom: 3rem;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.page-title {
|
|
173
|
+
font-size: 2.5rem;
|
|
174
|
+
font-weight: 800;
|
|
175
|
+
color: #111827;
|
|
176
|
+
margin: 0 0 1rem 0;
|
|
177
|
+
letter-spacing: -0.025em;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.page-description {
|
|
181
|
+
color: #6b7280;
|
|
182
|
+
font-size: 1.25rem;
|
|
183
|
+
max-width: 700px;
|
|
184
|
+
margin: 0 auto;
|
|
185
|
+
line-height: 1.7;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.demo-section {
|
|
189
|
+
margin-bottom: 3rem;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.features-grid {
|
|
193
|
+
display: grid;
|
|
194
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
195
|
+
gap: 1.5rem;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.feature-card {
|
|
199
|
+
background: #fff;
|
|
200
|
+
border-radius: 12px;
|
|
201
|
+
padding: 1.5rem;
|
|
202
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
|
203
|
+
transition: all 0.3s ease;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.feature-card:hover {
|
|
207
|
+
transform: translateY(-3px);
|
|
208
|
+
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.feature-title {
|
|
212
|
+
font-size: 1.25rem;
|
|
213
|
+
font-weight: 600;
|
|
214
|
+
color: #111827;
|
|
215
|
+
margin: 0 0 0.75rem 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.feature-description {
|
|
219
|
+
color: #6b7280;
|
|
220
|
+
line-height: 1.6;
|
|
221
|
+
margin: 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.stats-section {
|
|
225
|
+
margin-bottom: 3rem;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.section-title {
|
|
229
|
+
font-size: 1.75rem;
|
|
230
|
+
font-weight: 700;
|
|
231
|
+
color: #111827;
|
|
232
|
+
margin: 0 0 1.5rem 0;
|
|
233
|
+
padding-bottom: 0.75rem;
|
|
234
|
+
border-bottom: 2px solid #e5e7eb;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.stats-grid {
|
|
238
|
+
display: grid;
|
|
239
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
240
|
+
gap: 1.5rem;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.stat-card {
|
|
244
|
+
background: #fff;
|
|
245
|
+
border-radius: 12px;
|
|
246
|
+
padding: 2rem;
|
|
247
|
+
text-align: center;
|
|
248
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
|
249
|
+
transition: all 0.3s ease;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.stat-card:hover {
|
|
253
|
+
transform: translateY(-5px);
|
|
254
|
+
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.stat-value {
|
|
258
|
+
font-size: 2.5rem;
|
|
259
|
+
font-weight: 700;
|
|
260
|
+
color: #3b82f6;
|
|
261
|
+
margin-bottom: 0.5rem;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.stat-label {
|
|
265
|
+
font-size: 1.1rem;
|
|
266
|
+
color: #6b7280;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.content-section {
|
|
270
|
+
margin-bottom: 3rem;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.articles-list {
|
|
274
|
+
display: flex;
|
|
275
|
+
flex-direction: column;
|
|
276
|
+
gap: 1.5rem;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.article-item {
|
|
280
|
+
background: #fff;
|
|
281
|
+
border-radius: 12px;
|
|
282
|
+
padding: 1.5rem;
|
|
283
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.article-title {
|
|
287
|
+
font-size: 1.25rem;
|
|
288
|
+
font-weight: 600;
|
|
289
|
+
color: #111827;
|
|
290
|
+
margin: 0 0 0.75rem 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.article-summary {
|
|
294
|
+
color: #6b7280;
|
|
295
|
+
line-height: 1.6;
|
|
296
|
+
margin: 0 0 1rem 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.article-meta {
|
|
300
|
+
display: flex;
|
|
301
|
+
gap: 1rem;
|
|
302
|
+
font-size: 0.9rem;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.article-category {
|
|
306
|
+
background: #eff6ff;
|
|
307
|
+
color: #3b82f6;
|
|
308
|
+
padding: 0.25rem 0.75rem;
|
|
309
|
+
border-radius: 9999px;
|
|
310
|
+
font-weight: 500;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.publish-date {
|
|
314
|
+
color: #9ca3af;
|
|
315
|
+
font-style: italic;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.categories-grid {
|
|
319
|
+
display: grid;
|
|
320
|
+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
321
|
+
gap: 1rem;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.category-card {
|
|
325
|
+
background: #fff;
|
|
326
|
+
border-radius: 12px;
|
|
327
|
+
padding: 1.5rem;
|
|
328
|
+
text-align: center;
|
|
329
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
330
|
+
transition: all 0.3s ease;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.category-card:hover {
|
|
334
|
+
transform: translateY(-3px);
|
|
335
|
+
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.category-name {
|
|
339
|
+
font-size: 1.1rem;
|
|
340
|
+
font-weight: 600;
|
|
341
|
+
color: #111827;
|
|
342
|
+
margin: 0 0 0.5rem 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.category-count {
|
|
346
|
+
color: #6b7280;
|
|
347
|
+
font-size: 0.9rem;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.tags-list {
|
|
351
|
+
display: flex;
|
|
352
|
+
flex-wrap: wrap;
|
|
353
|
+
gap: 0.5rem;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.tag-item {
|
|
357
|
+
background: #f3f4f6;
|
|
358
|
+
color: #4b5563;
|
|
359
|
+
padding: 0.4rem 0.8rem;
|
|
360
|
+
border-radius: 0.375rem;
|
|
361
|
+
font-size: 0.875rem;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.cta-section {
|
|
365
|
+
text-align: center;
|
|
366
|
+
margin-top: 2rem;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.back-link {
|
|
370
|
+
display: inline-block;
|
|
371
|
+
color: #3b82f6;
|
|
372
|
+
text-decoration: none;
|
|
373
|
+
font-weight: 600;
|
|
374
|
+
padding: 0.75rem 1.5rem;
|
|
375
|
+
border: 2px solid #3b82f6;
|
|
376
|
+
border-radius: 12px;
|
|
377
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.back-link:hover {
|
|
381
|
+
background-color: #3b82f6;
|
|
382
|
+
color: #fff;
|
|
383
|
+
transform: translateY(-2px);
|
|
384
|
+
box-shadow: 0 4px 10px rgba(59, 130, 246, 0.3);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* Responsive Design */
|
|
388
|
+
@media (max-width: 768px) {
|
|
389
|
+
.content-wrapper {
|
|
390
|
+
padding: 0 1rem;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.page-title {
|
|
394
|
+
font-size: 2rem;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.page-description {
|
|
398
|
+
font-size: 1.1rem;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.features-grid {
|
|
402
|
+
grid-template-columns: 1fr;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.stats-grid {
|
|
406
|
+
grid-template-columns: 1fr;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.categories-grid {
|
|
410
|
+
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
`}</style>
|
|
414
|
+
|
|
415
|
+
<style jsx global>{`
|
|
416
|
+
html,
|
|
417
|
+
body {
|
|
418
|
+
padding: 0;
|
|
419
|
+
margin: 0;
|
|
420
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans',
|
|
421
|
+
'Helvetica Neue', sans-serif;
|
|
422
|
+
background-color: #f8f9fa;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
* {
|
|
426
|
+
box-sizing: border-box;
|
|
427
|
+
}
|
|
428
|
+
`}</style>
|
|
429
|
+
</div>
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export const getStaticProps: GetStaticProps<ToolkitDemoProps> = async () => {
|
|
434
|
+
try {
|
|
435
|
+
// Demonstrate different ways to use the ReactPress Toolkit
|
|
436
|
+
|
|
437
|
+
// Method 1: Using a custom API instance
|
|
438
|
+
const [articlesResponse, categoriesResponse, tagsResponse] = await Promise.all([
|
|
439
|
+
customApi.article.findAll() as any,
|
|
440
|
+
customApi.category.findAll() as any,
|
|
441
|
+
customApi.tag.findAll() as any,
|
|
442
|
+
]);
|
|
443
|
+
|
|
444
|
+
// Method 2: Using the default API instance (commented out as example)
|
|
445
|
+
// const articlesResponse = await api.article.findAll() as any;
|
|
446
|
+
|
|
447
|
+
// Extract the actual data from the responses
|
|
448
|
+
const articles = articlesResponse?.data?.data?.[0] || [];
|
|
449
|
+
const categories = categoriesResponse?.data?.data || [];
|
|
450
|
+
const tags = tagsResponse?.data?.data || [];
|
|
451
|
+
|
|
452
|
+
// Calculate statistics
|
|
453
|
+
const stats = {
|
|
454
|
+
articlesCount: articles.length,
|
|
455
|
+
categoriesCount: categories.length,
|
|
456
|
+
tagsCount: tags.length,
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
props: {
|
|
461
|
+
articles,
|
|
462
|
+
categories,
|
|
463
|
+
tags,
|
|
464
|
+
stats,
|
|
465
|
+
},
|
|
466
|
+
revalidate: 60, // Revalidate at most once per minute
|
|
467
|
+
};
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.error('Failed to fetch data:', error);
|
|
470
|
+
|
|
471
|
+
// Example of using utils.ApiError
|
|
472
|
+
if (utils.ApiError.isInstance(error)) {
|
|
473
|
+
console.error(`API Error ${error.code}: ${error.message}`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
props: {
|
|
478
|
+
articles: [],
|
|
479
|
+
categories: [],
|
|
480
|
+
tags: [],
|
|
481
|
+
stats: {
|
|
482
|
+
articlesCount: 0,
|
|
483
|
+
categoriesCount: 0,
|
|
484
|
+
tagsCount: 0,
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
revalidate: 60,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es5",
|
|
4
|
+
"lib": [
|
|
5
|
+
"dom",
|
|
6
|
+
"dom.iterable",
|
|
7
|
+
"esnext"
|
|
8
|
+
],
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"strict": false,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"esModuleInterop": true,
|
|
15
|
+
"module": "esnext",
|
|
16
|
+
"moduleResolution": "node",
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
"isolatedModules": true,
|
|
19
|
+
"jsx": "preserve",
|
|
20
|
+
"incremental": true
|
|
21
|
+
},
|
|
22
|
+
"include": [
|
|
23
|
+
"next-env.d.ts",
|
|
24
|
+
"**/*.ts",
|
|
25
|
+
"**/*.tsx"
|
|
26
|
+
],
|
|
27
|
+
"exclude": [
|
|
28
|
+
"node_modules"
|
|
29
|
+
]
|
|
30
|
+
}
|