@tanstack/create 0.61.5 → 0.62.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/CHANGELOG.md +23 -0
- package/dist/config-file.js +5 -2
- package/dist/custom-add-ons/starter.js +45 -28
- package/dist/file-helpers.js +1 -0
- package/dist/frameworks/react/add-ons/shadcn/assets/src/styles.css +224 -15
- package/dist/frameworks/react/add-ons/store/assets/src/lib/demo-store.ts +5 -6
- package/dist/frameworks/react/add-ons/store/assets/src/routes/demo/store.tsx.ejs +1 -1
- package/dist/frameworks/react/add-ons/store/package.json +2 -2
- package/dist/frameworks/react/add-ons/strapi/README.md +158 -8
- package/dist/frameworks/react/add-ons/strapi/assets/_dot_env.local.append +1 -1
- package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/block-renderer.tsx +55 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/index.ts +14 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/media.tsx +27 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/quote.tsx +19 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/rich-text.tsx +11 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/slider.tsx +28 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/components/markdown-content.tsx +74 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/components/pagination.tsx +120 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/components/search.tsx +35 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/components/strapi-image.tsx +47 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/data/loaders/articles.ts +106 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/data/loaders/index.ts +28 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/data/strapi-sdk.ts +9 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/lib/strapi-utils.ts +25 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi.$articleId.tsx +170 -0
- package/dist/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi.tsx +269 -43
- package/dist/frameworks/react/add-ons/strapi/assets/src/types/strapi.ts +90 -0
- package/dist/frameworks/react/add-ons/strapi/info.json +3 -3
- package/dist/frameworks/react/add-ons/strapi/package.json +5 -2
- package/dist/frameworks/react/index.js +2 -2
- package/dist/frameworks/react/project/base/content/blog/fifth-post.mdx.ejs +54 -0
- package/dist/frameworks/react/project/base/content/blog/first-post.md.ejs +47 -0
- package/dist/frameworks/react/project/base/content/blog/fourth-post.md.ejs +42 -0
- package/dist/frameworks/react/project/base/content/blog/second-post.mdx.ejs +46 -0
- package/dist/frameworks/react/project/base/content/blog/third-post.md.ejs +49 -0
- package/dist/frameworks/react/project/base/content-collections.ts.ejs +37 -0
- package/dist/frameworks/react/project/base/package.json +8 -1
- package/dist/frameworks/react/project/base/public/images/lagoon-1.svg +13 -0
- package/dist/frameworks/react/project/base/public/images/lagoon-2.svg +12 -0
- package/dist/frameworks/react/project/base/public/images/lagoon-3.svg +12 -0
- package/dist/frameworks/react/project/base/public/images/lagoon-4.svg +12 -0
- package/dist/frameworks/react/project/base/public/images/lagoon-5.svg +12 -0
- package/dist/frameworks/react/project/base/public/images/lagoon-about.svg +14 -0
- package/dist/frameworks/react/project/base/src/components/Footer.tsx.ejs +42 -0
- package/dist/frameworks/react/project/base/src/components/Header.tsx.ejs +92 -138
- package/dist/frameworks/react/project/base/src/components/MdxCallout.tsx.ejs +16 -0
- package/dist/frameworks/react/project/base/src/components/MdxMetrics.tsx.ejs +23 -0
- package/dist/frameworks/react/project/base/src/components/ThemeToggle.tsx.ejs +81 -0
- package/dist/frameworks/react/project/base/src/lib/site.ts.ejs +4 -0
- package/dist/frameworks/react/project/base/src/main.tsx.ejs +0 -1
- package/dist/frameworks/react/project/base/src/routes/__root.tsx.ejs +10 -6
- package/dist/frameworks/react/project/base/src/routes/about.tsx.ejs +27 -0
- package/dist/frameworks/react/project/base/src/routes/blog.$slug.tsx.ejs +71 -0
- package/dist/frameworks/react/project/base/src/routes/blog.index.tsx.ejs +93 -0
- package/dist/frameworks/react/project/base/src/routes/index.tsx.ejs +58 -91
- package/dist/frameworks/react/project/base/src/routes/rss[.]xml.ts.ejs +35 -0
- package/dist/frameworks/react/project/base/src/styles.css.ejs +268 -6
- package/dist/frameworks/react/project/base/tsconfig.json.ejs +2 -0
- package/dist/frameworks/react/project/base/vite.config.ts.ejs +2 -0
- package/dist/frameworks/solid/add-ons/store/assets/src/lib/demo-store.ts +5 -6
- package/dist/frameworks/solid/add-ons/store/assets/src/routes/demo.store.tsx.ejs +2 -2
- package/dist/frameworks/solid/examples/tanchat/assets/src/lib/demo-store.ts +5 -6
- package/dist/frameworks/solid/project/base/src/components/Header.tsx.ejs +8 -6
- package/dist/frameworks/solid/project/base/src/routes/__root.tsx.ejs +1 -1
- package/dist/frameworks/solid/project/base/src/routes/index.tsx.ejs +1 -1
- package/dist/frameworks.js +3 -0
- package/dist/package-json.js +1 -1
- package/dist/registry.js +21 -4
- package/dist/types/registry.d.ts +38 -0
- package/package.json +1 -1
- package/src/config-file.ts +6 -2
- package/src/custom-add-ons/starter.ts +30 -10
- package/src/file-helpers.ts +1 -0
- package/src/frameworks/react/add-ons/shadcn/assets/src/styles.css +224 -15
- package/src/frameworks/react/add-ons/store/assets/src/lib/demo-store.ts +5 -6
- package/src/frameworks/react/add-ons/store/assets/src/routes/demo/store.tsx.ejs +1 -1
- package/src/frameworks/react/add-ons/store/package.json +2 -2
- package/src/frameworks/react/add-ons/strapi/README.md +158 -8
- package/src/frameworks/react/add-ons/strapi/assets/_dot_env.local.append +1 -1
- package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/block-renderer.tsx +55 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/index.ts +14 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/media.tsx +27 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/quote.tsx +19 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/rich-text.tsx +11 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/slider.tsx +28 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/components/markdown-content.tsx +74 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/components/pagination.tsx +120 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/components/search.tsx +35 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/components/strapi-image.tsx +47 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/data/loaders/articles.ts +106 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/data/loaders/index.ts +28 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/data/strapi-sdk.ts +9 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/lib/strapi-utils.ts +25 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi.$articleId.tsx +170 -0
- package/src/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi.tsx +269 -43
- package/src/frameworks/react/add-ons/strapi/assets/src/types/strapi.ts +90 -0
- package/src/frameworks/react/add-ons/strapi/info.json +3 -3
- package/src/frameworks/react/add-ons/strapi/package.json +5 -2
- package/src/frameworks/react/index.ts +2 -2
- package/src/frameworks/react/project/base/content/blog/fifth-post.mdx.ejs +54 -0
- package/src/frameworks/react/project/base/content/blog/first-post.md.ejs +47 -0
- package/src/frameworks/react/project/base/content/blog/fourth-post.md.ejs +42 -0
- package/src/frameworks/react/project/base/content/blog/second-post.mdx.ejs +46 -0
- package/src/frameworks/react/project/base/content/blog/third-post.md.ejs +49 -0
- package/src/frameworks/react/project/base/content-collections.ts.ejs +37 -0
- package/src/frameworks/react/project/base/package.json +8 -1
- package/src/frameworks/react/project/base/public/images/lagoon-1.svg +13 -0
- package/src/frameworks/react/project/base/public/images/lagoon-2.svg +12 -0
- package/src/frameworks/react/project/base/public/images/lagoon-3.svg +12 -0
- package/src/frameworks/react/project/base/public/images/lagoon-4.svg +12 -0
- package/src/frameworks/react/project/base/public/images/lagoon-5.svg +12 -0
- package/src/frameworks/react/project/base/public/images/lagoon-about.svg +14 -0
- package/src/frameworks/react/project/base/src/components/Footer.tsx.ejs +42 -0
- package/src/frameworks/react/project/base/src/components/Header.tsx.ejs +92 -138
- package/src/frameworks/react/project/base/src/components/MdxCallout.tsx.ejs +16 -0
- package/src/frameworks/react/project/base/src/components/MdxMetrics.tsx.ejs +23 -0
- package/src/frameworks/react/project/base/src/components/ThemeToggle.tsx.ejs +81 -0
- package/src/frameworks/react/project/base/src/lib/site.ts.ejs +4 -0
- package/src/frameworks/react/project/base/src/main.tsx.ejs +0 -1
- package/src/frameworks/react/project/base/src/routes/__root.tsx.ejs +10 -6
- package/src/frameworks/react/project/base/src/routes/about.tsx.ejs +27 -0
- package/src/frameworks/react/project/base/src/routes/blog.$slug.tsx.ejs +71 -0
- package/src/frameworks/react/project/base/src/routes/blog.index.tsx.ejs +93 -0
- package/src/frameworks/react/project/base/src/routes/index.tsx.ejs +58 -91
- package/src/frameworks/react/project/base/src/routes/rss[.]xml.ts.ejs +35 -0
- package/src/frameworks/react/project/base/src/styles.css.ejs +268 -6
- package/src/frameworks/react/project/base/tsconfig.json.ejs +2 -0
- package/src/frameworks/react/project/base/vite.config.ts.ejs +2 -0
- package/src/frameworks/solid/add-ons/store/assets/src/lib/demo-store.ts +5 -6
- package/src/frameworks/solid/add-ons/store/assets/src/routes/demo.store.tsx.ejs +2 -2
- package/src/frameworks/solid/examples/tanchat/assets/src/lib/demo-store.ts +5 -6
- package/src/frameworks/solid/project/base/src/components/Header.tsx.ejs +8 -6
- package/src/frameworks/solid/project/base/src/routes/__root.tsx.ejs +1 -1
- package/src/frameworks/solid/project/base/src/routes/index.tsx.ejs +1 -1
- package/src/frameworks.ts +4 -0
- package/src/package-json.ts +1 -1
- package/src/registry.ts +28 -4
- package/tests/add-ons.test.ts +4 -4
- package/tests/config-file.test.ts +3 -3
- package/tests/custom-add-ons/starter.test.ts +34 -2
- package/tests/frameworks.test.ts +24 -0
- package/tests/options.test.ts +4 -4
- package/tests/utils.test.ts +2 -2
- package/dist/frameworks/react/add-ons/strapi/assets/src/lib/strapiClient.ts +0 -7
- package/dist/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi_.$articleId.tsx +0 -78
- package/src/frameworks/react/add-ons/strapi/assets/src/lib/strapiClient.ts +0 -7
- package/src/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi_.$articleId.tsx +0 -78
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { createFileRoute, Link } from "@tanstack/react-router";
|
|
2
|
+
import { strapiApi } from "@/data/loaders";
|
|
3
|
+
import { StrapiImage } from "@/components/strapi-image";
|
|
4
|
+
import { BlockRenderer } from "@/components/blocks";
|
|
5
|
+
import type { TArticle } from "@/types/strapi";
|
|
6
|
+
|
|
7
|
+
export const Route = createFileRoute("/demo/strapi/$articleId")({
|
|
8
|
+
component: RouteComponent,
|
|
9
|
+
errorComponent: ErrorComponent,
|
|
10
|
+
loader: async ({ params }) => {
|
|
11
|
+
try {
|
|
12
|
+
const response = await strapiApi.articles.getArticleByIdData({
|
|
13
|
+
data: params.articleId,
|
|
14
|
+
});
|
|
15
|
+
return { success: true, article: response.data };
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return {
|
|
18
|
+
success: false,
|
|
19
|
+
error: error instanceof Error ? error.message : "Failed to load article",
|
|
20
|
+
article: null,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
function ErrorComponent({ error }: { error: Error }) {
|
|
27
|
+
return (
|
|
28
|
+
<div className="min-h-screen bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900 p-8">
|
|
29
|
+
<div className="max-w-4xl mx-auto">
|
|
30
|
+
<Link
|
|
31
|
+
to="/demo/strapi"
|
|
32
|
+
className="inline-flex items-center text-cyan-400 hover:text-cyan-300 mb-6 transition-colors"
|
|
33
|
+
>
|
|
34
|
+
← Back to Articles
|
|
35
|
+
</Link>
|
|
36
|
+
<div className="bg-red-900/20 border border-red-500/50 rounded-xl p-8 text-center">
|
|
37
|
+
<h1 className="text-2xl font-bold text-red-400 mb-4">Error Loading Article</h1>
|
|
38
|
+
<p className="text-gray-300">{error.message}</p>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function RouteComponent() {
|
|
46
|
+
const { success, article, error } = Route.useLoaderData() as {
|
|
47
|
+
success: boolean;
|
|
48
|
+
article: TArticle | null;
|
|
49
|
+
error?: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Show error state
|
|
53
|
+
if (!success || !article) {
|
|
54
|
+
return (
|
|
55
|
+
<div className="min-h-screen bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900 p-8">
|
|
56
|
+
<div className="max-w-4xl mx-auto">
|
|
57
|
+
<Link
|
|
58
|
+
to="/demo/strapi"
|
|
59
|
+
className="inline-flex items-center text-cyan-400 hover:text-cyan-300 mb-6 transition-colors"
|
|
60
|
+
>
|
|
61
|
+
<svg
|
|
62
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
63
|
+
className="h-5 w-5 mr-2"
|
|
64
|
+
viewBox="0 0 20 20"
|
|
65
|
+
fill="currentColor"
|
|
66
|
+
>
|
|
67
|
+
<path
|
|
68
|
+
fillRule="evenodd"
|
|
69
|
+
d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z"
|
|
70
|
+
clipRule="evenodd"
|
|
71
|
+
/>
|
|
72
|
+
</svg>
|
|
73
|
+
Back to Articles
|
|
74
|
+
</Link>
|
|
75
|
+
|
|
76
|
+
<div className="bg-amber-900/20 border border-amber-500/50 rounded-xl p-8">
|
|
77
|
+
<div className="flex items-start gap-4">
|
|
78
|
+
<div className="text-amber-400 text-2xl">⚠️</div>
|
|
79
|
+
<div>
|
|
80
|
+
<h2 className="text-xl font-semibold text-amber-400 mb-2">
|
|
81
|
+
{error || "Article Not Found"}
|
|
82
|
+
</h2>
|
|
83
|
+
<p className="text-gray-300">
|
|
84
|
+
Make sure the Strapi server is running and the article exists.
|
|
85
|
+
</p>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="min-h-screen bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900 p-8">
|
|
96
|
+
<div className="max-w-4xl mx-auto">
|
|
97
|
+
<Link
|
|
98
|
+
to="/demo/strapi"
|
|
99
|
+
className="inline-flex items-center text-cyan-400 hover:text-cyan-300 mb-6 transition-colors"
|
|
100
|
+
>
|
|
101
|
+
<svg
|
|
102
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
103
|
+
className="h-5 w-5 mr-2"
|
|
104
|
+
viewBox="0 0 20 20"
|
|
105
|
+
fill="currentColor"
|
|
106
|
+
>
|
|
107
|
+
<path
|
|
108
|
+
fillRule="evenodd"
|
|
109
|
+
d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z"
|
|
110
|
+
clipRule="evenodd"
|
|
111
|
+
/>
|
|
112
|
+
</svg>
|
|
113
|
+
Back to Articles
|
|
114
|
+
</Link>
|
|
115
|
+
|
|
116
|
+
<article className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-xl overflow-hidden">
|
|
117
|
+
<StrapiImage
|
|
118
|
+
src={article.cover?.url}
|
|
119
|
+
alt={article.cover?.alternativeText || article.title}
|
|
120
|
+
className="w-full h-64"
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
<div className="p-8">
|
|
124
|
+
<h1 className="text-4xl font-bold text-white mb-4">
|
|
125
|
+
{article.title || "Untitled"}
|
|
126
|
+
</h1>
|
|
127
|
+
|
|
128
|
+
<div className="flex items-center gap-4 mb-6">
|
|
129
|
+
{article.author?.name && (
|
|
130
|
+
<span className="text-gray-400">
|
|
131
|
+
By{" "}
|
|
132
|
+
<span className="text-cyan-400">{article.author.name}</span>
|
|
133
|
+
</span>
|
|
134
|
+
)}
|
|
135
|
+
{article.createdAt && (
|
|
136
|
+
<span className="text-sm text-cyan-400/70">
|
|
137
|
+
{new Date(article.createdAt).toLocaleDateString("en-US", {
|
|
138
|
+
year: "numeric",
|
|
139
|
+
month: "long",
|
|
140
|
+
day: "numeric",
|
|
141
|
+
})}
|
|
142
|
+
</span>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
{article.category?.name && (
|
|
147
|
+
<div className="mb-6">
|
|
148
|
+
<span className="text-xs px-3 py-1 bg-slate-700 text-cyan-400 rounded-full">
|
|
149
|
+
{article.category.name}
|
|
150
|
+
</span>
|
|
151
|
+
</div>
|
|
152
|
+
)}
|
|
153
|
+
|
|
154
|
+
{article.description && (
|
|
155
|
+
<div className="mb-8">
|
|
156
|
+
<p className="text-xl text-gray-300 leading-relaxed">
|
|
157
|
+
{article.description}
|
|
158
|
+
</p>
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{article.blocks && article.blocks.length > 0 && (
|
|
163
|
+
<BlockRenderer blocks={article.blocks} />
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
</article>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
@@ -1,64 +1,290 @@
|
|
|
1
|
-
import { articles } from '#/lib/strapiClient'
|
|
2
1
|
import { createFileRoute, Link } from '@tanstack/react-router'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { strapiApi } from '@/data/loaders'
|
|
4
|
+
import { StrapiImage } from '@/components/strapi-image'
|
|
5
|
+
import { Search } from '@/components/search'
|
|
6
|
+
import { Pagination } from '@/components/pagination'
|
|
7
|
+
import type { TArticle } from '@/types/strapi'
|
|
8
|
+
|
|
9
|
+
type LoaderResult = {
|
|
10
|
+
status: 'success' | 'empty' | 'error'
|
|
11
|
+
articles: TArticle[]
|
|
12
|
+
meta?: { pagination?: { page: number; pageCount: number; total: number } }
|
|
13
|
+
error?: string
|
|
14
|
+
query?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const searchSchema = z.object({
|
|
18
|
+
query: z.string().optional(),
|
|
19
|
+
page: z.number().default(1),
|
|
20
|
+
})
|
|
3
21
|
|
|
4
22
|
export const Route = createFileRoute('/demo/strapi')({
|
|
5
23
|
component: RouteComponent,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
24
|
+
validateSearch: searchSchema,
|
|
25
|
+
loaderDeps: ({ search }) => ({ search }),
|
|
26
|
+
loader: async ({ deps }): Promise<LoaderResult> => {
|
|
27
|
+
const { query, page } = deps.search
|
|
28
|
+
try {
|
|
29
|
+
const response = await strapiApi.articles.getArticlesData({
|
|
30
|
+
data: { query, page },
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// Check if we got data
|
|
34
|
+
if (!response || !response.data) {
|
|
35
|
+
return {
|
|
36
|
+
status: 'empty',
|
|
37
|
+
articles: [],
|
|
38
|
+
meta: response?.meta,
|
|
39
|
+
query,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check if data array is empty
|
|
44
|
+
if (response.data.length === 0) {
|
|
45
|
+
return {
|
|
46
|
+
status: 'empty',
|
|
47
|
+
articles: [],
|
|
48
|
+
meta: response.meta,
|
|
49
|
+
query,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
status: 'success',
|
|
55
|
+
articles: response.data,
|
|
56
|
+
meta: response.meta,
|
|
57
|
+
query,
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('Strapi fetch error:', error)
|
|
61
|
+
return {
|
|
62
|
+
status: 'error',
|
|
63
|
+
articles: [],
|
|
64
|
+
error:
|
|
65
|
+
error instanceof Error
|
|
66
|
+
? error.message
|
|
67
|
+
: 'Failed to connect to Strapi',
|
|
68
|
+
query,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
9
71
|
},
|
|
10
72
|
})
|
|
11
73
|
|
|
74
|
+
function StrapiServerInstructions() {
|
|
75
|
+
return (
|
|
76
|
+
<div className="bg-slate-800/50 rounded-lg p-6 text-left mt-6">
|
|
77
|
+
<h2 className="text-lg font-semibold text-white mb-4">
|
|
78
|
+
Start the Strapi Server
|
|
79
|
+
</h2>
|
|
80
|
+
<div className="space-y-2 text-sm font-mono text-gray-400">
|
|
81
|
+
<p>
|
|
82
|
+
<span className="text-cyan-400">$</span> cd ../server
|
|
83
|
+
</p>
|
|
84
|
+
<p>
|
|
85
|
+
<span className="text-cyan-400">$</span> npm install
|
|
86
|
+
</p>
|
|
87
|
+
<p>
|
|
88
|
+
<span className="text-cyan-400">$</span> npm run develop
|
|
89
|
+
</p>
|
|
90
|
+
</div>
|
|
91
|
+
<p className="text-gray-500 text-sm mt-4">
|
|
92
|
+
Then create an admin at{' '}
|
|
93
|
+
<a
|
|
94
|
+
href="http://localhost:1337/admin"
|
|
95
|
+
target="_blank"
|
|
96
|
+
rel="noopener noreferrer"
|
|
97
|
+
className="text-cyan-400 hover:underline"
|
|
98
|
+
>
|
|
99
|
+
http://localhost:1337/admin
|
|
100
|
+
</a>
|
|
101
|
+
</p>
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function ConnectionError({ error }: { error?: string }) {
|
|
107
|
+
return (
|
|
108
|
+
<div className="bg-amber-900/20 border border-amber-500/50 rounded-xl p-8">
|
|
109
|
+
<div className="flex items-start gap-4">
|
|
110
|
+
<div className="text-amber-400 text-2xl">⚠️</div>
|
|
111
|
+
<div>
|
|
112
|
+
<h2 className="text-xl font-semibold text-amber-400 mb-2">
|
|
113
|
+
Cannot Connect to Strapi
|
|
114
|
+
</h2>
|
|
115
|
+
<p className="text-gray-300 mb-4">
|
|
116
|
+
Make sure your Strapi server is running at{' '}
|
|
117
|
+
<code className="text-cyan-400 bg-slate-800 px-2 py-1 rounded">
|
|
118
|
+
http://localhost:1337
|
|
119
|
+
</code>
|
|
120
|
+
</p>
|
|
121
|
+
{error && (
|
|
122
|
+
<p className="text-gray-500 text-sm mb-4">Error: {error}</p>
|
|
123
|
+
)}
|
|
124
|
+
<StrapiServerInstructions />
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function NoArticlesFound({ query }: { query?: string }) {
|
|
132
|
+
if (query) {
|
|
133
|
+
return (
|
|
134
|
+
<div className="bg-slate-800/50 border border-slate-700 rounded-xl p-8">
|
|
135
|
+
<div className="text-center">
|
|
136
|
+
<div className="text-6xl mb-4">🔍</div>
|
|
137
|
+
<h2 className="text-2xl font-semibold text-white mb-4">
|
|
138
|
+
No Results Found
|
|
139
|
+
</h2>
|
|
140
|
+
<p className="text-gray-400 mb-6 max-w-md mx-auto">
|
|
141
|
+
No articles match your search for "{query}". Try adjusting your
|
|
142
|
+
search terms.
|
|
143
|
+
</p>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<div className="bg-slate-800/50 border border-slate-700 rounded-xl p-8">
|
|
151
|
+
<div className="text-center">
|
|
152
|
+
<div className="text-6xl mb-4">📝</div>
|
|
153
|
+
<h2 className="text-2xl font-semibold text-white mb-4">
|
|
154
|
+
No Articles Yet
|
|
155
|
+
</h2>
|
|
156
|
+
<p className="text-gray-400 mb-6 max-w-md mx-auto">
|
|
157
|
+
Your Strapi server is running, but there are no published articles.
|
|
158
|
+
Create and publish your first article to see it here.
|
|
159
|
+
</p>
|
|
160
|
+
|
|
161
|
+
<div className="bg-slate-900/50 rounded-lg p-6 text-left max-w-md mx-auto">
|
|
162
|
+
<h3 className="text-lg font-semibold text-white mb-4">
|
|
163
|
+
How to add articles:
|
|
164
|
+
</h3>
|
|
165
|
+
<ol className="space-y-3 text-gray-400">
|
|
166
|
+
<li className="flex gap-3">
|
|
167
|
+
<span className="text-cyan-400 font-bold">1.</span>
|
|
168
|
+
<span>
|
|
169
|
+
Open{' '}
|
|
170
|
+
<a
|
|
171
|
+
href="http://localhost:1337/admin"
|
|
172
|
+
target="_blank"
|
|
173
|
+
rel="noopener noreferrer"
|
|
174
|
+
className="text-cyan-400 hover:underline"
|
|
175
|
+
>
|
|
176
|
+
Strapi Admin Panel
|
|
177
|
+
</a>
|
|
178
|
+
</span>
|
|
179
|
+
</li>
|
|
180
|
+
<li className="flex gap-3">
|
|
181
|
+
<span className="text-cyan-400 font-bold">2.</span>
|
|
182
|
+
<span>
|
|
183
|
+
Go to <strong className="text-white">Content Manager</strong> →{' '}
|
|
184
|
+
<strong className="text-white">Article</strong>
|
|
185
|
+
</span>
|
|
186
|
+
</li>
|
|
187
|
+
<li className="flex gap-3">
|
|
188
|
+
<span className="text-cyan-400 font-bold">3.</span>
|
|
189
|
+
<span>
|
|
190
|
+
Click <strong className="text-white">Create new entry</strong>
|
|
191
|
+
</span>
|
|
192
|
+
</li>
|
|
193
|
+
<li className="flex gap-3">
|
|
194
|
+
<span className="text-cyan-400 font-bold">4.</span>
|
|
195
|
+
<span>
|
|
196
|
+
Fill in the details and click{' '}
|
|
197
|
+
<strong className="text-white">Publish</strong>
|
|
198
|
+
</span>
|
|
199
|
+
</li>
|
|
200
|
+
</ol>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
12
207
|
function RouteComponent() {
|
|
13
|
-
const
|
|
208
|
+
const { status, articles, meta, error, query } = Route.useLoaderData()
|
|
14
209
|
|
|
15
210
|
return (
|
|
16
211
|
<div className="min-h-screen bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900 p-8">
|
|
17
212
|
<div className="max-w-7xl mx-auto">
|
|
18
|
-
<h1 className="text-4xl font-bold mb-
|
|
213
|
+
<h1 className="text-4xl font-bold mb-6 text-white">
|
|
19
214
|
<span className="bg-gradient-to-r from-cyan-400 to-blue-400 bg-clip-text text-transparent">
|
|
20
215
|
Strapi
|
|
21
216
|
</span>{' '}
|
|
22
217
|
<span className="text-gray-300">Articles</span>
|
|
23
218
|
</h1>
|
|
24
219
|
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
220
|
+
<div className="mb-8">
|
|
221
|
+
<Search />
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
{status === 'error' && <ConnectionError error={error} />}
|
|
225
|
+
|
|
226
|
+
{status === 'empty' && <NoArticlesFound query={query} />}
|
|
227
|
+
|
|
228
|
+
{status === 'success' && (
|
|
229
|
+
<>
|
|
230
|
+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
231
|
+
{articles.map((article: TArticle) => (
|
|
232
|
+
<Link
|
|
233
|
+
key={article.id}
|
|
234
|
+
to="/demo/strapi/$articleId"
|
|
235
|
+
params={{ articleId: article.documentId }}
|
|
236
|
+
className="block"
|
|
237
|
+
>
|
|
238
|
+
<article className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-xl overflow-hidden hover:border-cyan-500/50 transition-all duration-300 hover:shadow-lg hover:shadow-cyan-500/10 cursor-pointer h-full flex flex-col">
|
|
239
|
+
<StrapiImage
|
|
240
|
+
src={article.cover?.url}
|
|
241
|
+
alt={article.cover?.alternativeText || article.title}
|
|
242
|
+
className="w-full h-48"
|
|
243
|
+
/>
|
|
244
|
+
|
|
245
|
+
<div className="p-6 flex flex-col flex-1">
|
|
246
|
+
<h2 className="text-xl font-semibold text-white mb-3">
|
|
247
|
+
{article.title || 'Untitled'}
|
|
248
|
+
</h2>
|
|
249
|
+
|
|
250
|
+
{article.description && (
|
|
251
|
+
<p className="text-gray-400 mb-4 leading-relaxed line-clamp-2">
|
|
252
|
+
{article.description}
|
|
253
|
+
</p>
|
|
254
|
+
)}
|
|
255
|
+
|
|
256
|
+
<div className="mt-auto flex items-center justify-between">
|
|
257
|
+
{article.author?.name && (
|
|
258
|
+
<span className="text-sm text-gray-500">
|
|
259
|
+
By {article.author.name}
|
|
260
|
+
</span>
|
|
261
|
+
)}
|
|
262
|
+
{article.createdAt && (
|
|
263
|
+
<span className="text-sm text-cyan-400/70">
|
|
264
|
+
{new Date(article.createdAt).toLocaleDateString()}
|
|
265
|
+
</span>
|
|
266
|
+
)}
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
{article.category?.name && (
|
|
270
|
+
<div className="mt-3">
|
|
271
|
+
<span className="text-xs px-2 py-1 bg-slate-700 text-cyan-400 rounded-full">
|
|
272
|
+
{article.category.name}
|
|
273
|
+
</span>
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
</article>
|
|
278
|
+
</Link>
|
|
279
|
+
))}
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
{meta?.pagination && meta.pagination.pageCount > 1 && (
|
|
283
|
+
<div className="mt-8">
|
|
284
|
+
<Pagination pageCount={meta.pagination.pageCount} />
|
|
285
|
+
</div>
|
|
286
|
+
)}
|
|
287
|
+
</>
|
|
62
288
|
)}
|
|
63
289
|
</div>
|
|
64
290
|
</div>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strapi type definitions
|
|
3
|
+
* These types match the Strapi Cloud Template Blog schema
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Block } from "@/components/blocks";
|
|
7
|
+
|
|
8
|
+
// Base image type from Strapi media library
|
|
9
|
+
export type TImage = {
|
|
10
|
+
id: number;
|
|
11
|
+
documentId: string;
|
|
12
|
+
alternativeText: string | null;
|
|
13
|
+
url: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Author content type
|
|
17
|
+
export type TAuthor = {
|
|
18
|
+
id: number;
|
|
19
|
+
documentId: string;
|
|
20
|
+
name: string;
|
|
21
|
+
email?: string;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
updatedAt: string;
|
|
24
|
+
publishedAt: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Category content type
|
|
28
|
+
export type TCategory = {
|
|
29
|
+
id: number;
|
|
30
|
+
documentId: string;
|
|
31
|
+
name: string;
|
|
32
|
+
slug: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
createdAt: string;
|
|
35
|
+
updatedAt: string;
|
|
36
|
+
publishedAt: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Article content type
|
|
40
|
+
export type TArticle = {
|
|
41
|
+
id: number;
|
|
42
|
+
documentId: string;
|
|
43
|
+
title: string;
|
|
44
|
+
description: string;
|
|
45
|
+
slug: string;
|
|
46
|
+
cover?: TImage;
|
|
47
|
+
author?: TAuthor;
|
|
48
|
+
category?: TCategory;
|
|
49
|
+
blocks?: Array<Block>;
|
|
50
|
+
createdAt: string;
|
|
51
|
+
updatedAt: string;
|
|
52
|
+
publishedAt: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Strapi response wrappers
|
|
56
|
+
export type TStrapiResponseSingle<T> = {
|
|
57
|
+
data: T;
|
|
58
|
+
meta?: {
|
|
59
|
+
pagination?: TStrapiPagination;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type TStrapiResponseCollection<T> = {
|
|
64
|
+
data: Array<T>;
|
|
65
|
+
meta?: {
|
|
66
|
+
pagination?: TStrapiPagination;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type TStrapiPagination = {
|
|
71
|
+
page: number;
|
|
72
|
+
pageSize: number;
|
|
73
|
+
pageCount: number;
|
|
74
|
+
total: number;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type TStrapiError = {
|
|
78
|
+
status: number;
|
|
79
|
+
name: string;
|
|
80
|
+
message: string;
|
|
81
|
+
details?: Record<string, Array<string>>;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type TStrapiResponse<T = null> = {
|
|
85
|
+
data?: T;
|
|
86
|
+
error?: TStrapiError;
|
|
87
|
+
meta?: {
|
|
88
|
+
pagination?: TStrapiPagination;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
@@ -11,9 +11,9 @@ export function createFrameworkDefinition() {
|
|
|
11
11
|
]);
|
|
12
12
|
const { files, basePackageJSON, optionalPackages } = scanProjectDirectory(join(baseDirectory, 'project'), join(baseDirectory, 'project/base'));
|
|
13
13
|
return {
|
|
14
|
-
id: 'react
|
|
14
|
+
id: 'react',
|
|
15
15
|
name: 'React',
|
|
16
|
-
description: 'Templates for React
|
|
16
|
+
description: 'Templates for React',
|
|
17
17
|
version: '0.1.0',
|
|
18
18
|
base: files,
|
|
19
19
|
addOns,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Velvet Runtime Notes'
|
|
3
|
+
description: 'Use color tokens to keep light and dark themes aligned.'
|
|
4
|
+
pubDate: 'Aug 05 2024'
|
|
5
|
+
heroImage: '/images/lagoon-5.svg'
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Semantic tokens keep your UI stable while the brand evolves.
|
|
9
|
+
|
|
10
|
+
Instead of hard-coding one-off colors in components, shape a small set of
|
|
11
|
+
variables and map all surfaces to those variables.
|
|
12
|
+
|
|
13
|
+
## Why this matters
|
|
14
|
+
|
|
15
|
+
- You can restyle the app in minutes instead of days
|
|
16
|
+
- Light and dark themes stay behaviorally consistent
|
|
17
|
+
- Add-on pages inherit your visual identity by default
|
|
18
|
+
|
|
19
|
+
<MdxCallout title="MDX Component Demo">
|
|
20
|
+
This callout is rendered from JSX inside an MDX post. It is useful for
|
|
21
|
+
release notes, warnings, and migration tips where you want stronger visual
|
|
22
|
+
emphasis than plain markdown blocks.
|
|
23
|
+
</MdxCallout>
|
|
24
|
+
|
|
25
|
+
<MdxMetrics
|
|
26
|
+
items={[
|
|
27
|
+
{ label: 'Token count', value: '12 core vars' },
|
|
28
|
+
{ label: 'Theme modes', value: 'light + dark + auto' },
|
|
29
|
+
{ label: 'Restyle time', value: '< 30 min' },
|
|
30
|
+
]}
|
|
31
|
+
/>
|
|
32
|
+
|
|
33
|
+
### Token-to-component mapping
|
|
34
|
+
|
|
35
|
+
| Token | Typical usage |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `--surface` | Card backgrounds |
|
|
38
|
+
| `--line` | Borders and separators |
|
|
39
|
+
| `--lagoon-deep` | Links and active nav |
|
|
40
|
+
|
|
41
|
+
Keep the mapping documented and your team will make fewer ad-hoc styling calls.
|
|
42
|
+
|
|
43
|
+
### Example: deriving UI from semantic tokens
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
const button = {
|
|
47
|
+
color: 'var(--sea-ink)',
|
|
48
|
+
background: 'var(--surface)',
|
|
49
|
+
borderColor: 'var(--line)',
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
MDX is useful here because you can interleave narrative, tables, code blocks,
|
|
54
|
+
and custom JSX components in one authoring surface.
|