create-nextjs-cms 0.9.30 → 0.9.32

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 (145) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +71 -71
  3. package/dist/helpers/utils.js +16 -16
  4. package/dist/lib/section-creators.js +166 -166
  5. package/package.json +1 -1
  6. package/templates/default/.eslintrc.json +5 -5
  7. package/templates/default/.prettierignore +7 -7
  8. package/templates/default/.prettierrc.json +27 -27
  9. package/templates/default/CHANGELOG.md +140 -140
  10. package/templates/default/_gitignore +57 -57
  11. package/templates/default/app/(auth)/auth/login/LoginPage.tsx +192 -192
  12. package/templates/default/app/(auth)/auth/login/page.tsx +11 -11
  13. package/templates/default/app/(auth)/auth-language-provider.tsx +34 -34
  14. package/templates/default/app/(auth)/layout.tsx +81 -81
  15. package/templates/default/app/(rootLayout)/(plugins)/[...slug]/error.tsx +64 -0
  16. package/templates/default/app/(rootLayout)/(plugins)/[...slug]/page.tsx +59 -47
  17. package/templates/default/app/(rootLayout)/(plugins)/[...slug]/plugin-server-registry.ts +14 -16
  18. package/templates/default/app/(rootLayout)/admins/page.tsx +10 -10
  19. package/templates/default/app/(rootLayout)/browse/[section]/[page]/page.tsx +22 -22
  20. package/templates/default/app/(rootLayout)/categorized/[section]/page.tsx +15 -15
  21. package/templates/default/app/(rootLayout)/dashboard/page.tsx +68 -70
  22. package/templates/default/app/(rootLayout)/edit/[section]/[itemId]/page.tsx +20 -20
  23. package/templates/default/app/(rootLayout)/layout.tsx +81 -81
  24. package/templates/default/app/(rootLayout)/loading.tsx +10 -10
  25. package/templates/default/app/(rootLayout)/log/page.tsx +5 -7
  26. package/templates/default/app/(rootLayout)/new/[section]/page.tsx +15 -15
  27. package/templates/default/app/(rootLayout)/section/[section]/page.tsx +19 -19
  28. package/templates/default/app/(rootLayout)/settings/page.tsx +11 -13
  29. package/templates/default/app/api/auth/csrf/route.ts +25 -25
  30. package/templates/default/app/api/auth/refresh/route.ts +10 -10
  31. package/templates/default/app/api/auth/route.ts +49 -49
  32. package/templates/default/app/api/auth/session/route.ts +20 -20
  33. package/templates/default/app/api/document/route.ts +165 -165
  34. package/templates/default/app/api/editor/photo/route.ts +49 -49
  35. package/templates/default/app/api/photo/route.ts +27 -27
  36. package/templates/default/app/api/submit/section/item/[slug]/route.ts +95 -95
  37. package/templates/default/app/api/submit/section/item/route.ts +56 -56
  38. package/templates/default/app/api/submit/section/simple/route.ts +86 -86
  39. package/templates/default/app/api/video/route.ts +174 -174
  40. package/templates/default/app/globals.css +236 -236
  41. package/templates/default/cms.config.ts +56 -56
  42. package/templates/default/components/admin/admin-card.tsx +165 -165
  43. package/templates/default/components/admin/admin-edit-page.tsx +124 -124
  44. package/templates/default/components/admin/admin-privilege-card.tsx +184 -184
  45. package/templates/default/components/admin/new-admin-form.tsx +172 -172
  46. package/templates/default/components/container-box.tsx +24 -24
  47. package/templates/default/components/dnd-kit/draggable.tsx +21 -21
  48. package/templates/default/components/dnd-kit/droppable.tsx +20 -20
  49. package/templates/default/components/dnd-kit/sortable-item.tsx +18 -18
  50. package/templates/default/components/feedback/error-component.tsx +16 -16
  51. package/templates/default/components/feedback/info-card.tsx +93 -93
  52. package/templates/default/components/feedback/loading-spinners.tsx +67 -67
  53. package/templates/default/components/feedback/modal.tsx +166 -166
  54. package/templates/default/components/feedback/progress-bar.tsx +48 -48
  55. package/templates/default/components/feedback/tooltip-component.tsx +27 -27
  56. package/templates/default/components/form/form-input-element.tsx +70 -70
  57. package/templates/default/components/form/helpers/_section-hot-reload.js +1 -1
  58. package/templates/default/components/form/helpers/util.ts +17 -17
  59. package/templates/default/components/form/inputs/checkbox-form-input.tsx +46 -46
  60. package/templates/default/components/form/inputs/color-form-input.tsx +44 -44
  61. package/templates/default/components/form/inputs/date-form-input.tsx +93 -93
  62. package/templates/default/components/form/inputs/map-form-input.tsx +141 -141
  63. package/templates/default/components/form/inputs/multiple-select-form-input.tsx +85 -85
  64. package/templates/default/components/form/inputs/number-form-input.tsx +43 -43
  65. package/templates/default/components/form/inputs/password-form-input.tsx +47 -47
  66. package/templates/default/components/form/inputs/photo-form-input.tsx +279 -279
  67. package/templates/default/components/form/inputs/rich-text-form-input.tsx +148 -148
  68. package/templates/default/components/form/inputs/select-form-input.tsx +159 -159
  69. package/templates/default/components/form/inputs/slug-form-input.tsx +131 -131
  70. package/templates/default/components/form/inputs/tags-form-input.tsx +255 -255
  71. package/templates/default/components/form/inputs/text-form-input.tsx +61 -61
  72. package/templates/default/components/form/inputs/textarea-form-input.tsx +61 -61
  73. package/templates/default/components/layout/default-nav-items.tsx +3 -3
  74. package/templates/default/components/layout/layout.tsx +84 -84
  75. package/templates/default/components/layout/navbar.tsx +258 -258
  76. package/templates/default/components/layout/sidebar-dropdown-item.tsx +83 -83
  77. package/templates/default/components/layout/sidebar-item.tsx +24 -24
  78. package/templates/default/components/layout/sidebar.tsx +229 -229
  79. package/templates/default/components/layout/theme-provider.tsx +8 -8
  80. package/templates/default/components/layout/theme-toggle.tsx +39 -39
  81. package/templates/default/components/locale/locale-switcher.tsx +98 -98
  82. package/templates/default/components/media/dropzone.tsx +154 -154
  83. package/templates/default/components/media/protected-document.tsx +44 -44
  84. package/templates/default/components/media/protected-image.tsx +143 -143
  85. package/templates/default/components/media/protected-video.tsx +76 -76
  86. package/templates/default/components/multi-select.tsx +1150 -1150
  87. package/templates/default/components/pages/admins-page.tsx +43 -43
  88. package/templates/default/components/pages/browse-page.tsx +106 -106
  89. package/templates/default/components/pages/categorized-section-page.tsx +31 -31
  90. package/templates/default/components/pages/dashboard-page-alt.tsx +45 -45
  91. package/templates/default/components/pages/item-edit-page.tsx +267 -267
  92. package/templates/default/components/pages/log-page.tsx +107 -107
  93. package/templates/default/components/pages/new-page.tsx +183 -183
  94. package/templates/default/components/pages/section-page.tsx +203 -203
  95. package/templates/default/components/pages/settings-page.tsx +232 -232
  96. package/templates/default/components/pagination/pagination-buttons.tsx +147 -147
  97. package/templates/default/components/pagination/pagination.tsx +36 -36
  98. package/templates/default/components/sections/category-delete-confirm-page.tsx +130 -130
  99. package/templates/default/components/sections/category-section-select-input.tsx +139 -139
  100. package/templates/default/components/sections/conditional-fields.tsx +49 -49
  101. package/templates/default/components/sections/section-icon.tsx +8 -8
  102. package/templates/default/components/sections/section-item-card.tsx +143 -143
  103. package/templates/default/components/sections/section-item-status-badge.tsx +17 -17
  104. package/templates/default/components/sections/select-input-buttons.tsx +125 -125
  105. package/templates/default/components/select-box.tsx +98 -98
  106. package/templates/default/components/ui/accordion.tsx +53 -53
  107. package/templates/default/components/ui/alert-dialog.tsx +113 -113
  108. package/templates/default/components/ui/alert.tsx +47 -47
  109. package/templates/default/components/ui/badge.tsx +38 -38
  110. package/templates/default/components/ui/card.tsx +43 -43
  111. package/templates/default/components/ui/command.tsx +137 -137
  112. package/templates/default/components/ui/custom-alert-dialog.tsx +113 -113
  113. package/templates/default/components/ui/custom-dialog.tsx +123 -123
  114. package/templates/default/components/ui/dialog.tsx +123 -123
  115. package/templates/default/components/ui/direction.tsx +22 -22
  116. package/templates/default/components/ui/dropdown-menu.tsx +182 -182
  117. package/templates/default/components/ui/input-group.tsx +54 -54
  118. package/templates/default/components/ui/input.tsx +22 -22
  119. package/templates/default/components/ui/label.tsx +19 -19
  120. package/templates/default/components/ui/popover.tsx +42 -42
  121. package/templates/default/components/ui/progress.tsx +31 -31
  122. package/templates/default/components/ui/scroll-area.tsx +42 -42
  123. package/templates/default/components/ui/select.tsx +165 -165
  124. package/templates/default/components/ui/separator.tsx +28 -28
  125. package/templates/default/components/ui/sheet.tsx +103 -103
  126. package/templates/default/components/ui/spinner.tsx +16 -16
  127. package/templates/default/components/ui/switch.tsx +29 -29
  128. package/templates/default/components/ui/table.tsx +83 -83
  129. package/templates/default/components/ui/tabs.tsx +55 -55
  130. package/templates/default/components/ui/toast.tsx +113 -113
  131. package/templates/default/components/ui/toaster.tsx +35 -35
  132. package/templates/default/components/ui/tooltip.tsx +30 -30
  133. package/templates/default/components/ui/use-toast.ts +187 -187
  134. package/templates/default/drizzle.config.ts +4 -4
  135. package/templates/default/dynamic-schemas/schema.ts +225 -75
  136. package/templates/default/env/env.ts +46 -46
  137. package/templates/default/envConfig.ts +4 -4
  138. package/templates/default/lib/postinstall.js +14 -14
  139. package/templates/default/lib/utils.ts +6 -6
  140. package/templates/default/next-env.d.ts +6 -6
  141. package/templates/default/next.config.ts +24 -24
  142. package/templates/default/package.json +1 -1
  143. package/templates/default/postcss.config.mjs +6 -6
  144. package/templates/default/proxy.ts +32 -32
  145. package/templates/default/tsconfig.json +48 -48
@@ -0,0 +1,64 @@
1
+ 'use client'
2
+
3
+ import { useEffect } from 'react'
4
+ import { Button } from '@/components/ui/button'
5
+ import { AlertTriangle, Bug, RefreshCw } from 'lucide-react'
6
+ import { useI18n } from 'nextjs-cms/translations/client'
7
+ import { Badge } from '@/components/ui/badge'
8
+
9
+ export default function PluginError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
10
+ const t = useI18n()
11
+ const errorMessage = error.message || t('unexpectedPluginError')
12
+
13
+ useEffect(() => {
14
+ // Surface plugin failures in the console; error.digest correlates with server logs.
15
+ console.error('[plugin]', error)
16
+ }, [error])
17
+
18
+ return (
19
+ <div className='flex min-h-[360px] items-center justify-center p-4 sm:p-6'>
20
+ <section className='bg-card text-card-foreground w-full max-w-3xl overflow-hidden rounded-lg border shadow-sm'>
21
+ <div className='from-destructive/10 via-background to-background border-b bg-linear-to-r px-5 py-5 sm:px-6'>
22
+ <div className='flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between'>
23
+ <div className='flex items-start gap-4'>
24
+ <div className='bg-destructive/10 dark:bg-destructive/30 text-destructive ring-destructive/20 flex size-12 shrink-0 items-center justify-center rounded-lg ring-1'>
25
+ <AlertTriangle className='size-6' aria-hidden='true' />
26
+ </div>
27
+ <div>
28
+ <Badge variant='destructive' className='mb-2'>
29
+ {t('pluginError')}
30
+ </Badge>
31
+ <h2 className='text-xl font-semibold tracking-tight'>{t('pluginFailedToLoad')}</h2>
32
+ <p className='text-muted-foreground mt-2 max-w-2xl text-sm'>
33
+ {t('pluginFailedToLoadDescription')}
34
+ </p>
35
+ </div>
36
+ </div>
37
+
38
+ <Button type='button' size='sm' onClick={() => reset()} className='self-start'>
39
+ <RefreshCw className='size-4' aria-hidden='true' />
40
+ {t('retry')}
41
+ </Button>
42
+ </div>
43
+ </div>
44
+
45
+ <dl className='divide-y text-sm'>
46
+ <div className='grid gap-2 px-5 py-4 sm:grid-cols-[10rem_1fr] sm:px-6'>
47
+ <dt className='text-foreground flex items-center gap-2 font-medium'>
48
+ <Bug className='text-muted-foreground size-4' aria-hidden='true' />
49
+ {t('details')}
50
+ </dt>
51
+ <dd className='text-muted-foreground wrap-break-word'>{errorMessage}</dd>
52
+ </div>
53
+
54
+ {error.digest && (
55
+ <div className='grid gap-2 px-5 py-4 sm:grid-cols-[10rem_1fr] sm:px-6'>
56
+ <dt className='text-foreground font-medium'>{t('errorDigest')}</dt>
57
+ <dd className='text-muted-foreground font-mono text-xs break-all'>{error.digest}</dd>
58
+ </div>
59
+ )}
60
+ </dl>
61
+ </section>
62
+ </div>
63
+ )
64
+ }
@@ -1,47 +1,59 @@
1
- import { notFound } from 'next/navigation'
2
- import auth from 'nextjs-cms/auth'
3
- import { getAdminPrivileges } from 'nextjs-cms/api/server/actions'
4
- import { findPluginRouteByPath, isDashboardOverridePlugin, runRoutePrefetches } from 'nextjs-cms/plugins/server'
5
- import { api, HydrateClient } from '@/app/_trpc/server'
6
- import { getPluginServerComponent } from './plugin-server-registry'
7
-
8
- export const dynamic = 'force-dynamic'
9
- type Params = Promise<{ slug?: string[] }>
10
-
11
- export default async function Page(props: { params: Params }) {
12
- const params = await props.params
13
- const slug = params.slug ?? []
14
-
15
- const path = `/${slug.join('/')}`
16
- const session = await auth()
17
- if (!session?.user) {
18
- notFound()
19
- }
20
-
21
- const plugin = await findPluginRouteByPath(path)
22
- if (!plugin) {
23
- notFound()
24
- }
25
-
26
- // Bypass privilege check for dashboard override plugin
27
- const isDashboardPlugin = await isDashboardOverridePlugin(plugin.pluginName)
28
- if (!isDashboardPlugin) {
29
- const privilegeSet = await getAdminPrivileges(session.user.id)
30
- if (!privilegeSet.has(plugin.pluginName)) {
31
- notFound()
32
- }
33
- }
34
-
35
- const PluginComponent = await getPluginServerComponent(plugin.pluginName, plugin.component)
36
- if (!PluginComponent) {
37
- notFound()
38
- }
39
-
40
- await runRoutePrefetches(api, plugin)
41
-
42
- return (
43
- <HydrateClient>
44
- <PluginComponent />
45
- </HydrateClient>
46
- )
47
- }
1
+ import { notFound } from 'next/navigation'
2
+ import auth from 'nextjs-cms/auth'
3
+ import { Suspense } from 'react'
4
+ import { getAdminPrivileges } from 'nextjs-cms/api/server/actions'
5
+ import { findPluginRouteByPath, isDashboardOverridePlugin, runRoutePrefetches } from 'nextjs-cms/plugins/server'
6
+ import { api, HydrateClient } from '@/app/_trpc/server'
7
+ import { getPluginServerComponent } from './plugin-server-registry'
8
+ import LoadingSpinners from '@/components/feedback/loading-spinners'
9
+
10
+ type Params = Promise<{ slug?: string[] }>
11
+
12
+ export default async function Page(props: { params: Params }) {
13
+ const params = await props.params
14
+ const slug = params.slug ?? []
15
+
16
+ const path = `/${slug.join('/')}`
17
+ const session = await auth()
18
+ if (!session?.user) {
19
+ notFound()
20
+ }
21
+
22
+ const plugin = await findPluginRouteByPath(path)
23
+ if (!plugin) {
24
+ notFound()
25
+ }
26
+
27
+ // Bypass privilege check for dashboard override plugin
28
+ const isDashboardPlugin = await isDashboardOverridePlugin(plugin.pluginName)
29
+ if (!isDashboardPlugin) {
30
+ const privilegeSet = await getAdminPrivileges(session.user.id)
31
+ if (!privilegeSet.has(plugin.pluginName)) {
32
+ notFound()
33
+ }
34
+ }
35
+
36
+ // Fire-and-forget: HydrateClient streams resolved query data as it becomes available,
37
+ // so awaiting here would block the shell on slow upstream APIs (cPanel, GA, etc.)
38
+ void runRoutePrefetches(api, plugin)
39
+
40
+ const PluginComponent = await getPluginServerComponent(plugin.pluginName, plugin.component)
41
+ if (!PluginComponent) {
42
+ notFound()
43
+ }
44
+ return (
45
+ <HydrateClient>
46
+ {/* This way, or use useSuspenseQuery
47
+ in each plugin client component and remove the <Suspense /> here */}
48
+ <Suspense
49
+ fallback={
50
+ <div className='p-20'>
51
+ <LoadingSpinners />
52
+ </div>
53
+ }
54
+ >
55
+ <PluginComponent />
56
+ </Suspense>
57
+ </HydrateClient>
58
+ )
59
+ }
@@ -1,22 +1,20 @@
1
- import type { ComponentType } from 'react'
2
-
1
+ import type { ComponentType } from 'react'
2
+
3
3
  export const pluginNamesMap: Record<string, string> = {
4
4
  'cpanel-dashboard': '@nextjscms/plugin-cpanel-dashboard/server',
5
5
  'cpanel-emails': '@nextjscms/plugin-cpanel-emails/server',
6
6
  'google-analytics': '@nextjscms/plugin-google-analytics/server',
7
7
  // A workaround to avoid importing error if no plugins are installed
8
8
  blank: 'nextjs-cms/plugins/blank-component',
9
- }
10
-
11
- export const getPluginServerComponent = async (
12
- pluginName: string,
13
- componentName: string | undefined,
14
- ): Promise<ComponentType | null> => {
15
- const modulePath = pluginNamesMap[pluginName] ?? pluginNamesMap.blank
16
- if (!modulePath) return null
17
-
18
- const mod = (await import(modulePath)) as Record<string, unknown>
19
- const component = (componentName ? mod[componentName] : mod.default) as ComponentType | undefined
20
-
21
- return component ?? null
22
- }
9
+ }
10
+
11
+ export const getPluginServerComponent = async (
12
+ pluginName: string,
13
+ componentName: string | undefined,
14
+ ): Promise<ComponentType | null> => {
15
+ const modulePath = pluginNamesMap[pluginName]
16
+ if (!modulePath) return null
17
+ const mod = await import(modulePath)
18
+ const Component = componentName ? mod[componentName] : mod.default
19
+ return (Component as ComponentType) ?? null
20
+ }
@@ -1,10 +1,10 @@
1
- import AdminsPage from '@/components/pages/admins-page'
2
- import { api, HydrateClient } from '@/app/_trpc/server'
3
- export default async function Page() {
4
- await api.admins.list.prefetch()
5
- return (
6
- <HydrateClient>
7
- <AdminsPage />
8
- </HydrateClient>
9
- )
10
- }
1
+ import AdminsPage from '@/components/pages/admins-page'
2
+ import { api, HydrateClient } from '@/app/_trpc/server'
3
+ export default async function Page() {
4
+ await api.admins.list.prefetch()
5
+ return (
6
+ <HydrateClient>
7
+ <AdminsPage />
8
+ </HydrateClient>
9
+ )
10
+ }
@@ -1,22 +1,22 @@
1
- import BrowsePage from '@/components/pages/browse-page'
2
- import { api, HydrateClient } from '@/app/_trpc/server'
3
- type Params = Promise<{ section: string; page: string }>
4
- type SearchParams = Promise<{ q: string | undefined }>
5
- export default async function Page(props: { params: Params; searchParams: SearchParams }) {
6
- const params = await props.params
7
- const searchParams = await props.searchParams
8
- const { q } = searchParams
9
- const { section, page } = params
10
-
11
- await api.hasItemsSections.listItems.prefetch({
12
- sectionName: section,
13
- page: parseInt(page) ?? 1,
14
- q: q ?? '',
15
- })
16
-
17
- return (
18
- <HydrateClient>
19
- <BrowsePage page={params.page} section={params.section} />
20
- </HydrateClient>
21
- )
22
- }
1
+ import BrowsePage from '@/components/pages/browse-page'
2
+ import { api, HydrateClient } from '@/app/_trpc/server'
3
+ type Params = Promise<{ section: string; page: string }>
4
+ type SearchParams = Promise<{ q: string | undefined }>
5
+ export default async function Page(props: { params: Params; searchParams: SearchParams }) {
6
+ const params = await props.params
7
+ const searchParams = await props.searchParams
8
+ const { q } = searchParams
9
+ const { section, page } = params
10
+
11
+ await api.hasItemsSections.listItems.prefetch({
12
+ sectionName: section,
13
+ page: parseInt(page) ?? 1,
14
+ q: q ?? '',
15
+ })
16
+
17
+ return (
18
+ <HydrateClient>
19
+ <BrowsePage page={params.page} section={params.section} />
20
+ </HydrateClient>
21
+ )
22
+ }
@@ -1,15 +1,15 @@
1
- import CategorizedSectionPage from '@/components/pages/categorized-section-page'
2
- import { api, HydrateClient } from '@/app/_trpc/server'
3
- type Params = Promise<{ section: string }>
4
-
5
- export default async function Page(props: { params: Params }) {
6
- const params = await props.params
7
- await api.categorySections.get.prefetch({
8
- sectionName: params.section,
9
- })
10
- return (
11
- <HydrateClient>
12
- <CategorizedSectionPage section={params.section} />
13
- </HydrateClient>
14
- )
15
- }
1
+ import CategorizedSectionPage from '@/components/pages/categorized-section-page'
2
+ import { api, HydrateClient } from '@/app/_trpc/server'
3
+ type Params = Promise<{ section: string }>
4
+
5
+ export default async function Page(props: { params: Params }) {
6
+ const params = await props.params
7
+ await api.categorySections.get.prefetch({
8
+ sectionName: params.section,
9
+ })
10
+ return (
11
+ <HydrateClient>
12
+ <CategorizedSectionPage section={params.section} />
13
+ </HydrateClient>
14
+ )
15
+ }
@@ -1,70 +1,68 @@
1
- import { getDashboardOverride, runRoutePrefetches } from 'nextjs-cms/plugins/server'
2
- import { api, HydrateClient } from '@/app/_trpc/server'
3
- import { getPluginServerComponent } from '../(plugins)/[...slug]/plugin-server-registry'
4
-
5
- export const dynamic = 'force-dynamic'
6
-
7
- function DefaultDashboard() {
8
- return (
9
- <div className='w-full'>
10
- <div className='bg-linear-to-r from-amber-200 via-orange-200 to-rose-200 p-8 text-foreground dark:from-amber-900 dark:via-orange-900 dark:to-rose-900'>
11
- <h1 className='text-3xl font-extrabold tracking-tight'>Welcome to Mission Control</h1>
12
- <p className='mt-2 text-base text-muted-foreground'>
13
- Your CMS is ready. Chart a course, publish boldly, and keep the lights green.
14
- </p>
15
- </div>
16
-
17
- <div className='space-y-6 p-6'>
18
- <section className='rounded-lg border bg-card p-6 text-card-foreground shadow-sm'>
19
- <h2 className='text-xl font-semibold'>Today&apos;s focus</h2>
20
- <p className='mt-2 text-sm text-muted-foreground'>
21
- Start with one small win: verify a section, ship a quick update, or polish a headline.
22
- </p>
23
- </section>
24
-
25
- <section className='grid gap-4 md:grid-cols-3'>
26
- <div className='rounded-lg border bg-card p-4 text-card-foreground shadow-sm'>
27
- <div className='text-sm font-semibold'>Ship with confidence</div>
28
- <div className='mt-2 text-sm text-muted-foreground'>
29
- Keep changes tight and reversible. Small releases, fast feedback.
30
- </div>
31
- </div>
32
- <div className='rounded-lg border bg-card p-4 text-card-foreground shadow-sm'>
33
- <div className='text-sm font-semibold'>Stay organized</div>
34
- <div className='mt-2 text-sm text-muted-foreground'>
35
- Group your content by intent, not just by type.
36
- </div>
37
- </div>
38
- <div className='rounded-lg border bg-card p-4 text-card-foreground shadow-sm'>
39
- <div className='text-sm font-semibold'>Delight users</div>
40
- <div className='mt-2 text-sm text-muted-foreground'>
41
- A clean headline and a single strong image can do the heavy lifting.
42
- </div>
43
- </div>
44
- </section>
45
- </div>
46
- </div>
47
- )
48
- }
49
-
50
- export default async function DashboardPage() {
51
- // Check for dashboard override configuration
52
- const override = await getDashboardOverride()
53
- if (!override) {
54
- return <DefaultDashboard />
55
- }
56
-
57
- // Render the override plugin component (no privilege check for dashboard override)
58
- const PluginComponent = await getPluginServerComponent(override.pluginName, override.component)
59
- if (!PluginComponent) {
60
- return <DefaultDashboard />
61
- }
62
-
63
- await runRoutePrefetches(api, override)
64
-
65
- return (
66
- <HydrateClient>
67
- <PluginComponent />
68
- </HydrateClient>
69
- )
70
- }
1
+ import { getDashboardOverride, runRoutePrefetches } from 'nextjs-cms/plugins/server'
2
+ import { api, HydrateClient } from '@/app/_trpc/server'
3
+ import { getPluginServerComponent } from '../(plugins)/[...slug]/plugin-server-registry'
4
+
5
+ function DefaultDashboard() {
6
+ return (
7
+ <div className='w-full'>
8
+ <div className='text-foreground bg-linear-to-r from-amber-200 via-orange-200 to-rose-200 p-8 dark:from-amber-900 dark:via-orange-900 dark:to-rose-900'>
9
+ <h1 className='text-3xl font-extrabold tracking-tight'>Welcome to Mission Control</h1>
10
+ <p className='text-muted-foreground mt-2 text-base'>
11
+ Your CMS is ready. Chart a course, publish boldly, and keep the lights green.
12
+ </p>
13
+ </div>
14
+
15
+ <div className='space-y-6 p-6'>
16
+ <section className='bg-card text-card-foreground rounded-lg border p-6 shadow-sm'>
17
+ <h2 className='text-xl font-semibold'>Today&apos;s focus</h2>
18
+ <p className='text-muted-foreground mt-2 text-sm'>
19
+ Start with one small win: verify a section, ship a quick update, or polish a headline.
20
+ </p>
21
+ </section>
22
+
23
+ <section className='grid gap-4 md:grid-cols-3'>
24
+ <div className='bg-card text-card-foreground rounded-lg border p-4 shadow-sm'>
25
+ <div className='text-sm font-semibold'>Ship with confidence</div>
26
+ <div className='text-muted-foreground mt-2 text-sm'>
27
+ Keep changes tight and reversible. Small releases, fast feedback.
28
+ </div>
29
+ </div>
30
+ <div className='bg-card text-card-foreground rounded-lg border p-4 shadow-sm'>
31
+ <div className='text-sm font-semibold'>Stay organized</div>
32
+ <div className='text-muted-foreground mt-2 text-sm'>
33
+ Group your content by intent, not just by type.
34
+ </div>
35
+ </div>
36
+ <div className='bg-card text-card-foreground rounded-lg border p-4 shadow-sm'>
37
+ <div className='text-sm font-semibold'>Delight users</div>
38
+ <div className='text-muted-foreground mt-2 text-sm'>
39
+ A clean headline and a single strong image can do the heavy lifting.
40
+ </div>
41
+ </div>
42
+ </section>
43
+ </div>
44
+ </div>
45
+ )
46
+ }
47
+
48
+ export default async function DashboardPage() {
49
+ // Check for dashboard override configuration
50
+ const override = await getDashboardOverride()
51
+ if (!override) {
52
+ return <DefaultDashboard />
53
+ }
54
+
55
+ // Render the override plugin component (no privilege check for dashboard override)
56
+ const PluginComponent = await getPluginServerComponent(override.pluginName, override.component)
57
+ if (!PluginComponent) {
58
+ return <DefaultDashboard />
59
+ }
60
+
61
+ await runRoutePrefetches(api, override)
62
+
63
+ return (
64
+ <HydrateClient>
65
+ <PluginComponent />
66
+ </HydrateClient>
67
+ )
68
+ }
@@ -1,20 +1,20 @@
1
- import ItemEditPage from '@/components/pages/item-edit-page'
2
- import { api, HydrateClient } from '@/app/_trpc/server'
3
- type Params = Promise<{ section: string; itemId: string }>
4
- type SearchParams = Promise<{ locale?: string }>
5
-
6
- export default async function Page(props: { params: Params; searchParams: SearchParams }) {
7
- const params = await props.params
8
- const searchParams = await props.searchParams
9
- await api.hasItemsSections.editItem.prefetch({
10
- sectionName: params.section,
11
- sectionItemId: params.itemId,
12
- locale: searchParams.locale,
13
- })
14
-
15
- return (
16
- <HydrateClient>
17
- <ItemEditPage sectionType='has_items' section={params.section} itemId={params.itemId} />
18
- </HydrateClient>
19
- )
20
- }
1
+ import ItemEditPage from '@/components/pages/item-edit-page'
2
+ import { api, HydrateClient } from '@/app/_trpc/server'
3
+ type Params = Promise<{ section: string; itemId: string }>
4
+ type SearchParams = Promise<{ locale?: string }>
5
+
6
+ export default async function Page(props: { params: Params; searchParams: SearchParams }) {
7
+ const params = await props.params
8
+ const searchParams = await props.searchParams
9
+ await api.hasItemsSections.editItem.prefetch({
10
+ sectionName: params.section,
11
+ sectionItemId: params.itemId,
12
+ locale: searchParams.locale,
13
+ })
14
+
15
+ return (
16
+ <HydrateClient>
17
+ <ItemEditPage sectionType='has_items' section={params.section} itemId={params.itemId} />
18
+ </HydrateClient>
19
+ )
20
+ }