@fnd-platform/cms 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/lib/cms-project.d.ts +127 -0
- package/lib/cms-project.d.ts.map +1 -0
- package/lib/cms-project.js +343 -0
- package/lib/cms-project.js.map +1 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +20 -0
- package/lib/index.js.map +1 -0
- package/lib/options.d.ts +59 -0
- package/lib/options.d.ts.map +1 -0
- package/lib/options.js +3 -0
- package/lib/options.js.map +1 -0
- package/lib/templates/admin-breadcrumbs.d.ts +13 -0
- package/lib/templates/admin-breadcrumbs.d.ts.map +1 -0
- package/lib/templates/admin-breadcrumbs.js +80 -0
- package/lib/templates/admin-breadcrumbs.js.map +1 -0
- package/lib/templates/admin-content-route.d.ts +18 -0
- package/lib/templates/admin-content-route.d.ts.map +1 -0
- package/lib/templates/admin-content-route.js +100 -0
- package/lib/templates/admin-content-route.js.map +1 -0
- package/lib/templates/admin-content-type-route.d.ts +9 -0
- package/lib/templates/admin-content-type-route.d.ts.map +1 -0
- package/lib/templates/admin-content-type-route.js +96 -0
- package/lib/templates/admin-content-type-route.js.map +1 -0
- package/lib/templates/admin-header.d.ts +13 -0
- package/lib/templates/admin-header.d.ts.map +1 -0
- package/lib/templates/admin-header.js +123 -0
- package/lib/templates/admin-header.js.map +1 -0
- package/lib/templates/admin-index.d.ts +9 -0
- package/lib/templates/admin-index.d.ts.map +1 -0
- package/lib/templates/admin-index.js +60 -0
- package/lib/templates/admin-index.js.map +1 -0
- package/lib/templates/admin-layout.d.ts +10 -0
- package/lib/templates/admin-layout.d.ts.map +1 -0
- package/lib/templates/admin-layout.js +46 -0
- package/lib/templates/admin-layout.js.map +1 -0
- package/lib/templates/admin-sidebar.d.ts +13 -0
- package/lib/templates/admin-sidebar.d.ts.map +1 -0
- package/lib/templates/admin-sidebar.js +149 -0
- package/lib/templates/admin-sidebar.js.map +1 -0
- package/lib/templates/content-editor.d.ts +10 -0
- package/lib/templates/content-editor.d.ts.map +1 -0
- package/lib/templates/content-editor.js +354 -0
- package/lib/templates/content-editor.js.map +1 -0
- package/lib/templates/content-schema.d.ts +10 -0
- package/lib/templates/content-schema.d.ts.map +1 -0
- package/lib/templates/content-schema.js +274 -0
- package/lib/templates/content-schema.js.map +1 -0
- package/lib/templates/content-table.d.ts +13 -0
- package/lib/templates/content-table.d.ts.map +1 -0
- package/lib/templates/content-table.js +177 -0
- package/lib/templates/content-table.js.map +1 -0
- package/lib/templates/content-types-examples.d.ts +19 -0
- package/lib/templates/content-types-examples.d.ts.map +1 -0
- package/lib/templates/content-types-examples.js +275 -0
- package/lib/templates/content-types-examples.js.map +1 -0
- package/lib/templates/content-types-registry.d.ts +10 -0
- package/lib/templates/content-types-registry.d.ts.map +1 -0
- package/lib/templates/content-types-registry.js +87 -0
- package/lib/templates/content-types-registry.js.map +1 -0
- package/lib/templates/content-types.d.ts +10 -0
- package/lib/templates/content-types.d.ts.map +1 -0
- package/lib/templates/content-types.js +384 -0
- package/lib/templates/content-types.js.map +1 -0
- package/lib/templates/dashboard-stats.d.ts +13 -0
- package/lib/templates/dashboard-stats.d.ts.map +1 -0
- package/lib/templates/dashboard-stats.js +117 -0
- package/lib/templates/dashboard-stats.js.map +1 -0
- package/lib/templates/editor/index.d.ts +6 -0
- package/lib/templates/editor/index.d.ts.map +1 -0
- package/lib/templates/editor/index.js +21 -0
- package/lib/templates/editor/index.js.map +1 -0
- package/lib/templates/editor/rich-text-editor.d.ts +7 -0
- package/lib/templates/editor/rich-text-editor.d.ts.map +1 -0
- package/lib/templates/editor/rich-text-editor.js +115 -0
- package/lib/templates/editor/rich-text-editor.js.map +1 -0
- package/lib/templates/editor/toolbar.d.ts +7 -0
- package/lib/templates/editor/toolbar.d.ts.map +1 -0
- package/lib/templates/editor/toolbar.js +272 -0
- package/lib/templates/editor/toolbar.js.map +1 -0
- package/lib/templates/form-fields/boolean-field.d.ts +7 -0
- package/lib/templates/form-fields/boolean-field.d.ts.map +1 -0
- package/lib/templates/form-fields/boolean-field.js +76 -0
- package/lib/templates/form-fields/boolean-field.js.map +1 -0
- package/lib/templates/form-fields/date-field.d.ts +7 -0
- package/lib/templates/form-fields/date-field.d.ts.map +1 -0
- package/lib/templates/form-fields/date-field.js +61 -0
- package/lib/templates/form-fields/date-field.js.map +1 -0
- package/lib/templates/form-fields/datetime-field.d.ts +7 -0
- package/lib/templates/form-fields/datetime-field.d.ts.map +1 -0
- package/lib/templates/form-fields/datetime-field.js +87 -0
- package/lib/templates/form-fields/datetime-field.js.map +1 -0
- package/lib/templates/form-fields/index.d.ts +23 -0
- package/lib/templates/form-fields/index.d.ts.map +1 -0
- package/lib/templates/form-fields/index.js +275 -0
- package/lib/templates/form-fields/index.js.map +1 -0
- package/lib/templates/form-fields/media-field.d.ts +10 -0
- package/lib/templates/form-fields/media-field.d.ts.map +1 -0
- package/lib/templates/form-fields/media-field.js +225 -0
- package/lib/templates/form-fields/media-field.js.map +1 -0
- package/lib/templates/form-fields/multiselect-field.d.ts +7 -0
- package/lib/templates/form-fields/multiselect-field.d.ts.map +1 -0
- package/lib/templates/form-fields/multiselect-field.js +121 -0
- package/lib/templates/form-fields/multiselect-field.js.map +1 -0
- package/lib/templates/form-fields/number-field.d.ts +7 -0
- package/lib/templates/form-fields/number-field.d.ts.map +1 -0
- package/lib/templates/form-fields/number-field.js +87 -0
- package/lib/templates/form-fields/number-field.js.map +1 -0
- package/lib/templates/form-fields/reference-field.d.ts +9 -0
- package/lib/templates/form-fields/reference-field.d.ts.map +1 -0
- package/lib/templates/form-fields/reference-field.js +145 -0
- package/lib/templates/form-fields/reference-field.js.map +1 -0
- package/lib/templates/form-fields/richtext-field.d.ts +9 -0
- package/lib/templates/form-fields/richtext-field.d.ts.map +1 -0
- package/lib/templates/form-fields/richtext-field.js +60 -0
- package/lib/templates/form-fields/richtext-field.js.map +1 -0
- package/lib/templates/form-fields/select-field.d.ts +7 -0
- package/lib/templates/form-fields/select-field.d.ts.map +1 -0
- package/lib/templates/form-fields/select-field.js +70 -0
- package/lib/templates/form-fields/select-field.js.map +1 -0
- package/lib/templates/form-fields/slug-field.d.ts +7 -0
- package/lib/templates/form-fields/slug-field.d.ts.map +1 -0
- package/lib/templates/form-fields/slug-field.js +143 -0
- package/lib/templates/form-fields/slug-field.js.map +1 -0
- package/lib/templates/form-fields/tags-field.d.ts +7 -0
- package/lib/templates/form-fields/tags-field.d.ts.map +1 -0
- package/lib/templates/form-fields/tags-field.js +172 -0
- package/lib/templates/form-fields/tags-field.js.map +1 -0
- package/lib/templates/form-fields/text-field.d.ts +7 -0
- package/lib/templates/form-fields/text-field.d.ts.map +1 -0
- package/lib/templates/form-fields/text-field.js +63 -0
- package/lib/templates/form-fields/text-field.js.map +1 -0
- package/lib/templates/form-fields/textarea-field.d.ts +7 -0
- package/lib/templates/form-fields/textarea-field.d.ts.map +1 -0
- package/lib/templates/form-fields/textarea-field.js +64 -0
- package/lib/templates/form-fields/textarea-field.js.map +1 -0
- package/lib/templates/index.d.ts +34 -0
- package/lib/templates/index.d.ts.map +1 -0
- package/lib/templates/index.js +92 -0
- package/lib/templates/index.js.map +1 -0
- package/lib/templates/media/index.d.ts +12 -0
- package/lib/templates/media/index.d.ts.map +1 -0
- package/lib/templates/media/index.js +50 -0
- package/lib/templates/media/index.js.map +1 -0
- package/lib/templates/media/media-api.d.ts +13 -0
- package/lib/templates/media/media-api.d.ts.map +1 -0
- package/lib/templates/media/media-api.js +274 -0
- package/lib/templates/media/media-api.js.map +1 -0
- package/lib/templates/media/media-grid.d.ts +14 -0
- package/lib/templates/media/media-grid.d.ts.map +1 -0
- package/lib/templates/media/media-grid.js +314 -0
- package/lib/templates/media/media-grid.js.map +1 -0
- package/lib/templates/media/media-library-route.d.ts +13 -0
- package/lib/templates/media/media-library-route.d.ts.map +1 -0
- package/lib/templates/media/media-library-route.js +105 -0
- package/lib/templates/media/media-library-route.js.map +1 -0
- package/lib/templates/media/media-picker.d.ts +13 -0
- package/lib/templates/media/media-picker.d.ts.map +1 -0
- package/lib/templates/media/media-picker.js +152 -0
- package/lib/templates/media/media-picker.js.map +1 -0
- package/lib/templates/media/media-uploader.d.ts +14 -0
- package/lib/templates/media/media-uploader.d.ts.map +1 -0
- package/lib/templates/media/media-uploader.js +318 -0
- package/lib/templates/media/media-uploader.js.map +1 -0
- package/lib/templates/recent-content.d.ts +13 -0
- package/lib/templates/recent-content.d.ts.map +1 -0
- package/lib/templates/recent-content.js +138 -0
- package/lib/templates/recent-content.js.map +1 -0
- package/lib/templates/slug-utils.d.ts +10 -0
- package/lib/templates/slug-utils.d.ts.map +1 -0
- package/lib/templates/slug-utils.js +194 -0
- package/lib/templates/slug-utils.js.map +1 -0
- package/lib/templates/ui-avatar.d.ts +8 -0
- package/lib/templates/ui-avatar.d.ts.map +1 -0
- package/lib/templates/ui-avatar.js +60 -0
- package/lib/templates/ui-avatar.js.map +1 -0
- package/lib/templates/ui-badge.d.ts +8 -0
- package/lib/templates/ui-badge.d.ts.map +1 -0
- package/lib/templates/ui-badge.js +52 -0
- package/lib/templates/ui-badge.js.map +1 -0
- package/lib/templates/ui-dialog.d.ts +10 -0
- package/lib/templates/ui-dialog.d.ts.map +1 -0
- package/lib/templates/ui-dialog.js +134 -0
- package/lib/templates/ui-dialog.js.map +1 -0
- package/lib/templates/ui-dropdown-menu.d.ts +8 -0
- package/lib/templates/ui-dropdown-menu.d.ts.map +1 -0
- package/lib/templates/ui-dropdown-menu.js +210 -0
- package/lib/templates/ui-dropdown-menu.js.map +1 -0
- package/lib/templates/ui-popover.d.ts +8 -0
- package/lib/templates/ui-popover.d.ts.map +1 -0
- package/lib/templates/ui-popover.js +43 -0
- package/lib/templates/ui-popover.js.map +1 -0
- package/lib/templates/ui-progress.d.ts +10 -0
- package/lib/templates/ui-progress.d.ts.map +1 -0
- package/lib/templates/ui-progress.js +40 -0
- package/lib/templates/ui-progress.js.map +1 -0
- package/lib/templates/ui-table.d.ts +8 -0
- package/lib/templates/ui-table.d.ts.map +1 -0
- package/lib/templates/ui-table.js +129 -0
- package/lib/templates/ui-table.js.map +1 -0
- package/lib/templates/ui-tabs.d.ts +10 -0
- package/lib/templates/ui-tabs.d.ts.map +1 -0
- package/lib/templates/ui-tabs.js +67 -0
- package/lib/templates/ui-tabs.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getAdminContentRouteTemplate = getAdminContentRouteTemplate;
|
|
4
|
+
exports.getAdminContentIndexTemplate = getAdminContentIndexTemplate;
|
|
5
|
+
/**
|
|
6
|
+
* Generates the admin content index route template.
|
|
7
|
+
*
|
|
8
|
+
* This route serves as the content section layout/outlet
|
|
9
|
+
* that wraps all content-related routes.
|
|
10
|
+
*
|
|
11
|
+
* @returns Template string for app/routes/_admin.content.tsx
|
|
12
|
+
*/
|
|
13
|
+
function getAdminContentRouteTemplate() {
|
|
14
|
+
return `import { json, type LoaderFunctionArgs } from '@remix-run/node';
|
|
15
|
+
import { Outlet, useLoaderData } from '@remix-run/react';
|
|
16
|
+
import { requireAuth } from '~/lib/auth.server';
|
|
17
|
+
|
|
18
|
+
export async function loader({ request }: LoaderFunctionArgs) {
|
|
19
|
+
await requireAuth(request);
|
|
20
|
+
return json({});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function ContentLayout() {
|
|
24
|
+
return <Outlet />;
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Generates the admin content index page template.
|
|
30
|
+
*
|
|
31
|
+
* This shows an overview of all content when visiting /admin/content.
|
|
32
|
+
*
|
|
33
|
+
* @returns Template string for app/routes/_admin.content._index.tsx
|
|
34
|
+
*/
|
|
35
|
+
function getAdminContentIndexTemplate() {
|
|
36
|
+
return `import { json, type LoaderFunctionArgs, type MetaFunction } from '@remix-run/node';
|
|
37
|
+
import { Link, useLoaderData } from '@remix-run/react';
|
|
38
|
+
import { FileText, Plus } from 'lucide-react';
|
|
39
|
+
import { requireAuth } from '~/lib/auth.server';
|
|
40
|
+
import { Card, CardContent, CardHeader, CardTitle } from '~/components/ui/card';
|
|
41
|
+
import { Button } from '~/components/ui/button';
|
|
42
|
+
|
|
43
|
+
export const meta: MetaFunction = () => {
|
|
44
|
+
return [{ title: 'Content - CMS Admin' }];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Mock content types - will be replaced with actual content types in Sprint 3
|
|
48
|
+
const contentTypes = [
|
|
49
|
+
{ name: 'blog-post', label: 'Blog Posts', count: 0 },
|
|
50
|
+
{ name: 'page', label: 'Pages', count: 0 },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
export async function loader({ request }: LoaderFunctionArgs) {
|
|
54
|
+
await requireAuth(request);
|
|
55
|
+
|
|
56
|
+
// TODO: Fetch actual content type counts from API
|
|
57
|
+
return json({ contentTypes });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default function ContentIndex() {
|
|
61
|
+
const { contentTypes } = useLoaderData<typeof loader>();
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div className="space-y-6">
|
|
65
|
+
<div className="flex items-center justify-between">
|
|
66
|
+
<h1 className="text-2xl font-bold">Content</h1>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
70
|
+
{contentTypes.map((type) => (
|
|
71
|
+
<Card key={type.name} className="hover:shadow-md transition-shadow">
|
|
72
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
73
|
+
<CardTitle className="text-sm font-medium">
|
|
74
|
+
{type.label}
|
|
75
|
+
</CardTitle>
|
|
76
|
+
<FileText className="h-4 w-4 text-muted-foreground" />
|
|
77
|
+
</CardHeader>
|
|
78
|
+
<CardContent>
|
|
79
|
+
<div className="text-2xl font-bold">{type.count}</div>
|
|
80
|
+
<div className="mt-4 flex gap-2">
|
|
81
|
+
<Button asChild variant="outline" size="sm">
|
|
82
|
+
<Link to={\`/admin/content/\${type.name}\`}>View All</Link>
|
|
83
|
+
</Button>
|
|
84
|
+
<Button asChild size="sm">
|
|
85
|
+
<Link to={\`/admin/content/\${type.name}/new\`}>
|
|
86
|
+
<Plus className="mr-1 h-3 w-3" />
|
|
87
|
+
New
|
|
88
|
+
</Link>
|
|
89
|
+
</Button>
|
|
90
|
+
</div>
|
|
91
|
+
</CardContent>
|
|
92
|
+
</Card>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=admin-content-route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-content-route.js","sourceRoot":"","sources":["../../src/templates/admin-content-route.ts"],"names":[],"mappings":";;AAQA,oEAcC;AASD,oEAgEC;AA/FD;;;;;;;GAOG;AACH,SAAgB,4BAA4B;IAC1C,OAAO;;;;;;;;;;;;CAYR,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,4BAA4B;IAC1C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8DR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the admin content type list route template.
|
|
3
|
+
*
|
|
4
|
+
* This route displays the list of content items for a specific type.
|
|
5
|
+
*
|
|
6
|
+
* @returns Template string for app/routes/_admin.content.$type.tsx
|
|
7
|
+
*/
|
|
8
|
+
export declare function getAdminContentTypeRouteTemplate(): string;
|
|
9
|
+
//# sourceMappingURL=admin-content-type-route.d.ts.map
|
|
@@ -0,0 +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,CAoFzD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getAdminContentTypeRouteTemplate = getAdminContentTypeRouteTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the admin content type list route template.
|
|
6
|
+
*
|
|
7
|
+
* This route displays the list of content items for a specific type.
|
|
8
|
+
*
|
|
9
|
+
* @returns Template string for app/routes/_admin.content.$type.tsx
|
|
10
|
+
*/
|
|
11
|
+
function getAdminContentTypeRouteTemplate() {
|
|
12
|
+
return `import { json, type LoaderFunctionArgs, type MetaFunction } from '@remix-run/node';
|
|
13
|
+
import { Link, useLoaderData, useParams } from '@remix-run/react';
|
|
14
|
+
import { Plus } from 'lucide-react';
|
|
15
|
+
import { requireAuth, hasAnyRole, getOptionalUser } from '~/lib/auth.server';
|
|
16
|
+
import { Button } from '~/components/ui/button';
|
|
17
|
+
import { ContentTable, type ContentItem } from '~/components/admin/content-table';
|
|
18
|
+
import { Breadcrumbs } from '~/components/admin/breadcrumbs';
|
|
19
|
+
|
|
20
|
+
export const meta: MetaFunction<typeof loader> = ({ data }) => {
|
|
21
|
+
const label = data?.contentType?.label ?? 'Content';
|
|
22
|
+
return [{ title: \`\${label} - CMS Admin\` }];
|
|
23
|
+
};
|
|
24
|
+
|
|
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
|
+
export async function loader({ request, params }: LoaderFunctionArgs) {
|
|
38
|
+
await requireAuth(request);
|
|
39
|
+
const user = await getOptionalUser(request);
|
|
40
|
+
|
|
41
|
+
const { type } = params;
|
|
42
|
+
|
|
43
|
+
if (!type || !contentTypeConfig[type]) {
|
|
44
|
+
throw new Response('Content type not found', { status: 404 });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const contentType = contentTypeConfig[type];
|
|
48
|
+
const items = mockContent[type] ?? [];
|
|
49
|
+
|
|
50
|
+
// Check user permissions
|
|
51
|
+
const canEdit = hasAnyRole(user, ['admin', 'editor']);
|
|
52
|
+
const canDelete = hasAnyRole(user, ['admin']);
|
|
53
|
+
|
|
54
|
+
return json({ contentType, items, canEdit, canDelete });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default function ContentTypeList() {
|
|
58
|
+
const { contentType, items, canEdit, canDelete } = useLoaderData<typeof loader>();
|
|
59
|
+
const params = useParams();
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="space-y-6">
|
|
63
|
+
{/* Breadcrumbs */}
|
|
64
|
+
<Breadcrumbs
|
|
65
|
+
items={[
|
|
66
|
+
{ label: 'Content', href: '/admin/content' },
|
|
67
|
+
{ label: contentType.label },
|
|
68
|
+
]}
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
{/* Header */}
|
|
72
|
+
<div className="flex items-center justify-between">
|
|
73
|
+
<h1 className="text-2xl font-bold">{contentType.label}</h1>
|
|
74
|
+
{canEdit && (
|
|
75
|
+
<Button asChild>
|
|
76
|
+
<Link to={\`/admin/content/\${params.type}/new\`}>
|
|
77
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
78
|
+
Create {contentType.label.replace(/s$/, '')}
|
|
79
|
+
</Link>
|
|
80
|
+
</Button>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{/* Content Table */}
|
|
85
|
+
<ContentTable
|
|
86
|
+
contentType={contentType}
|
|
87
|
+
items={items}
|
|
88
|
+
canEdit={canEdit}
|
|
89
|
+
canDelete={canDelete}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=admin-content-type-route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-content-type-route.js","sourceRoot":"","sources":["../../src/templates/admin-content-type-route.ts"],"names":[],"mappings":";;AAOA,4EAoFC;AA3FD;;;;;;GAMG;AACH,SAAgB,gCAAgC;IAC9C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkFR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the admin header component template.
|
|
3
|
+
*
|
|
4
|
+
* This header provides:
|
|
5
|
+
* - Breadcrumb navigation slot
|
|
6
|
+
* - User menu with avatar and dropdown
|
|
7
|
+
* - Role badge display
|
|
8
|
+
* - Sign out functionality
|
|
9
|
+
*
|
|
10
|
+
* @returns Template string for app/components/admin/header.tsx
|
|
11
|
+
*/
|
|
12
|
+
export declare function getAdminHeaderTemplate(): string;
|
|
13
|
+
//# sourceMappingURL=admin-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-header.d.ts","sourceRoot":"","sources":["../../src/templates/admin-header.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CA2G/C"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getAdminHeaderTemplate = getAdminHeaderTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the admin header component template.
|
|
6
|
+
*
|
|
7
|
+
* This header provides:
|
|
8
|
+
* - Breadcrumb navigation slot
|
|
9
|
+
* - User menu with avatar and dropdown
|
|
10
|
+
* - Role badge display
|
|
11
|
+
* - Sign out functionality
|
|
12
|
+
*
|
|
13
|
+
* @returns Template string for app/components/admin/header.tsx
|
|
14
|
+
*/
|
|
15
|
+
function getAdminHeaderTemplate() {
|
|
16
|
+
return `import { Form } from '@remix-run/react';
|
|
17
|
+
import { LogOut, User } from 'lucide-react';
|
|
18
|
+
import { Button } from '~/components/ui/button';
|
|
19
|
+
import {
|
|
20
|
+
DropdownMenu,
|
|
21
|
+
DropdownMenuContent,
|
|
22
|
+
DropdownMenuItem,
|
|
23
|
+
DropdownMenuLabel,
|
|
24
|
+
DropdownMenuSeparator,
|
|
25
|
+
DropdownMenuTrigger,
|
|
26
|
+
} from '~/components/ui/dropdown-menu';
|
|
27
|
+
import { Avatar, AvatarFallback } from '~/components/ui/avatar';
|
|
28
|
+
import { Badge } from '~/components/ui/badge';
|
|
29
|
+
|
|
30
|
+
interface AdminHeaderProps {
|
|
31
|
+
user: {
|
|
32
|
+
email: string;
|
|
33
|
+
groups?: string[];
|
|
34
|
+
} | null;
|
|
35
|
+
breadcrumbs?: React.ReactNode;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getInitials(email: string): string {
|
|
39
|
+
const parts = email.split('@')[0].split(/[._-]/);
|
|
40
|
+
if (parts.length >= 2) {
|
|
41
|
+
return (parts[0][0] + parts[1][0]).toUpperCase();
|
|
42
|
+
}
|
|
43
|
+
return email.substring(0, 2).toUpperCase();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getUserRole(groups?: string[]): 'admin' | 'editor' | 'viewer' {
|
|
47
|
+
if (!groups) return 'viewer';
|
|
48
|
+
if (groups.includes('admin')) return 'admin';
|
|
49
|
+
if (groups.includes('editor')) return 'editor';
|
|
50
|
+
return 'viewer';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getRoleBadgeVariant(role: string): 'default' | 'secondary' | 'outline' {
|
|
54
|
+
switch (role) {
|
|
55
|
+
case 'admin':
|
|
56
|
+
return 'default';
|
|
57
|
+
case 'editor':
|
|
58
|
+
return 'secondary';
|
|
59
|
+
default:
|
|
60
|
+
return 'outline';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function AdminHeader({ user, breadcrumbs }: AdminHeaderProps) {
|
|
65
|
+
const role = getUserRole(user?.groups);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<header className="flex h-16 items-center justify-between border-b bg-background px-6">
|
|
69
|
+
{/* Breadcrumbs */}
|
|
70
|
+
<div className="flex items-center">
|
|
71
|
+
{breadcrumbs}
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{/* User Menu */}
|
|
75
|
+
<div className="flex items-center gap-4">
|
|
76
|
+
<Badge variant={getRoleBadgeVariant(role)} className="capitalize">
|
|
77
|
+
{role}
|
|
78
|
+
</Badge>
|
|
79
|
+
|
|
80
|
+
<DropdownMenu>
|
|
81
|
+
<DropdownMenuTrigger asChild>
|
|
82
|
+
<Button variant="ghost" className="relative h-10 w-10 rounded-full">
|
|
83
|
+
<Avatar>
|
|
84
|
+
<AvatarFallback>
|
|
85
|
+
{user ? getInitials(user.email) : 'U'}
|
|
86
|
+
</AvatarFallback>
|
|
87
|
+
</Avatar>
|
|
88
|
+
</Button>
|
|
89
|
+
</DropdownMenuTrigger>
|
|
90
|
+
<DropdownMenuContent className="w-56" align="end" forceMount>
|
|
91
|
+
<DropdownMenuLabel className="font-normal">
|
|
92
|
+
<div className="flex flex-col space-y-1">
|
|
93
|
+
<p className="text-sm font-medium leading-none">Account</p>
|
|
94
|
+
<p className="text-xs leading-none text-muted-foreground">
|
|
95
|
+
{user?.email ?? 'Not signed in'}
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
</DropdownMenuLabel>
|
|
99
|
+
<DropdownMenuSeparator />
|
|
100
|
+
<DropdownMenuItem asChild>
|
|
101
|
+
<a href="/admin/profile" className="flex items-center">
|
|
102
|
+
<User className="mr-2 h-4 w-4" />
|
|
103
|
+
Profile
|
|
104
|
+
</a>
|
|
105
|
+
</DropdownMenuItem>
|
|
106
|
+
<DropdownMenuSeparator />
|
|
107
|
+
<Form action="/logout" method="post">
|
|
108
|
+
<DropdownMenuItem asChild>
|
|
109
|
+
<button type="submit" className="flex w-full items-center">
|
|
110
|
+
<LogOut className="mr-2 h-4 w-4" />
|
|
111
|
+
Sign Out
|
|
112
|
+
</button>
|
|
113
|
+
</DropdownMenuItem>
|
|
114
|
+
</Form>
|
|
115
|
+
</DropdownMenuContent>
|
|
116
|
+
</DropdownMenu>
|
|
117
|
+
</div>
|
|
118
|
+
</header>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=admin-header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-header.js","sourceRoot":"","sources":["../../src/templates/admin-header.ts"],"names":[],"mappings":";;AAWA,wDA2GC;AAtHD;;;;;;;;;;GAUG;AACH,SAAgB,sBAAsB;IACpC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyGR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the admin dashboard index route template.
|
|
3
|
+
*
|
|
4
|
+
* This dashboard displays content statistics and recent content.
|
|
5
|
+
*
|
|
6
|
+
* @returns Template string for _admin._index.tsx
|
|
7
|
+
*/
|
|
8
|
+
export declare function getAdminIndexTemplate(): string;
|
|
9
|
+
//# sourceMappingURL=admin-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-index.d.ts","sourceRoot":"","sources":["../../src/templates/admin-index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAgD9C"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getAdminIndexTemplate = getAdminIndexTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the admin dashboard index route template.
|
|
6
|
+
*
|
|
7
|
+
* This dashboard displays content statistics and recent content.
|
|
8
|
+
*
|
|
9
|
+
* @returns Template string for _admin._index.tsx
|
|
10
|
+
*/
|
|
11
|
+
function getAdminIndexTemplate() {
|
|
12
|
+
return `import { json, type LoaderFunctionArgs, type MetaFunction } from '@remix-run/node';
|
|
13
|
+
import { useLoaderData } from '@remix-run/react';
|
|
14
|
+
import { requireAuth } from '~/lib/auth.server';
|
|
15
|
+
import { DashboardStats, type ContentStats } from '~/components/admin/dashboard-stats';
|
|
16
|
+
import { RecentContent, type ContentItem } from '~/components/admin/recent-content';
|
|
17
|
+
|
|
18
|
+
export const meta: MetaFunction = () => {
|
|
19
|
+
return [{ title: 'Dashboard - CMS Admin' }];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export async function loader({ request }: LoaderFunctionArgs) {
|
|
23
|
+
await requireAuth(request);
|
|
24
|
+
|
|
25
|
+
// TODO: Replace with actual API calls in Sprint 3
|
|
26
|
+
const stats: ContentStats = {
|
|
27
|
+
total: 0,
|
|
28
|
+
published: 0,
|
|
29
|
+
draft: 0,
|
|
30
|
+
byType: [
|
|
31
|
+
{ type: 'blog-post', label: 'Blog Posts', count: 0 },
|
|
32
|
+
{ type: 'page', label: 'Pages', count: 0 },
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const recentContent: ContentItem[] = [];
|
|
37
|
+
|
|
38
|
+
return json({ stats, recentContent });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default function AdminDashboard() {
|
|
42
|
+
const { stats, recentContent } = useLoaderData<typeof loader>();
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="space-y-6">
|
|
46
|
+
<div>
|
|
47
|
+
<h1 className="text-2xl font-bold">Dashboard</h1>
|
|
48
|
+
<p className="text-muted-foreground">
|
|
49
|
+
Overview of your content management system.
|
|
50
|
+
</p>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<DashboardStats stats={stats} />
|
|
54
|
+
<RecentContent items={recentContent} />
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=admin-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-index.js","sourceRoot":"","sources":["../../src/templates/admin-index.ts"],"names":[],"mappings":";;AAOA,sDAgDC;AAvDD;;;;;;GAMG;AACH,SAAgB,qBAAqB;IACnC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8CR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the admin layout route template.
|
|
3
|
+
*
|
|
4
|
+
* This layout wraps all admin routes and enforces authentication.
|
|
5
|
+
* Includes sidebar navigation and header with user menu.
|
|
6
|
+
*
|
|
7
|
+
* @returns Template string for _admin.tsx
|
|
8
|
+
*/
|
|
9
|
+
export declare function getAdminLayoutTemplate(): string;
|
|
10
|
+
//# sourceMappingURL=admin-layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-layout.d.ts","sourceRoot":"","sources":["../../src/templates/admin-layout.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAiC/C"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getAdminLayoutTemplate = getAdminLayoutTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the admin layout route template.
|
|
6
|
+
*
|
|
7
|
+
* This layout wraps all admin routes and enforces authentication.
|
|
8
|
+
* Includes sidebar navigation and header with user menu.
|
|
9
|
+
*
|
|
10
|
+
* @returns Template string for _admin.tsx
|
|
11
|
+
*/
|
|
12
|
+
function getAdminLayoutTemplate() {
|
|
13
|
+
return `import { json, type LoaderFunctionArgs } from '@remix-run/node';
|
|
14
|
+
import { Outlet, useLoaderData } from '@remix-run/react';
|
|
15
|
+
import { requireAuth, getOptionalUser, hasRole, hasAnyRole } from '~/lib/auth.server';
|
|
16
|
+
import { AdminSidebar } from '~/components/admin/sidebar';
|
|
17
|
+
import { AdminHeader } from '~/components/admin/header';
|
|
18
|
+
|
|
19
|
+
export async function loader({ request }: LoaderFunctionArgs) {
|
|
20
|
+
await requireAuth(request);
|
|
21
|
+
const user = await getOptionalUser(request);
|
|
22
|
+
|
|
23
|
+
const isAdmin = hasRole(user, 'admin');
|
|
24
|
+
const isEditor = hasAnyRole(user, ['admin', 'editor']);
|
|
25
|
+
|
|
26
|
+
return json({ user, isAdmin, isEditor });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function AdminLayout() {
|
|
30
|
+
const { user, isAdmin, isEditor } = useLoaderData<typeof loader>();
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="flex h-screen bg-muted/30">
|
|
34
|
+
<AdminSidebar isAdmin={isAdmin} />
|
|
35
|
+
<div className="flex flex-1 flex-col overflow-hidden">
|
|
36
|
+
<AdminHeader user={user} />
|
|
37
|
+
<main className="flex-1 overflow-y-auto p-6">
|
|
38
|
+
<Outlet context={{ user, isAdmin, isEditor }} />
|
|
39
|
+
</main>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=admin-layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-layout.js","sourceRoot":"","sources":["../../src/templates/admin-layout.ts"],"names":[],"mappings":";;AAQA,wDAiCC;AAzCD;;;;;;;GAOG;AACH,SAAgB,sBAAsB;IACpC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the admin sidebar component template.
|
|
3
|
+
*
|
|
4
|
+
* This sidebar provides navigation for the CMS admin interface with:
|
|
5
|
+
* - Dashboard link
|
|
6
|
+
* - Content section with expandable content types
|
|
7
|
+
* - Media library link
|
|
8
|
+
* - Settings link (admin only)
|
|
9
|
+
*
|
|
10
|
+
* @returns Template string for app/components/admin/sidebar.tsx
|
|
11
|
+
*/
|
|
12
|
+
export declare function getAdminSidebarTemplate(): string;
|
|
13
|
+
//# sourceMappingURL=admin-sidebar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-sidebar.d.ts","sourceRoot":"","sources":["../../src/templates/admin-sidebar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAqIhD"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getAdminSidebarTemplate = getAdminSidebarTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the admin sidebar component template.
|
|
6
|
+
*
|
|
7
|
+
* This sidebar provides navigation for the CMS admin interface with:
|
|
8
|
+
* - Dashboard link
|
|
9
|
+
* - Content section with expandable content types
|
|
10
|
+
* - Media library link
|
|
11
|
+
* - Settings link (admin only)
|
|
12
|
+
*
|
|
13
|
+
* @returns Template string for app/components/admin/sidebar.tsx
|
|
14
|
+
*/
|
|
15
|
+
function getAdminSidebarTemplate() {
|
|
16
|
+
return `import { NavLink, useLocation } from '@remix-run/react';
|
|
17
|
+
import {
|
|
18
|
+
LayoutDashboard,
|
|
19
|
+
FileText,
|
|
20
|
+
Image,
|
|
21
|
+
Settings,
|
|
22
|
+
ChevronDown,
|
|
23
|
+
ChevronRight,
|
|
24
|
+
} from 'lucide-react';
|
|
25
|
+
import { useState } from 'react';
|
|
26
|
+
import { cn } from '~/lib/utils';
|
|
27
|
+
|
|
28
|
+
interface SidebarProps {
|
|
29
|
+
isAdmin?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface NavItemProps {
|
|
33
|
+
to: string;
|
|
34
|
+
icon: React.ReactNode;
|
|
35
|
+
label: string;
|
|
36
|
+
end?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function NavItem({ to, icon, label, end }: NavItemProps) {
|
|
40
|
+
return (
|
|
41
|
+
<NavLink
|
|
42
|
+
to={to}
|
|
43
|
+
end={end}
|
|
44
|
+
className={({ isActive }) =>
|
|
45
|
+
cn(
|
|
46
|
+
'flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
|
47
|
+
isActive
|
|
48
|
+
? 'bg-accent text-accent-foreground'
|
|
49
|
+
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
>
|
|
53
|
+
{icon}
|
|
54
|
+
{label}
|
|
55
|
+
</NavLink>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function AdminSidebar({ isAdmin = false }: SidebarProps) {
|
|
60
|
+
const [contentExpanded, setContentExpanded] = useState(true);
|
|
61
|
+
const location = useLocation();
|
|
62
|
+
const isContentActive = location.pathname.startsWith('/admin/content');
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<aside className="flex h-full w-64 flex-col border-r bg-background">
|
|
66
|
+
{/* Logo/Brand */}
|
|
67
|
+
<div className="flex h-16 items-center border-b px-6">
|
|
68
|
+
<span className="text-lg font-semibold">CMS Admin</span>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
{/* Navigation */}
|
|
72
|
+
<nav className="flex-1 space-y-1 p-4">
|
|
73
|
+
<NavItem
|
|
74
|
+
to="/admin"
|
|
75
|
+
icon={<LayoutDashboard className="h-4 w-4" />}
|
|
76
|
+
label="Dashboard"
|
|
77
|
+
end
|
|
78
|
+
/>
|
|
79
|
+
|
|
80
|
+
{/* Content Section */}
|
|
81
|
+
<div className="space-y-1">
|
|
82
|
+
<button
|
|
83
|
+
onClick={() => setContentExpanded(!contentExpanded)}
|
|
84
|
+
className={cn(
|
|
85
|
+
'flex w-full items-center justify-between rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
|
86
|
+
isContentActive
|
|
87
|
+
? 'bg-accent text-accent-foreground'
|
|
88
|
+
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
|
89
|
+
)}
|
|
90
|
+
>
|
|
91
|
+
<span className="flex items-center gap-3">
|
|
92
|
+
<FileText className="h-4 w-4" />
|
|
93
|
+
Content
|
|
94
|
+
</span>
|
|
95
|
+
{contentExpanded ? (
|
|
96
|
+
<ChevronDown className="h-4 w-4" />
|
|
97
|
+
) : (
|
|
98
|
+
<ChevronRight className="h-4 w-4" />
|
|
99
|
+
)}
|
|
100
|
+
</button>
|
|
101
|
+
|
|
102
|
+
{contentExpanded && (
|
|
103
|
+
<div className="ml-4 space-y-1 border-l pl-4">
|
|
104
|
+
<NavLink
|
|
105
|
+
to="/admin/content"
|
|
106
|
+
end
|
|
107
|
+
className={({ isActive }) =>
|
|
108
|
+
cn(
|
|
109
|
+
'block rounded-lg px-3 py-2 text-sm transition-colors',
|
|
110
|
+
isActive
|
|
111
|
+
? 'bg-accent/50 text-accent-foreground'
|
|
112
|
+
: 'text-muted-foreground hover:bg-accent/50 hover:text-accent-foreground'
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
>
|
|
116
|
+
All Content
|
|
117
|
+
</NavLink>
|
|
118
|
+
{/* Content types will be dynamically added here in future sprints */}
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<NavItem
|
|
124
|
+
to="/admin/media"
|
|
125
|
+
icon={<Image className="h-4 w-4" />}
|
|
126
|
+
label="Media"
|
|
127
|
+
/>
|
|
128
|
+
|
|
129
|
+
{isAdmin && (
|
|
130
|
+
<NavItem
|
|
131
|
+
to="/admin/settings"
|
|
132
|
+
icon={<Settings className="h-4 w-4" />}
|
|
133
|
+
label="Settings"
|
|
134
|
+
/>
|
|
135
|
+
)}
|
|
136
|
+
</nav>
|
|
137
|
+
|
|
138
|
+
{/* Footer */}
|
|
139
|
+
<div className="border-t p-4">
|
|
140
|
+
<p className="text-xs text-muted-foreground">
|
|
141
|
+
fnd-platform CMS
|
|
142
|
+
</p>
|
|
143
|
+
</div>
|
|
144
|
+
</aside>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=admin-sidebar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-sidebar.js","sourceRoot":"","sources":["../../src/templates/admin-sidebar.ts"],"names":[],"mappings":";;AAWA,0DAqIC;AAhJD;;;;;;;;;;GAUG;AACH,SAAgB,uBAAuB;IACrC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmIR,CAAC;AACF,CAAC"}
|