@fnd-platform/cms 1.0.0-alpha.3 → 1.0.0-alpha.4
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/lib/cms-project.d.ts +88 -86
- package/lib/cms-project.d.ts.map +1 -1
- package/lib/cms-project.js +22 -3
- package/lib/cms-project.js.map +1 -1
- package/lib/index.js +5 -10
- package/lib/templates/admin-content-edit-route.d.ts +9 -0
- package/lib/templates/admin-content-edit-route.d.ts.map +1 -0
- package/lib/templates/admin-content-edit-route.js +195 -0
- package/lib/templates/admin-content-edit-route.js.map +1 -0
- package/lib/templates/admin-content-new-route.d.ts +9 -0
- package/lib/templates/admin-content-new-route.d.ts.map +1 -0
- package/lib/templates/admin-content-new-route.js +160 -0
- package/lib/templates/admin-content-new-route.js.map +1 -0
- package/lib/templates/admin-content-type-route.d.ts +2 -2
- package/lib/templates/admin-content-type-route.d.ts.map +1 -1
- package/lib/templates/admin-content-type-route.js +67 -27
- package/lib/templates/admin-content-type-route.js.map +1 -1
- package/lib/templates/content-api.d.ts +9 -0
- package/lib/templates/content-api.d.ts.map +1 -0
- package/lib/templates/content-api.js +195 -0
- package/lib/templates/content-api.js.map +1 -0
- package/lib/templates/index.d.ts +4 -0
- package/lib/templates/index.d.ts.map +1 -1
- package/lib/templates/index.js +11 -1
- package/lib/templates/index.js.map +1 -1
- package/lib/templates/ui-select.d.ts +9 -0
- package/lib/templates/ui-select.d.ts.map +1 -0
- package/lib/templates/ui-select.js +172 -0
- package/lib/templates/ui-select.js.map +1 -0
- package/package.json +13 -13
- package/LICENSE +0 -21
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAdminContentNewRouteTemplate = getAdminContentNewRouteTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the admin content create route template.
|
|
6
|
+
*
|
|
7
|
+
* This route provides a form for creating new content items.
|
|
8
|
+
*
|
|
9
|
+
* @returns Template string for app/routes/_admin.content.$type.new.tsx
|
|
10
|
+
*/
|
|
11
|
+
function getAdminContentNewRouteTemplate() {
|
|
12
|
+
return `import {
|
|
13
|
+
json,
|
|
14
|
+
redirect,
|
|
15
|
+
type LoaderFunctionArgs,
|
|
16
|
+
type ActionFunctionArgs,
|
|
17
|
+
type MetaFunction,
|
|
18
|
+
} from '@remix-run/node';
|
|
19
|
+
import { useLoaderData } from '@remix-run/react';
|
|
20
|
+
import { requireAuth, hasAnyRole, getOptionalUser, getAccessToken } from '~/lib/auth.server';
|
|
21
|
+
import { ContentEditor, type ContentActionData } from '~/components/admin/content-editor';
|
|
22
|
+
import { Breadcrumbs } from '~/components/admin/breadcrumbs';
|
|
23
|
+
import { contentTypes } from '~/content-types';
|
|
24
|
+
import { contentApi, type CreateContentInput } from '~/lib/content-api';
|
|
25
|
+
|
|
26
|
+
export const meta: MetaFunction<typeof loader> = ({ data }) => {
|
|
27
|
+
const label = data?.contentType?.singularLabel ?? 'Content';
|
|
28
|
+
return [{ title: \`New \${label} - CMS Admin\` }];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export async function loader({ request, params }: LoaderFunctionArgs) {
|
|
32
|
+
await requireAuth(request);
|
|
33
|
+
const user = await getOptionalUser(request);
|
|
34
|
+
|
|
35
|
+
const { type } = params;
|
|
36
|
+
|
|
37
|
+
if (!type) {
|
|
38
|
+
throw new Response('Content type not specified', { status: 400 });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Get content type definition
|
|
42
|
+
const contentType = contentTypes[type];
|
|
43
|
+
if (!contentType) {
|
|
44
|
+
throw new Response(\`Content type '\${type}' not found\`, { status: 404 });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check permissions
|
|
48
|
+
const canEdit = hasAnyRole(user, ['admin', 'editor']);
|
|
49
|
+
if (!canEdit) {
|
|
50
|
+
throw new Response('You do not have permission to create content', { status: 403 });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const canPublish = hasAnyRole(user, ['admin', 'editor']);
|
|
54
|
+
const canDelete = hasAnyRole(user, ['admin']);
|
|
55
|
+
|
|
56
|
+
return json({
|
|
57
|
+
contentType,
|
|
58
|
+
permissions: { canPublish, canDelete },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function action({ request, params }: ActionFunctionArgs) {
|
|
63
|
+
await requireAuth(request);
|
|
64
|
+
const user = await getOptionalUser(request);
|
|
65
|
+
const token = await getAccessToken(request);
|
|
66
|
+
|
|
67
|
+
const { type } = params;
|
|
68
|
+
|
|
69
|
+
if (!type) {
|
|
70
|
+
return json<ContentActionData>(
|
|
71
|
+
{ success: false, message: 'Content type not specified' },
|
|
72
|
+
{ status: 400 }
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check permissions
|
|
77
|
+
const canEdit = hasAnyRole(user, ['admin', 'editor']);
|
|
78
|
+
if (!canEdit) {
|
|
79
|
+
return json<ContentActionData>(
|
|
80
|
+
{ success: false, message: 'You do not have permission to create content' },
|
|
81
|
+
{ status: 403 }
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const formData = await request.formData();
|
|
86
|
+
const action = formData.get('_action') as string;
|
|
87
|
+
const formDataJson = formData.get('_formData') as string;
|
|
88
|
+
|
|
89
|
+
if (!formDataJson) {
|
|
90
|
+
return json<ContentActionData>(
|
|
91
|
+
{ success: false, message: 'Form data missing' },
|
|
92
|
+
{ status: 400 }
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const data = JSON.parse(formDataJson) as Record<string, unknown>;
|
|
97
|
+
|
|
98
|
+
// Determine status based on action
|
|
99
|
+
let status: 'draft' | 'published' = 'draft';
|
|
100
|
+
if (action === 'publish') {
|
|
101
|
+
const canPublish = hasAnyRole(user, ['admin', 'editor']);
|
|
102
|
+
if (!canPublish) {
|
|
103
|
+
return json<ContentActionData>(
|
|
104
|
+
{ success: false, message: 'You do not have permission to publish' },
|
|
105
|
+
{ status: 403 }
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
status = 'published';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const input: CreateContentInput = {
|
|
113
|
+
slug: data.slug as string,
|
|
114
|
+
title: data.title as string,
|
|
115
|
+
excerpt: data.excerpt as string | undefined,
|
|
116
|
+
content: data.content as string,
|
|
117
|
+
contentType: type,
|
|
118
|
+
status,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const created = await contentApi.create(input, token!);
|
|
122
|
+
|
|
123
|
+
// Redirect to edit page on success
|
|
124
|
+
return redirect(\`/admin/content/\${type}/\${created.id}\`);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
return json<ContentActionData>(
|
|
127
|
+
{
|
|
128
|
+
success: false,
|
|
129
|
+
message: error instanceof Error ? error.message : 'Failed to create content',
|
|
130
|
+
},
|
|
131
|
+
{ status: 500 }
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export default function NewContent() {
|
|
137
|
+
const { contentType, permissions } = useLoaderData<typeof loader>();
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div className="space-y-6">
|
|
141
|
+
<Breadcrumbs
|
|
142
|
+
items={[
|
|
143
|
+
{ label: 'Content', href: '/admin/content' },
|
|
144
|
+
{ label: contentType.label, href: \`/admin/content/\${contentType.name}\` },
|
|
145
|
+
{ label: \`New \${contentType.singularLabel}\` },
|
|
146
|
+
]}
|
|
147
|
+
/>
|
|
148
|
+
|
|
149
|
+
<ContentEditor
|
|
150
|
+
contentType={contentType}
|
|
151
|
+
mode="create"
|
|
152
|
+
cancelUrl={\`/admin/content/\${contentType.name}\`}
|
|
153
|
+
permissions={permissions}
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
`;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=admin-content-new-route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-content-new-route.js","sourceRoot":"","sources":["../../src/templates/admin-content-new-route.ts"],"names":[],"mappings":";;AAOA,0EAoJC;AA3JD;;;;;;GAMG;AACH,SAAgB,+BAA+B;IAC7C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkJR,CAAC;AACF,CAAC"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This route displays the list of content items for a specific type.
|
|
5
5
|
*
|
|
6
|
-
* @returns Template string for app/routes/_admin.content.$type.tsx
|
|
6
|
+
* @returns Template string for app/routes/_admin.content.$type._index.tsx
|
|
7
7
|
*/
|
|
8
8
|
export declare function getAdminContentTypeRouteTemplate(): string;
|
|
9
|
-
//# sourceMappingURL=admin-content-type-route.d.ts.map
|
|
9
|
+
//# sourceMappingURL=admin-content-type-route.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-content-type-route.d.ts","sourceRoot":"","sources":["../../src/templates/admin-content-type-route.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,gCAAgC,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"admin-content-type-route.d.ts","sourceRoot":"","sources":["../../src/templates/admin-content-type-route.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,gCAAgC,IAAI,MAAM,CA4HzD"}
|
|
@@ -1,62 +1,89 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports,
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getAdminContentTypeRouteTemplate = getAdminContentTypeRouteTemplate;
|
|
4
4
|
/**
|
|
5
5
|
* Generates the admin content type list route template.
|
|
6
6
|
*
|
|
7
7
|
* This route displays the list of content items for a specific type.
|
|
8
8
|
*
|
|
9
|
-
* @returns Template string for app/routes/_admin.content.$type.tsx
|
|
9
|
+
* @returns Template string for app/routes/_admin.content.$type._index.tsx
|
|
10
10
|
*/
|
|
11
11
|
function getAdminContentTypeRouteTemplate() {
|
|
12
|
-
|
|
13
|
-
import { Link, useLoaderData, useParams } from '@remix-run/react';
|
|
12
|
+
return `import { json, type LoaderFunctionArgs, type MetaFunction } from '@remix-run/node';
|
|
13
|
+
import { Link, useLoaderData, useParams, useSearchParams } from '@remix-run/react';
|
|
14
14
|
import { Plus } from 'lucide-react';
|
|
15
|
-
import { requireAuth, hasAnyRole, getOptionalUser } from '~/lib/auth.server';
|
|
15
|
+
import { requireAuth, hasAnyRole, getOptionalUser, getAccessToken } from '~/lib/auth.server';
|
|
16
16
|
import { Button } from '~/components/ui/button';
|
|
17
|
-
import { ContentTable
|
|
17
|
+
import { ContentTable } from '~/components/admin/content-table';
|
|
18
18
|
import { Breadcrumbs } from '~/components/admin/breadcrumbs';
|
|
19
|
+
import { contentTypes } from '~/content-types';
|
|
20
|
+
import { contentApi } from '~/lib/content-api';
|
|
19
21
|
|
|
20
22
|
export const meta: MetaFunction<typeof loader> = ({ data }) => {
|
|
21
23
|
const label = data?.contentType?.label ?? 'Content';
|
|
22
24
|
return [{ title: \`\${label} - CMS Admin\` }];
|
|
23
25
|
};
|
|
24
26
|
|
|
25
|
-
// Mock content types - will be replaced with actual content types in Sprint 3
|
|
26
|
-
const contentTypeConfig: Record<string, { name: string; label: string }> = {
|
|
27
|
-
'blog-post': { name: 'blog-post', label: 'Blog Posts' },
|
|
28
|
-
'page': { name: 'page', label: 'Pages' },
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// Mock data - will be replaced with API calls in Sprint 3
|
|
32
|
-
const mockContent: Record<string, ContentItem[]> = {
|
|
33
|
-
'blog-post': [],
|
|
34
|
-
'page': [],
|
|
35
|
-
};
|
|
36
|
-
|
|
37
27
|
export async function loader({ request, params }: LoaderFunctionArgs) {
|
|
38
28
|
await requireAuth(request);
|
|
39
29
|
const user = await getOptionalUser(request);
|
|
30
|
+
const token = await getAccessToken(request);
|
|
40
31
|
|
|
41
32
|
const { type } = params;
|
|
42
33
|
|
|
43
|
-
if (!type
|
|
44
|
-
throw new Response('Content type not
|
|
34
|
+
if (!type) {
|
|
35
|
+
throw new Response('Content type not specified', { status: 400 });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Get content type definition
|
|
39
|
+
const contentType = contentTypes[type];
|
|
40
|
+
if (!contentType) {
|
|
41
|
+
throw new Response(\`Content type '\${type}' not found\`, { status: 404 });
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
const
|
|
44
|
+
// Parse query parameters for pagination and filtering
|
|
45
|
+
const url = new URL(request.url);
|
|
46
|
+
const cursor = url.searchParams.get('cursor') ?? undefined;
|
|
47
|
+
const status = url.searchParams.get('status') as 'draft' | 'published' | undefined;
|
|
48
|
+
|
|
49
|
+
// Fetch content from API
|
|
50
|
+
let response;
|
|
51
|
+
try {
|
|
52
|
+
response = await contentApi.list(
|
|
53
|
+
{
|
|
54
|
+
type,
|
|
55
|
+
limit: 20,
|
|
56
|
+
cursor,
|
|
57
|
+
status,
|
|
58
|
+
},
|
|
59
|
+
token
|
|
60
|
+
);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Failed to fetch content:', error);
|
|
63
|
+
response = { items: [], nextCursor: undefined };
|
|
64
|
+
}
|
|
49
65
|
|
|
50
66
|
// Check user permissions
|
|
51
67
|
const canEdit = hasAnyRole(user, ['admin', 'editor']);
|
|
52
68
|
const canDelete = hasAnyRole(user, ['admin']);
|
|
53
69
|
|
|
54
|
-
return json({
|
|
70
|
+
return json({
|
|
71
|
+
contentType: {
|
|
72
|
+
name: contentType.name,
|
|
73
|
+
label: contentType.label,
|
|
74
|
+
singularLabel: contentType.singularLabel,
|
|
75
|
+
},
|
|
76
|
+
items: response.items,
|
|
77
|
+
nextCursor: response.nextCursor,
|
|
78
|
+
canEdit,
|
|
79
|
+
canDelete,
|
|
80
|
+
});
|
|
55
81
|
}
|
|
56
82
|
|
|
57
83
|
export default function ContentTypeList() {
|
|
58
|
-
const { contentType, items, canEdit, canDelete } = useLoaderData<typeof loader>();
|
|
84
|
+
const { contentType, items, nextCursor, canEdit, canDelete } = useLoaderData<typeof loader>();
|
|
59
85
|
const params = useParams();
|
|
86
|
+
const [searchParams] = useSearchParams();
|
|
60
87
|
|
|
61
88
|
return (
|
|
62
89
|
<div className="space-y-6">
|
|
@@ -75,7 +102,7 @@ export default function ContentTypeList() {
|
|
|
75
102
|
<Button asChild>
|
|
76
103
|
<Link to={\`/admin/content/\${params.type}/new\`}>
|
|
77
104
|
<Plus className="mr-2 h-4 w-4" />
|
|
78
|
-
Create {contentType.
|
|
105
|
+
Create {contentType.singularLabel}
|
|
79
106
|
</Link>
|
|
80
107
|
</Button>
|
|
81
108
|
)}
|
|
@@ -88,9 +115,22 @@ export default function ContentTypeList() {
|
|
|
88
115
|
canEdit={canEdit}
|
|
89
116
|
canDelete={canDelete}
|
|
90
117
|
/>
|
|
118
|
+
|
|
119
|
+
{/* Pagination */}
|
|
120
|
+
{nextCursor && (
|
|
121
|
+
<div className="flex justify-center">
|
|
122
|
+
<Button variant="outline" asChild>
|
|
123
|
+
<Link
|
|
124
|
+
to={\`?cursor=\${nextCursor}\${searchParams.get('status') ? \`&status=\${searchParams.get('status')}\` : ''}\`}
|
|
125
|
+
>
|
|
126
|
+
Load more
|
|
127
|
+
</Link>
|
|
128
|
+
</Button>
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
91
131
|
</div>
|
|
92
132
|
);
|
|
93
133
|
}
|
|
94
134
|
`;
|
|
95
135
|
}
|
|
96
|
-
//# sourceMappingURL=admin-content-type-route.js.map
|
|
136
|
+
//# sourceMappingURL=admin-content-type-route.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-content-type-route.js","sourceRoot":"","sources":["../../src/templates/admin-content-type-route.ts"],"names":[],"mappings":";;AAOA,
|
|
1
|
+
{"version":3,"file":"admin-content-type-route.js","sourceRoot":"","sources":["../../src/templates/admin-content-type-route.ts"],"names":[],"mappings":";;AAOA,4EA4HC;AAnID;;;;;;GAMG;AACH,SAAgB,gCAAgC;IAC9C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0HR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the CMS content API client template.
|
|
3
|
+
*
|
|
4
|
+
* This provides functions for making API calls to the content endpoints.
|
|
5
|
+
*
|
|
6
|
+
* @returns Template string for app/lib/content-api.ts
|
|
7
|
+
*/
|
|
8
|
+
export declare function getContentApiTemplate(): string;
|
|
9
|
+
//# sourceMappingURL=content-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-api.d.ts","sourceRoot":"","sources":["../../src/templates/content-api.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAuL9C"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getContentApiTemplate = getContentApiTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the CMS content API client template.
|
|
6
|
+
*
|
|
7
|
+
* This provides functions for making API calls to the content endpoints.
|
|
8
|
+
*
|
|
9
|
+
* @returns Template string for app/lib/content-api.ts
|
|
10
|
+
*/
|
|
11
|
+
function getContentApiTemplate() {
|
|
12
|
+
return `/**
|
|
13
|
+
* Content API client for CMS operations.
|
|
14
|
+
*
|
|
15
|
+
* Provides type-safe functions for content CRUD operations.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const API_URL = process.env.API_URL ?? 'http://localhost:3000';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Content item from the API.
|
|
22
|
+
*/
|
|
23
|
+
export interface ContentItem {
|
|
24
|
+
id: string;
|
|
25
|
+
slug: string;
|
|
26
|
+
title: string;
|
|
27
|
+
excerpt?: string;
|
|
28
|
+
content: string;
|
|
29
|
+
contentType: string;
|
|
30
|
+
status: 'draft' | 'published';
|
|
31
|
+
authorId: string;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
updatedAt: string;
|
|
34
|
+
publishedAt?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Paginated response from list endpoints.
|
|
39
|
+
*/
|
|
40
|
+
export interface PaginatedResponse<T> {
|
|
41
|
+
items: T[];
|
|
42
|
+
nextCursor?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Options for listing content.
|
|
47
|
+
*/
|
|
48
|
+
export interface ListContentOptions {
|
|
49
|
+
limit?: number;
|
|
50
|
+
cursor?: string;
|
|
51
|
+
type?: string;
|
|
52
|
+
status?: 'draft' | 'published';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Input for creating content.
|
|
57
|
+
*/
|
|
58
|
+
export interface CreateContentInput {
|
|
59
|
+
slug: string;
|
|
60
|
+
title: string;
|
|
61
|
+
excerpt?: string;
|
|
62
|
+
content: string;
|
|
63
|
+
contentType: string;
|
|
64
|
+
status?: 'draft' | 'published';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Input for updating content.
|
|
69
|
+
*/
|
|
70
|
+
export interface UpdateContentInput {
|
|
71
|
+
slug?: string;
|
|
72
|
+
title?: string;
|
|
73
|
+
excerpt?: string;
|
|
74
|
+
content?: string;
|
|
75
|
+
status?: 'draft' | 'published';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* API response wrapper.
|
|
80
|
+
*/
|
|
81
|
+
interface ApiResponse<T> {
|
|
82
|
+
success: boolean;
|
|
83
|
+
data?: T;
|
|
84
|
+
error?: {
|
|
85
|
+
code: string;
|
|
86
|
+
message: string;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Make an authenticated API request.
|
|
92
|
+
*/
|
|
93
|
+
async function fetchApi<T>(
|
|
94
|
+
path: string,
|
|
95
|
+
options: RequestInit & { token?: string } = {}
|
|
96
|
+
): Promise<T> {
|
|
97
|
+
const { token, ...fetchOptions } = options;
|
|
98
|
+
|
|
99
|
+
const headers: Record<string, string> = {
|
|
100
|
+
'Content-Type': 'application/json',
|
|
101
|
+
...(fetchOptions.headers as Record<string, string>),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
if (token) {
|
|
105
|
+
headers['Authorization'] = \`Bearer \${token}\`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const response = await fetch(\`\${API_URL}\${path}\`, {
|
|
109
|
+
...fetchOptions,
|
|
110
|
+
headers,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const data: ApiResponse<T> = await response.json();
|
|
114
|
+
|
|
115
|
+
if (!response.ok || !data.success) {
|
|
116
|
+
throw new Error(data.error?.message ?? \`API error: \${response.status}\`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return data.data as T;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Content API methods.
|
|
124
|
+
*/
|
|
125
|
+
export const contentApi = {
|
|
126
|
+
/**
|
|
127
|
+
* List content items with optional filtering.
|
|
128
|
+
*/
|
|
129
|
+
async list(
|
|
130
|
+
options: ListContentOptions = {},
|
|
131
|
+
token?: string
|
|
132
|
+
): Promise<PaginatedResponse<ContentItem>> {
|
|
133
|
+
const params = new URLSearchParams();
|
|
134
|
+
if (options.limit) params.set('limit', String(options.limit));
|
|
135
|
+
if (options.cursor) params.set('cursor', options.cursor);
|
|
136
|
+
if (options.type) params.set('type', options.type);
|
|
137
|
+
if (options.status) params.set('status', options.status);
|
|
138
|
+
|
|
139
|
+
const query = params.toString();
|
|
140
|
+
return fetchApi(\`/content\${query ? \`?\${query}\` : ''}\`, { token });
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get content by ID.
|
|
145
|
+
*/
|
|
146
|
+
async get(id: string, token?: string): Promise<ContentItem> {
|
|
147
|
+
return fetchApi(\`/content/\${id}\`, { token });
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get content by slug.
|
|
152
|
+
*/
|
|
153
|
+
async getBySlug(slug: string, token?: string): Promise<ContentItem> {
|
|
154
|
+
return fetchApi(\`/content/slug/\${slug}\`, { token });
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Create new content.
|
|
159
|
+
*/
|
|
160
|
+
async create(input: CreateContentInput, token: string): Promise<ContentItem> {
|
|
161
|
+
return fetchApi('/content', {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
body: JSON.stringify(input),
|
|
164
|
+
token,
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Update existing content.
|
|
170
|
+
*/
|
|
171
|
+
async update(
|
|
172
|
+
id: string,
|
|
173
|
+
input: UpdateContentInput,
|
|
174
|
+
token: string
|
|
175
|
+
): Promise<ContentItem> {
|
|
176
|
+
return fetchApi(\`/content/\${id}\`, {
|
|
177
|
+
method: 'PUT',
|
|
178
|
+
body: JSON.stringify(input),
|
|
179
|
+
token,
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Delete content.
|
|
185
|
+
*/
|
|
186
|
+
async delete(id: string, token: string): Promise<{ deleted: boolean; id: string }> {
|
|
187
|
+
return fetchApi(\`/content/\${id}\`, {
|
|
188
|
+
method: 'DELETE',
|
|
189
|
+
token,
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=content-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-api.js","sourceRoot":"","sources":["../../src/templates/content-api.ts"],"names":[],"mappings":";;AAOA,sDAuLC;AA9LD;;;;;;GAMG;AACH,SAAgB,qBAAqB;IACnC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqLR,CAAC;AACF,CAAC"}
|
package/lib/templates/index.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ export { getAdminLayoutTemplate } from './admin-layout';
|
|
|
8
8
|
export { getAdminIndexTemplate } from './admin-index';
|
|
9
9
|
export { getAdminContentRouteTemplate, getAdminContentIndexTemplate } from './admin-content-route';
|
|
10
10
|
export { getAdminContentTypeRouteTemplate } from './admin-content-type-route';
|
|
11
|
+
export { getAdminContentNewRouteTemplate } from './admin-content-new-route';
|
|
12
|
+
export { getAdminContentEditRouteTemplate } from './admin-content-edit-route';
|
|
13
|
+
export { getContentApiTemplate } from './content-api';
|
|
11
14
|
export { getAdminSidebarTemplate } from './admin-sidebar';
|
|
12
15
|
export { getAdminHeaderTemplate } from './admin-header';
|
|
13
16
|
export { getAdminBreadcrumbsTemplate } from './admin-breadcrumbs';
|
|
@@ -19,6 +22,7 @@ export { getAvatarTemplate } from './ui-avatar';
|
|
|
19
22
|
export { getDropdownMenuTemplate } from './ui-dropdown-menu';
|
|
20
23
|
export { getTableTemplate } from './ui-table';
|
|
21
24
|
export { getPopoverTemplate } from './ui-popover';
|
|
25
|
+
export { getSelectTemplate } from './ui-select';
|
|
22
26
|
export { getEditorToolbarTemplate, getRichTextEditorTemplate } from './editor';
|
|
23
27
|
export { getContentTypesTemplate } from './content-types';
|
|
24
28
|
export { getContentSchemaTemplate } from './content-schema';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,4BAA4B,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AACnG,OAAO,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC;AAG9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAG1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,4BAA4B,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AACnG,OAAO,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,+BAA+B,EAAE,MAAM,2BAA2B,CAAC;AAC5E,OAAO,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC;AAG9E,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAG1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAG/E,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,+BAA+B,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EACL,8BAA8B,EAC9B,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,2BAA2B,EAC3B,oBAAoB,EACpB,wBAAwB,EACxB,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,EACpB,yBAAyB,GAC1B,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,4BAA4B,GAC7B,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC"}
|
package/lib/templates/index.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* in the user's CMS package when FndCmsProject is synthesized.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.
|
|
9
|
+
exports.getDialogTemplate = exports.getProgressTemplate = exports.getMediaLibraryRouteTemplate = exports.getMediaApiTemplate = exports.getMediaPickerTemplate = exports.getMediaGridTemplate = exports.getMediaUploaderTemplate = exports.getReferenceFieldTemplate = exports.getTagsFieldTemplate = exports.getNumberFieldTemplate = exports.getBooleanFieldTemplate = exports.getDatetimeFieldTemplate = exports.getDateFieldTemplate = exports.getMultiselectFieldTemplate = exports.getSelectFieldTemplate = exports.getMediaFieldTemplate = exports.getSlugFieldTemplate = exports.getRichtextFieldTemplate = exports.getTextareaFieldTemplate = exports.getTextFieldTemplate = exports.getFormFieldsIndexTemplate = exports.getPageContentTypeTemplate = exports.getBlogPostContentTypeTemplate = exports.getContentEditorTemplate = exports.getContentTypesRegistryTemplate = exports.getSlugUtilsTemplate = exports.getContentSchemaTemplate = exports.getContentTypesTemplate = exports.getRichTextEditorTemplate = exports.getEditorToolbarTemplate = exports.getSelectTemplate = exports.getPopoverTemplate = exports.getTableTemplate = exports.getDropdownMenuTemplate = exports.getAvatarTemplate = exports.getBadgeTemplate = exports.getContentTableTemplate = exports.getRecentContentTemplate = exports.getDashboardStatsTemplate = exports.getAdminBreadcrumbsTemplate = exports.getAdminHeaderTemplate = exports.getAdminSidebarTemplate = exports.getContentApiTemplate = exports.getAdminContentEditRouteTemplate = exports.getAdminContentNewRouteTemplate = exports.getAdminContentTypeRouteTemplate = exports.getAdminContentIndexTemplate = exports.getAdminContentRouteTemplate = exports.getAdminIndexTemplate = exports.getAdminLayoutTemplate = void 0;
|
|
10
|
+
exports.getTabsTemplate = void 0;
|
|
10
11
|
// Route templates
|
|
11
12
|
var admin_layout_1 = require("./admin-layout");
|
|
12
13
|
Object.defineProperty(exports, "getAdminLayoutTemplate", { enumerable: true, get: function () { return admin_layout_1.getAdminLayoutTemplate; } });
|
|
@@ -17,6 +18,13 @@ Object.defineProperty(exports, "getAdminContentRouteTemplate", { enumerable: tru
|
|
|
17
18
|
Object.defineProperty(exports, "getAdminContentIndexTemplate", { enumerable: true, get: function () { return admin_content_route_1.getAdminContentIndexTemplate; } });
|
|
18
19
|
var admin_content_type_route_1 = require("./admin-content-type-route");
|
|
19
20
|
Object.defineProperty(exports, "getAdminContentTypeRouteTemplate", { enumerable: true, get: function () { return admin_content_type_route_1.getAdminContentTypeRouteTemplate; } });
|
|
21
|
+
var admin_content_new_route_1 = require("./admin-content-new-route");
|
|
22
|
+
Object.defineProperty(exports, "getAdminContentNewRouteTemplate", { enumerable: true, get: function () { return admin_content_new_route_1.getAdminContentNewRouteTemplate; } });
|
|
23
|
+
var admin_content_edit_route_1 = require("./admin-content-edit-route");
|
|
24
|
+
Object.defineProperty(exports, "getAdminContentEditRouteTemplate", { enumerable: true, get: function () { return admin_content_edit_route_1.getAdminContentEditRouteTemplate; } });
|
|
25
|
+
// Content API client
|
|
26
|
+
var content_api_1 = require("./content-api");
|
|
27
|
+
Object.defineProperty(exports, "getContentApiTemplate", { enumerable: true, get: function () { return content_api_1.getContentApiTemplate; } });
|
|
20
28
|
// Admin component templates
|
|
21
29
|
var admin_sidebar_1 = require("./admin-sidebar");
|
|
22
30
|
Object.defineProperty(exports, "getAdminSidebarTemplate", { enumerable: true, get: function () { return admin_sidebar_1.getAdminSidebarTemplate; } });
|
|
@@ -41,6 +49,8 @@ var ui_table_1 = require("./ui-table");
|
|
|
41
49
|
Object.defineProperty(exports, "getTableTemplate", { enumerable: true, get: function () { return ui_table_1.getTableTemplate; } });
|
|
42
50
|
var ui_popover_1 = require("./ui-popover");
|
|
43
51
|
Object.defineProperty(exports, "getPopoverTemplate", { enumerable: true, get: function () { return ui_popover_1.getPopoverTemplate; } });
|
|
52
|
+
var ui_select_1 = require("./ui-select");
|
|
53
|
+
Object.defineProperty(exports, "getSelectTemplate", { enumerable: true, get: function () { return ui_select_1.getSelectTemplate; } });
|
|
44
54
|
// Editor component templates
|
|
45
55
|
var editor_1 = require("./editor");
|
|
46
56
|
Object.defineProperty(exports, "getEditorToolbarTemplate", { enumerable: true, get: function () { return editor_1.getEditorToolbarTemplate; } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;;AAEH,kBAAkB;AAClB,+CAAwD;AAA/C,sHAAA,sBAAsB,OAAA;AAC/B,6CAAsD;AAA7C,oHAAA,qBAAqB,OAAA;AAC9B,6DAAmG;AAA1F,mIAAA,4BAA4B,OAAA;AAAE,mIAAA,4BAA4B,OAAA;AACnE,uEAA8E;AAArE,4IAAA,gCAAgC,OAAA;AACzC,qEAA4E;AAAnE,0IAAA,+BAA+B,OAAA;AACxC,uEAA8E;AAArE,4IAAA,gCAAgC,OAAA;AAEzC,qBAAqB;AACrB,6CAAsD;AAA7C,oHAAA,qBAAqB,OAAA;AAE9B,4BAA4B;AAC5B,iDAA0D;AAAjD,wHAAA,uBAAuB,OAAA;AAChC,+CAAwD;AAA/C,sHAAA,sBAAsB,OAAA;AAC/B,yDAAkE;AAAzD,gIAAA,2BAA2B,OAAA;AACpC,qDAA8D;AAArD,4HAAA,yBAAyB,OAAA;AAClC,mDAA4D;AAAnD,0HAAA,wBAAwB,OAAA;AACjC,iDAA0D;AAAjD,wHAAA,uBAAuB,OAAA;AAEhC,yBAAyB;AACzB,uCAA8C;AAArC,4GAAA,gBAAgB,OAAA;AACzB,yCAAgD;AAAvC,8GAAA,iBAAiB,OAAA;AAC1B,uDAA6D;AAApD,2HAAA,uBAAuB,OAAA;AAChC,uCAA8C;AAArC,4GAAA,gBAAgB,OAAA;AACzB,2CAAkD;AAAzC,gHAAA,kBAAkB,OAAA;AAC3B,yCAAgD;AAAvC,8GAAA,iBAAiB,OAAA;AAE1B,6BAA6B;AAC7B,mCAA+E;AAAtE,kHAAA,wBAAwB,OAAA;AAAE,mHAAA,yBAAyB,OAAA;AAE5D,gCAAgC;AAChC,iDAA0D;AAAjD,wHAAA,uBAAuB,OAAA;AAChC,mDAA4D;AAAnD,0HAAA,wBAAwB,OAAA;AACjC,2CAAoD;AAA3C,kHAAA,oBAAoB,OAAA;AAC7B,mEAA2E;AAAlE,yIAAA,+BAA+B,OAAA;AACxC,mDAA4D;AAAnD,0HAAA,wBAAwB,OAAA;AACjC,mEAGkC;AAFhC,wIAAA,8BAA8B,OAAA;AAC9B,oIAAA,0BAA0B,OAAA;AAG5B,iCAAiC;AACjC,6CAeuB;AAdrB,yHAAA,0BAA0B,OAAA;AAC1B,mHAAA,oBAAoB,OAAA;AACpB,uHAAA,wBAAwB,OAAA;AACxB,uHAAA,wBAAwB,OAAA;AACxB,mHAAA,oBAAoB,OAAA;AACpB,oHAAA,qBAAqB,OAAA;AACrB,qHAAA,sBAAsB,OAAA;AACtB,0HAAA,2BAA2B,OAAA;AAC3B,mHAAA,oBAAoB,OAAA;AACpB,uHAAA,wBAAwB,OAAA;AACxB,sHAAA,uBAAuB,OAAA;AACvB,qHAAA,sBAAsB,OAAA;AACtB,mHAAA,oBAAoB,OAAA;AACpB,wHAAA,yBAAyB,OAAA;AAG3B,0BAA0B;AAC1B,iCAMiB;AALf,iHAAA,wBAAwB,OAAA;AACxB,6GAAA,oBAAoB,OAAA;AACpB,+GAAA,sBAAsB,OAAA;AACtB,4GAAA,mBAAmB,OAAA;AACnB,qHAAA,4BAA4B,OAAA;AAG9B,oCAAoC;AACpC,6CAAoD;AAA3C,kHAAA,mBAAmB,OAAA;AAC5B,yCAAgD;AAAvC,8GAAA,iBAAiB,OAAA;AAC1B,qCAA4C;AAAnC,0GAAA,eAAe,OAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the Select UI component template.
|
|
3
|
+
*
|
|
4
|
+
* This component provides a styled select dropdown using Radix UI primitives.
|
|
5
|
+
*
|
|
6
|
+
* @returns Template string for app/components/ui/select.tsx
|
|
7
|
+
*/
|
|
8
|
+
export declare function getSelectTemplate(): string;
|
|
9
|
+
//# sourceMappingURL=ui-select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-select.d.ts","sourceRoot":"","sources":["../../src/templates/ui-select.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAgK1C"}
|