@fecommunity/reactpress-template-hello-world 1.0.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.
@@ -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
+ }