create-nextjs-cms 0.9.31 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nextjs-cms",
3
- "version": "0.9.31",
3
+ "version": "0.9.32",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,8 +28,8 @@
28
28
  "prettier": "^3.3.3",
29
29
  "tsx": "^4.20.6",
30
30
  "typescript": "^5.9.2",
31
- "@lzcms/eslint-config": "0.3.0",
32
31
  "@lzcms/prettier-config": "0.1.0",
32
+ "@lzcms/eslint-config": "0.3.0",
33
33
  "@lzcms/tsconfig": "0.1.0"
34
34
  },
35
35
  "prettier": "@lzcms/prettier-config",
@@ -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,26 +1,20 @@
1
- import dynamic from 'next/dynamic'
2
- import type { ComponentType } from 'react'
3
-
1
+ import type { ComponentType } from 'react'
2
+
4
3
  export const pluginNamesMap: Record<string, string> = {
5
4
  'cpanel-dashboard': '@nextjscms/plugin-cpanel-dashboard/server',
6
5
  'cpanel-emails': '@nextjscms/plugin-cpanel-emails/server',
7
6
  'google-analytics': '@nextjscms/plugin-google-analytics/server',
8
7
  // A workaround to avoid importing error if no plugins are installed
9
8
  blank: 'nextjs-cms/plugins/blank-component',
10
- }
11
-
12
- export const getPluginServerComponent = async (
13
- pluginName: string,
14
- componentName: string | undefined,
15
- ): Promise<ComponentType | null> => {
16
- const modulePath = pluginNamesMap[pluginName]
17
- if (!modulePath) return null
18
- const Component = dynamic(
19
- () =>
20
- import(modulePath ?? 'nextjs-cms/plugins/blank-component').then(
21
- (mod) => (componentName ? mod[componentName] : mod.default) as ComponentType,
22
- ),
23
- { ssr: true },
24
- )
25
- return Component
26
- }
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
+ }
@@ -2,42 +2,40 @@ import { getDashboardOverride, runRoutePrefetches } from 'nextjs-cms/plugins/ser
2
2
  import { api, HydrateClient } from '@/app/_trpc/server'
3
3
  import { getPluginServerComponent } from '../(plugins)/[...slug]/plugin-server-registry'
4
4
 
5
- export const dynamic = 'force-dynamic'
6
-
7
5
  function DefaultDashboard() {
8
6
  return (
9
7
  <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'>
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'>
11
9
  <h1 className='text-3xl font-extrabold tracking-tight'>Welcome to Mission Control</h1>
12
- <p className='mt-2 text-base text-muted-foreground'>
10
+ <p className='text-muted-foreground mt-2 text-base'>
13
11
  Your CMS is ready. Chart a course, publish boldly, and keep the lights green.
14
12
  </p>
15
13
  </div>
16
14
 
17
15
  <div className='space-y-6 p-6'>
18
- <section className='rounded-lg border bg-card p-6 text-card-foreground shadow-sm'>
16
+ <section className='bg-card text-card-foreground rounded-lg border p-6 shadow-sm'>
19
17
  <h2 className='text-xl font-semibold'>Today&apos;s focus</h2>
20
- <p className='mt-2 text-sm text-muted-foreground'>
18
+ <p className='text-muted-foreground mt-2 text-sm'>
21
19
  Start with one small win: verify a section, ship a quick update, or polish a headline.
22
20
  </p>
23
21
  </section>
24
22
 
25
23
  <section className='grid gap-4 md:grid-cols-3'>
26
- <div className='rounded-lg border bg-card p-4 text-card-foreground shadow-sm'>
24
+ <div className='bg-card text-card-foreground rounded-lg border p-4 shadow-sm'>
27
25
  <div className='text-sm font-semibold'>Ship with confidence</div>
28
- <div className='mt-2 text-sm text-muted-foreground'>
26
+ <div className='text-muted-foreground mt-2 text-sm'>
29
27
  Keep changes tight and reversible. Small releases, fast feedback.
30
28
  </div>
31
29
  </div>
32
- <div className='rounded-lg border bg-card p-4 text-card-foreground shadow-sm'>
30
+ <div className='bg-card text-card-foreground rounded-lg border p-4 shadow-sm'>
33
31
  <div className='text-sm font-semibold'>Stay organized</div>
34
- <div className='mt-2 text-sm text-muted-foreground'>
32
+ <div className='text-muted-foreground mt-2 text-sm'>
35
33
  Group your content by intent, not just by type.
36
34
  </div>
37
35
  </div>
38
- <div className='rounded-lg border bg-card p-4 text-card-foreground shadow-sm'>
36
+ <div className='bg-card text-card-foreground rounded-lg border p-4 shadow-sm'>
39
37
  <div className='text-sm font-semibold'>Delight users</div>
40
- <div className='mt-2 text-sm text-muted-foreground'>
38
+ <div className='text-muted-foreground mt-2 text-sm'>
41
39
  A clean headline and a single strong image can do the heavy lifting.
42
40
  </div>
43
41
  </div>
@@ -1,7 +1,5 @@
1
1
  import LogPage from '@/components/pages/log-page'
2
2
 
3
- export const dynamic = 'force-dynamic'
4
-
5
3
  export default async function Page() {
6
4
  return <LogPage />
7
5
  }
@@ -1,8 +1,6 @@
1
1
  import SettingsPage from '@/components/pages/settings-page'
2
2
  import { api, HydrateClient } from '@/app/_trpc/server'
3
3
 
4
- export const dynamic = 'force-dynamic'
5
-
6
4
  export default async function Page() {
7
5
  await api.accountSettings.get.prefetch()
8
6
  return (
@@ -70,7 +70,7 @@
70
70
  "nanoid": "^5.1.2",
71
71
  "next": "16.2.5",
72
72
  "next-themes": "^0.4.6",
73
- "nextjs-cms": "0.9.31",
73
+ "nextjs-cms": "0.9.32",
74
74
  "plaiceholder": "^3.0.0",
75
75
  "prettier-plugin-tailwindcss": "^0.7.2",
76
76
  "qrcode": "^1.5.4",