@tanstack/create 0.61.5 → 0.61.6

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/frameworks/react/add-ons/strapi/README.md +158 -8
  3. package/dist/frameworks/react/add-ons/strapi/assets/_dot_env.local.append +1 -1
  4. package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/block-renderer.tsx +55 -0
  5. package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/index.ts +14 -0
  6. package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/media.tsx +27 -0
  7. package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/quote.tsx +19 -0
  8. package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/rich-text.tsx +11 -0
  9. package/dist/frameworks/react/add-ons/strapi/assets/src/components/blocks/slider.tsx +28 -0
  10. package/dist/frameworks/react/add-ons/strapi/assets/src/components/markdown-content.tsx +74 -0
  11. package/dist/frameworks/react/add-ons/strapi/assets/src/components/pagination.tsx +120 -0
  12. package/dist/frameworks/react/add-ons/strapi/assets/src/components/search.tsx +35 -0
  13. package/dist/frameworks/react/add-ons/strapi/assets/src/components/strapi-image.tsx +47 -0
  14. package/dist/frameworks/react/add-ons/strapi/assets/src/data/loaders/articles.ts +106 -0
  15. package/dist/frameworks/react/add-ons/strapi/assets/src/data/loaders/index.ts +28 -0
  16. package/dist/frameworks/react/add-ons/strapi/assets/src/data/strapi-sdk.ts +9 -0
  17. package/dist/frameworks/react/add-ons/strapi/assets/src/lib/strapi-utils.ts +25 -0
  18. package/dist/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi.$articleId.tsx +170 -0
  19. package/dist/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi.tsx +269 -43
  20. package/dist/frameworks/react/add-ons/strapi/assets/src/types/strapi.ts +90 -0
  21. package/dist/frameworks/react/add-ons/strapi/info.json +3 -3
  22. package/dist/frameworks/react/add-ons/strapi/package.json +5 -2
  23. package/package.json +1 -1
  24. package/src/frameworks/react/add-ons/strapi/README.md +158 -8
  25. package/src/frameworks/react/add-ons/strapi/assets/_dot_env.local.append +1 -1
  26. package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/block-renderer.tsx +55 -0
  27. package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/index.ts +14 -0
  28. package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/media.tsx +27 -0
  29. package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/quote.tsx +19 -0
  30. package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/rich-text.tsx +11 -0
  31. package/src/frameworks/react/add-ons/strapi/assets/src/components/blocks/slider.tsx +28 -0
  32. package/src/frameworks/react/add-ons/strapi/assets/src/components/markdown-content.tsx +74 -0
  33. package/src/frameworks/react/add-ons/strapi/assets/src/components/pagination.tsx +120 -0
  34. package/src/frameworks/react/add-ons/strapi/assets/src/components/search.tsx +35 -0
  35. package/src/frameworks/react/add-ons/strapi/assets/src/components/strapi-image.tsx +47 -0
  36. package/src/frameworks/react/add-ons/strapi/assets/src/data/loaders/articles.ts +106 -0
  37. package/src/frameworks/react/add-ons/strapi/assets/src/data/loaders/index.ts +28 -0
  38. package/src/frameworks/react/add-ons/strapi/assets/src/data/strapi-sdk.ts +9 -0
  39. package/src/frameworks/react/add-ons/strapi/assets/src/lib/strapi-utils.ts +25 -0
  40. package/src/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi.$articleId.tsx +170 -0
  41. package/src/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi.tsx +269 -43
  42. package/src/frameworks/react/add-ons/strapi/assets/src/types/strapi.ts +90 -0
  43. package/src/frameworks/react/add-ons/strapi/info.json +3 -3
  44. package/src/frameworks/react/add-ons/strapi/package.json +5 -2
  45. package/dist/frameworks/react/add-ons/strapi/assets/src/lib/strapiClient.ts +0 -7
  46. package/dist/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi_.$articleId.tsx +0 -78
  47. package/src/frameworks/react/add-ons/strapi/assets/src/lib/strapiClient.ts +0 -7
  48. package/src/frameworks/react/add-ons/strapi/assets/src/routes/demo/strapi_.$articleId.tsx +0 -78
@@ -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
- loader: async () => {
7
- const { data: strapiArticles } = await articles.find()
8
- return strapiArticles
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 strapiArticles = Route.useLoaderData()
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-8 text-white">
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
- {strapiArticles && strapiArticles.length > 0 ? (
26
- <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
27
- {strapiArticles.map((article) => (
28
- <Link
29
- key={article.id}
30
- to="/demo/strapi/$articleId"
31
- params={{ articleId: article.documentId }}
32
- className="block"
33
- >
34
- <article className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-xl p-6 hover:border-cyan-500/50 transition-all duration-300 hover:shadow-lg hover:shadow-cyan-500/10 cursor-pointer h-full">
35
- <h2 className="text-xl font-semibold text-white mb-3">
36
- {article.title || 'Untitled'}
37
- </h2>
38
-
39
- {article.description && (
40
- <p className="text-gray-400 mb-4 leading-relaxed">
41
- {article.description}
42
- </p>
43
- )}
44
-
45
- {article.content && (
46
- <p className="text-gray-400 line-clamp-3 leading-relaxed">
47
- {article.content}
48
- </p>
49
- )}
50
-
51
- {article.createdAt && (
52
- <p className="text-sm text-cyan-400/70 mt-4">
53
- {new Date(article.createdAt).toLocaleDateString()}
54
- </p>
55
- )}
56
- </article>
57
- </Link>
58
- ))}
59
- </div>
60
- ) : (
61
- <p className="text-gray-400">No articles found.</p>
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 @@
11
11
  "routes": [
12
12
  {
13
13
  "url": "/demo/strapi",
14
- "name": "Strapi",
15
- "path": "src/routes/demo.strapi.tsx",
16
- "jsName": "StrapiDemo"
14
+ "name": "Strapi Articles",
15
+ "path": "src/routes/demo/strapi.tsx",
16
+ "jsName": "StrapiArticles"
17
17
  }
18
18
  ]
19
19
  }
@@ -1,5 +1,8 @@
1
1
  {
2
2
  "dependencies": {
3
- "@strapi/client": "^1.5.0"
3
+ "@strapi/client": "^1.6.0",
4
+ "react-markdown": "^9.0.1",
5
+ "remark-gfm": "^4.0.0",
6
+ "use-debounce": "^10.0.0"
4
7
  }
5
- }
8
+ }
@@ -1,7 +0,0 @@
1
- import { strapi } from "@strapi/client";
2
-
3
- export const strapiClient = strapi({
4
- baseURL: import.meta.env.VITE_STRAPI_URL,
5
- });
6
-
7
- export const articles = strapiClient.collection("articles");
@@ -1,78 +0,0 @@
1
- import { articles } from '#/lib/strapiClient'
2
- import { createFileRoute, Link } from '@tanstack/react-router'
3
-
4
- export const Route = createFileRoute('/demo/strapi_/$articleId')({
5
- component: RouteComponent,
6
- loader: async ({ params }) => {
7
- const { data: article } = await articles.findOne(params.articleId)
8
- return article
9
- },
10
- })
11
-
12
- function RouteComponent() {
13
- const article = Route.useLoaderData()
14
-
15
- return (
16
- <div className="min-h-screen bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900 p-8">
17
- <div className="max-w-4xl mx-auto">
18
- <Link
19
- to="/demo/strapi"
20
- className="inline-flex items-center text-cyan-400 hover:text-cyan-300 mb-6 transition-colors"
21
- >
22
- <svg
23
- xmlns="http://www.w3.org/2000/svg"
24
- className="h-5 w-5 mr-2"
25
- viewBox="0 0 20 20"
26
- fill="currentColor"
27
- >
28
- <path
29
- fillRule="evenodd"
30
- 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"
31
- clipRule="evenodd"
32
- />
33
- </svg>
34
- Back to Articles
35
- </Link>
36
-
37
- <article className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-xl p-8">
38
- <h1 className="text-4xl font-bold text-white mb-4">
39
- {article?.title || 'Untitled'}
40
- </h1>
41
-
42
- {article?.createdAt && (
43
- <p className="text-sm text-cyan-400/70 mb-6">
44
- Published on{' '}
45
- {new Date(article?.createdAt).toLocaleDateString('en-US', {
46
- year: 'numeric',
47
- month: 'long',
48
- day: 'numeric',
49
- })}
50
- </p>
51
- )}
52
-
53
- {article?.description && (
54
- <div className="mb-6">
55
- <h2 className="text-xl font-semibold text-gray-300 mb-3">
56
- Description
57
- </h2>
58
- <p className="text-gray-400 leading-relaxed">
59
- {article?.description}
60
- </p>
61
- </div>
62
- )}
63
-
64
- {article?.content && (
65
- <div>
66
- <h2 className="text-xl font-semibold text-gray-300 mb-3">
67
- Content
68
- </h2>
69
- <div className="text-gray-400 leading-relaxed whitespace-pre-wrap">
70
- {article?.content}
71
- </div>
72
- </div>
73
- )}
74
- </article>
75
- </div>
76
- </div>
77
- )
78
- }
@@ -1,7 +0,0 @@
1
- import { strapi } from "@strapi/client";
2
-
3
- export const strapiClient = strapi({
4
- baseURL: import.meta.env.VITE_STRAPI_URL,
5
- });
6
-
7
- export const articles = strapiClient.collection("articles");
@@ -1,78 +0,0 @@
1
- import { articles } from '#/lib/strapiClient'
2
- import { createFileRoute, Link } from '@tanstack/react-router'
3
-
4
- export const Route = createFileRoute('/demo/strapi_/$articleId')({
5
- component: RouteComponent,
6
- loader: async ({ params }) => {
7
- const { data: article } = await articles.findOne(params.articleId)
8
- return article
9
- },
10
- })
11
-
12
- function RouteComponent() {
13
- const article = Route.useLoaderData()
14
-
15
- return (
16
- <div className="min-h-screen bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900 p-8">
17
- <div className="max-w-4xl mx-auto">
18
- <Link
19
- to="/demo/strapi"
20
- className="inline-flex items-center text-cyan-400 hover:text-cyan-300 mb-6 transition-colors"
21
- >
22
- <svg
23
- xmlns="http://www.w3.org/2000/svg"
24
- className="h-5 w-5 mr-2"
25
- viewBox="0 0 20 20"
26
- fill="currentColor"
27
- >
28
- <path
29
- fillRule="evenodd"
30
- 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"
31
- clipRule="evenodd"
32
- />
33
- </svg>
34
- Back to Articles
35
- </Link>
36
-
37
- <article className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-xl p-8">
38
- <h1 className="text-4xl font-bold text-white mb-4">
39
- {article?.title || 'Untitled'}
40
- </h1>
41
-
42
- {article?.createdAt && (
43
- <p className="text-sm text-cyan-400/70 mb-6">
44
- Published on{' '}
45
- {new Date(article?.createdAt).toLocaleDateString('en-US', {
46
- year: 'numeric',
47
- month: 'long',
48
- day: 'numeric',
49
- })}
50
- </p>
51
- )}
52
-
53
- {article?.description && (
54
- <div className="mb-6">
55
- <h2 className="text-xl font-semibold text-gray-300 mb-3">
56
- Description
57
- </h2>
58
- <p className="text-gray-400 leading-relaxed">
59
- {article?.description}
60
- </p>
61
- </div>
62
- )}
63
-
64
- {article?.content && (
65
- <div>
66
- <h2 className="text-xl font-semibold text-gray-300 mb-3">
67
- Content
68
- </h2>
69
- <div className="text-gray-400 leading-relaxed whitespace-pre-wrap">
70
- {article?.content}
71
- </div>
72
- </div>
73
- )}
74
- </article>
75
- </div>
76
- </div>
77
- )
78
- }