create-nextjs-cms 0.9.26 → 0.9.28

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.26",
3
+ "version": "0.9.28",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,8 +29,8 @@
29
29
  "tsx": "^4.20.6",
30
30
  "typescript": "^5.9.2",
31
31
  "@lzcms/eslint-config": "0.3.0",
32
- "@lzcms/tsconfig": "0.1.0",
33
- "@lzcms/prettier-config": "0.1.0"
32
+ "@lzcms/prettier-config": "0.1.0",
33
+ "@lzcms/tsconfig": "0.1.0"
34
34
  },
35
35
  "prettier": "@lzcms/prettier-config",
36
36
  "scripts": {
@@ -3,6 +3,8 @@
3
3
 
4
4
  @custom-variant dark (&:is(.dark *));
5
5
 
6
+ @source "../node_modules/@nextjscms/plugin-*/**/*.{tsx,ts,jsx,js}";
7
+
6
8
  /**
7
9
  * Background colors for the dashboard plugin
8
10
  */
@@ -198,9 +200,15 @@
198
200
  * {
199
201
  @apply border-border;
200
202
  }
203
+ html {
204
+ scrollbar-gutter: stable;
205
+ }
201
206
  body {
202
207
  @apply bg-background text-foreground;
203
208
  }
209
+ html body[data-scroll-locked] {
210
+ margin-right: 0 !important;
211
+ }
204
212
  }
205
213
 
206
214
  /* Progress bar indeterminate animation */
@@ -88,7 +88,7 @@ export default function DocumentFormInput({
88
88
  file={input.value}
89
89
  width={450}
90
90
  height={620}
91
- className='ring-3 mb-4 rounded p-1 ring-gray-400'
91
+ className='mb-4 rounded p-1 ring-3 ring-gray-400'
92
92
  />
93
93
  )
94
94
 
@@ -127,19 +127,22 @@ export default function DocumentFormInput({
127
127
  <CardContent className='flex flex-col gap-1'>
128
128
  <div className='mb-2 flex flex-col text-sm'>
129
129
  {input.maxFileSize ? (
130
- <div dir='ltr' className='flex flex-wrap items-center gap-2'>
130
+ <div className='flex flex-wrap items-center gap-2'>
131
131
  <InfoIcon size={14} />
132
132
  <span>{t('maxFileSize')}:</span>
133
133
  <Badge
134
+ dir='ltr'
134
135
  variant={'secondary'}
135
136
  >{`${input.maxFileSize.size} ${input.maxFileSize.unit.toUpperCase()}`}</Badge>
136
137
  </div>
137
138
  ) : null}
138
139
  {input.extensions ? (
139
- <div dir='ltr' className='flex flex-wrap items-center gap-2'>
140
+ <div className='flex flex-wrap items-center gap-2'>
140
141
  <InfoIcon size={14} />
141
142
  <span>{t('allowedExtensions')}:</span>
142
- <Badge variant={'secondary'}>{input.extensions.join(', ')}</Badge>
143
+ <Badge dir='ltr' variant={'secondary'}>
144
+ {input.extensions.join(', ')}
145
+ </Badge>
143
146
  </div>
144
147
  ) : null}
145
148
  </div>
@@ -165,12 +168,12 @@ export default function DocumentFormInput({
165
168
  <ChevronRight fontSize='large' />
166
169
  <div className='relative'>
167
170
  <X
168
- className='absolute -right-3 -top-3 z-10 cursor-pointer rounded-full border-2 border-gray-500 bg-white p-1'
171
+ className='absolute -top-3 -right-3 z-10 cursor-pointer rounded-full border-2 border-gray-500 bg-white p-1'
169
172
  onClick={clearSelectedFile}
170
173
  />
171
174
  <embed
172
175
  src={image}
173
- className='ring-3 mb-4 max-w-full rounded p-1 ring-green-600'
176
+ className='mb-4 max-w-full rounded p-1 ring-3 ring-green-600'
174
177
  width={350}
175
178
  height={520}
176
179
  />
@@ -204,7 +207,7 @@ export default function DocumentFormInput({
204
207
  <div className='w-[400px] max-w-full'>
205
208
  <button
206
209
  type='button'
207
- className='bg-linear-to-r flex w-full flex-wrap items-center justify-center gap-1.5 rounded border from-red-600 to-amber-500 p-2 text-center text-sm font-bold uppercase text-white drop-shadow-sm hover:from-red-400 hover:to-amber-400'
210
+ className='flex w-full flex-wrap items-center justify-center gap-1.5 rounded border bg-linear-to-r from-red-600 to-amber-500 p-2 text-center text-sm font-bold text-white uppercase drop-shadow-sm hover:from-red-400 hover:to-amber-400'
208
211
  onClick={() => {
209
212
  setModal({
210
213
  title: t('deleteDocument'),
@@ -244,7 +247,7 @@ export default function DocumentFormInput({
244
247
  <div className='dark:border-neutral my-2 flex w-[400px] max-w-full flex-col gap-1 rounded-lg border border-gray-400 p-2'>
245
248
  <button
246
249
  type='button'
247
- className='bg-linear-to-r flex w-full flex-wrap items-center justify-center gap-1.5 rounded border from-blue-700 to-sky-500 p-2 text-center text-sm font-bold uppercase text-white drop-shadow-sm hover:from-blue-500 hover:to-sky-400'
250
+ className='flex w-full flex-wrap items-center justify-center gap-1.5 rounded border bg-linear-to-r from-blue-700 to-sky-500 p-2 text-center text-sm font-bold text-white uppercase drop-shadow-sm hover:from-blue-500 hover:to-sky-400'
248
251
  onClick={() => {
249
252
  if (fileInputContainerRef.current?.firstChild) {
250
253
  ;(
@@ -122,29 +122,35 @@ export default function PhotoFormInput({
122
122
  <CardContent className='flex flex-col gap-1'>
123
123
  <div className='mb-2 flex flex-col text-sm'>
124
124
  {input.size ? (
125
- <div dir='ltr' className='flex flex-wrap items-center gap-2'>
125
+ <div className='flex flex-wrap items-center gap-2'>
126
126
  <InfoIcon size={14} />
127
127
  <span>
128
128
  {t('strict' in input.size ? 'imageDimensionsMustBe' : 'imageRecommendedDimensions')}
129
129
  :
130
130
  </span>
131
- <Badge variant={'secondary'}>{`${input.size.width} x ${input.size.height}`}</Badge>
131
+ <Badge
132
+ dir='ltr'
133
+ variant={'secondary'}
134
+ >{`${input.size.width} x ${input.size.height}`}</Badge>
132
135
  </div>
133
136
  ) : null}
134
137
  {input.maxFileSize ? (
135
- <div dir='ltr' className='flex flex-wrap items-center gap-2'>
138
+ <div className='flex flex-wrap items-center gap-2'>
136
139
  <InfoIcon size={14} />
137
140
  <span>{t('maxFileSize')}:</span>
138
141
  <Badge
142
+ dir='ltr'
139
143
  variant={'secondary'}
140
144
  >{`${input.maxFileSize.size} ${input.maxFileSize.unit.toUpperCase()}`}</Badge>
141
145
  </div>
142
146
  ) : null}
143
147
  {input.extensions ? (
144
- <div dir='ltr' className='flex flex-wrap items-center gap-2'>
148
+ <div className='flex flex-wrap items-center gap-2'>
145
149
  <InfoIcon size={14} />
146
150
  <span>{t('allowedExtensions')}:</span>
147
- <Badge variant={'secondary'}>{input.extensions.join(', ')}</Badge>
151
+ <Badge dir='ltr' variant={'secondary'}>
152
+ {input.extensions.join(', ')}
153
+ </Badge>
148
154
  </div>
149
155
  ) : null}
150
156
  </div>
@@ -6,6 +6,7 @@ import { useAxiosPrivate } from 'nextjs-cms/api/client'
6
6
  import { RichTextFieldClientConfig } from 'nextjs-cms/core/fields'
7
7
  import { FieldError, useFormContext } from 'react-hook-form'
8
8
  import { useLocale } from '@/components/form/content-locale-context'
9
+ import { useUILanguage } from 'nextjs-cms/translations/client'
9
10
 
10
11
  export default function RichTextFormInput({ input }: { input: RichTextFieldClientConfig }) {
11
12
  const { setValue, register, formState } = useFormContext()
@@ -15,6 +16,7 @@ export default function RichTextFormInput({ input }: { input: RichTextFieldClien
15
16
  const [initialValue, setInitialValue] = useState('')
16
17
  const [key, setKey] = useState(0) // Step 1: Add key state
17
18
  const locale = useLocale()
19
+ const uiLanguage = useUILanguage()
18
20
  // const [editorPhotos, setEditorPhotos] = useState<string[]>([])
19
21
  // const { theme } = useTheme()
20
22
  const axiosPrivate = useAxiosPrivate()
@@ -115,8 +117,16 @@ export default function RichTextFormInput({ input }: { input: RichTextFieldClien
115
117
  menubar: 'file edit view insert format tools table tc help',
116
118
  plugins: plugins,
117
119
  toolbar: toolbar,
118
-
119
- directionality: input.rtl !== undefined ? (input.rtl ? 'rtl' : 'ltr') : (locale?.rtl ? 'rtl' : 'ltr'),
120
+ directionality:
121
+ input.rtl !== undefined
122
+ ? input.rtl
123
+ ? 'rtl'
124
+ : 'ltr'
125
+ : locale
126
+ ? locale.rtl
127
+ ? 'rtl'
128
+ : 'ltr'
129
+ : uiLanguage.dir,
120
130
  content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
121
131
  // skin: theme === 'light' ? 'oxide' : 'oxide-dark',
122
132
  // content_css: theme === 'light' ? 'default' : 'dark',
@@ -10,13 +10,7 @@ import { useSearchParams } from 'next/navigation'
10
10
  import { cn } from '@/lib/utils'
11
11
  import { trpc } from '@/app/_trpc/client'
12
12
 
13
- export default function TagsFormInput({
14
- input,
15
- sectionName,
16
- }: {
17
- input: TagsFieldClientConfig
18
- sectionName: string
19
- }) {
13
+ export default function TagsFormInput({ input, sectionName }: { input: TagsFieldClientConfig; sectionName: string }) {
20
14
  const t = useI18n()
21
15
  const searchParams = useSearchParams()
22
16
  const localeParam = searchParams.get('locale')
@@ -101,16 +95,12 @@ export default function TagsFormInput({
101
95
  if (showDropdown && filteredSuggestions.length > 0) {
102
96
  if (event.key === 'ArrowDown') {
103
97
  event.preventDefault()
104
- setHighlightedIndex((prev) =>
105
- prev < filteredSuggestions.length - 1 ? prev + 1 : 0,
106
- )
98
+ setHighlightedIndex((prev) => (prev < filteredSuggestions.length - 1 ? prev + 1 : 0))
107
99
  return
108
100
  }
109
101
  if (event.key === 'ArrowUp') {
110
102
  event.preventDefault()
111
- setHighlightedIndex((prev) =>
112
- prev > 0 ? prev - 1 : filteredSuggestions.length - 1,
113
- )
103
+ setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : filteredSuggestions.length - 1))
114
104
  return
115
105
  }
116
106
  if (event.key === 'Enter' && highlightedIndex >= 0) {
@@ -180,7 +170,7 @@ export default function TagsFormInput({
180
170
  <div className='relative'>
181
171
  <div
182
172
  className={cn(
183
- 'bg-input text-foreground w-full rounded px-3 py-1 shadow-xs ring-2 ring-gray-300 outline-0 transition-colors hover:ring-gray-400 focus-within:ring-blue-500',
173
+ 'bg-input text-foreground w-full rounded px-3 py-1 shadow-xs ring-2 ring-gray-300 outline-0 transition-colors focus-within:ring-blue-500 hover:ring-gray-400',
184
174
  'flex min-h-9 flex-wrap items-center gap-1 text-sm',
185
175
  'cursor-text',
186
176
  field.disabled && 'cursor-not-allowed opacity-50',
@@ -192,7 +182,7 @@ export default function TagsFormInput({
192
182
  key={`${tag}-${index}`}
193
183
  variant='secondary'
194
184
  className={cn(
195
- 'flex items-center gap-1 px-2 py-1 text-xs transition-all duration-200 border-foreground/40',
185
+ 'border-foreground/40 flex items-center gap-1 px-2 py-1 text-xs transition-all duration-200',
196
186
  highlightedTag === tag ? 'bg-success/20 border-success/50 animate-pulse' : '',
197
187
  )}
198
188
  >
@@ -1,8 +1,8 @@
1
- import React from 'react'
2
1
  import FormInputElement from '@/components/form/form-input-element'
3
2
  import type { TextFieldClientConfig } from 'nextjs-cms/core/fields'
4
3
  import { useFormContext, useController } from 'react-hook-form'
5
4
  import { useLocale } from '@/components/form/content-locale-context'
5
+ import { useUILanguage } from 'nextjs-cms/translations/client'
6
6
 
7
7
  export default function TextFormInput({
8
8
  input,
@@ -23,8 +23,8 @@ export default function TextFormInput({
23
23
  defaultValue: input.value ?? undefined,
24
24
  disabled: disabled,
25
25
  })
26
+ const uiLanguage = useUILanguage()
26
27
  const locale = useLocale()
27
-
28
28
  return (
29
29
  <FormInputElement
30
30
  validationError={error}
@@ -36,7 +36,17 @@ export default function TextFormInput({
36
36
  <input
37
37
  placeholder={input.placeholder ? input.placeholder : input.label}
38
38
  type='text'
39
- dir={input.rtl !== undefined ? (input.rtl ? 'rtl' : 'ltr') : locale?.rtl ? 'rtl' : 'ltr'}
39
+ dir={
40
+ input.rtl !== undefined
41
+ ? input.rtl
42
+ ? 'rtl'
43
+ : 'ltr'
44
+ : locale
45
+ ? locale.rtl
46
+ ? 'rtl'
47
+ : 'ltr'
48
+ : uiLanguage.dir
49
+ }
40
50
  readOnly={disabled}
41
51
  disabled={field.disabled}
42
52
  name={field.name}
@@ -1,8 +1,8 @@
1
- import React from 'react'
2
1
  import FormInputElement from '@/components/form/form-input-element'
3
2
  import { TextAreaFieldClientConfig } from 'nextjs-cms/core/fields'
4
- import { FieldError, useController, useFormContext } from 'react-hook-form'
3
+ import { useController, useFormContext } from 'react-hook-form'
5
4
  import { useLocale } from '@/components/form/content-locale-context'
5
+ import { useUILanguage } from 'nextjs-cms/translations/client'
6
6
 
7
7
  export default function TextareaFormInput({
8
8
  input,
@@ -24,6 +24,7 @@ export default function TextareaFormInput({
24
24
  disabled: input.readonly,
25
25
  })
26
26
  const locale = useLocale()
27
+ const uiLanguage = useUILanguage()
27
28
 
28
29
  return (
29
30
  <FormInputElement
@@ -35,7 +36,17 @@ export default function TextareaFormInput({
35
36
  >
36
37
  <textarea
37
38
  placeholder={input.label}
38
- dir={input.rtl !== undefined ? (input.rtl ? 'rtl' : 'ltr') : (locale?.rtl ? 'rtl' : 'ltr')}
39
+ dir={
40
+ input.rtl !== undefined
41
+ ? input.rtl
42
+ ? 'rtl'
43
+ : 'ltr'
44
+ : locale
45
+ ? locale.rtl
46
+ ? 'rtl'
47
+ : 'ltr'
48
+ : uiLanguage.dir
49
+ }
39
50
  readOnly={input.readonly}
40
51
  disabled={field.disabled}
41
52
  name={field.name}
@@ -43,7 +54,7 @@ export default function TextareaFormInput({
43
54
  onBlur={field.onBlur}
44
55
  value={field.value}
45
56
  ref={field.ref}
46
- className='h-[150px] w-full rounded bg-input p-3 text-foreground shadow-xs outline-0 ring-2 ring-gray-300 hover:ring-gray-400 focus:ring-blue-500'
57
+ className='bg-input text-foreground h-[150px] w-full rounded p-3 shadow-xs ring-2 ring-gray-300 outline-0 hover:ring-gray-400 focus:ring-blue-500'
47
58
  ></textarea>
48
59
  </FormInputElement>
49
60
  )
@@ -88,7 +88,7 @@ export default function VideoFormInput({
88
88
  file={input.value}
89
89
  width={400}
90
90
  height={280}
91
- className='ring-3 mb-4 rounded p-1 ring-gray-400'
91
+ className='mb-4 rounded p-1 ring-3 ring-gray-400'
92
92
  />
93
93
  )
94
94
 
@@ -127,19 +127,22 @@ export default function VideoFormInput({
127
127
  <CardContent className='flex flex-col gap-1'>
128
128
  <div className='mb-2 flex flex-col text-sm'>
129
129
  {input.maxFileSize ? (
130
- <div dir='ltr' className='flex flex-wrap items-center gap-2'>
130
+ <div className='flex flex-wrap items-center gap-2'>
131
131
  <InfoIcon size={14} />
132
132
  <span>{t('maxFileSize')}:</span>
133
133
  <Badge
134
+ dir='ltr'
134
135
  variant={'secondary'}
135
136
  >{`${input.maxFileSize.size} ${input.maxFileSize.unit.toUpperCase()}`}</Badge>
136
137
  </div>
137
138
  ) : null}
138
139
  {input.extensions ? (
139
- <div dir='ltr' className='flex flex-wrap items-center gap-2'>
140
+ <div className='flex flex-wrap items-center gap-2'>
140
141
  <InfoIcon size={14} />
141
142
  <span>{t('allowedExtensions')}:</span>
142
- <Badge variant={'secondary'}>{input.extensions.join(', ')}</Badge>
143
+ <Badge dir='ltr' variant={'secondary'}>
144
+ {input.extensions.join(', ')}
145
+ </Badge>
143
146
  </div>
144
147
  ) : null}
145
148
  </div>
@@ -165,12 +168,12 @@ export default function VideoFormInput({
165
168
  <ChevronRight fontSize='large' />
166
169
  <div className='relative'>
167
170
  <X
168
- className='absolute -right-3 -top-3 z-10 cursor-pointer rounded-full border-2 border-gray-500 bg-white p-1'
171
+ className='absolute -top-3 -right-3 z-10 cursor-pointer rounded-full border-2 border-gray-500 bg-white p-1'
169
172
  onClick={clearSelectedFile}
170
173
  />
171
174
  <embed
172
175
  src={image}
173
- className='ring-3 mb-4 max-w-full rounded p-1 ring-green-600'
176
+ className='mb-4 max-w-full rounded p-1 ring-3 ring-green-600'
174
177
  width={300}
175
178
  height={190}
176
179
  />
@@ -204,7 +207,7 @@ export default function VideoFormInput({
204
207
  <div className='w-[400px] max-w-full'>
205
208
  <button
206
209
  type='button'
207
- className='bg-linear-to-r flex w-full flex-wrap items-center justify-center gap-1.5 rounded border from-red-600 to-amber-500 p-2 text-center text-sm font-bold uppercase text-white drop-shadow-sm hover:from-red-400 hover:to-amber-400'
210
+ className='flex w-full flex-wrap items-center justify-center gap-1.5 rounded border bg-linear-to-r from-red-600 to-amber-500 p-2 text-center text-sm font-bold text-white uppercase drop-shadow-sm hover:from-red-400 hover:to-amber-400'
208
211
  onClick={() => {
209
212
  setModal({
210
213
  title: t('deleteVideo'),
@@ -244,7 +247,7 @@ export default function VideoFormInput({
244
247
  <div className='dark:border-neutral my-2 flex w-[400px] max-w-full flex-col gap-1 rounded-lg border border-gray-400 p-2'>
245
248
  <button
246
249
  type='button'
247
- className='bg-linear-to-r flex w-full flex-wrap items-center justify-center gap-1.5 rounded border from-blue-700 to-sky-500 p-2 text-center text-sm font-bold uppercase text-white drop-shadow-sm hover:from-blue-500 hover:to-sky-400'
250
+ className='flex w-full flex-wrap items-center justify-center gap-1.5 rounded border bg-linear-to-r from-blue-700 to-sky-500 p-2 text-center text-sm font-bold text-white uppercase drop-shadow-sm hover:from-blue-500 hover:to-sky-400'
248
251
  onClick={() => {
249
252
  if (fileInputContainerRef.current?.firstChild) {
250
253
  ;(
@@ -46,25 +46,9 @@ export const TestSectionTable = mysqlTable('test_section', {
46
46
 
47
47
  export const TestTagsTable = mysqlTable('test_tags', {
48
48
  testId: int('test_id').notNull(),
49
- tagName: varchar('tag_name', { length: 255 }).notNull(),
50
- locale: varchar('locale', { length: 255 }).notNull()
49
+ tagName: varchar('tag_name', { length: 255 }).notNull()
51
50
  }, (table) => [
52
- primaryKey({ columns: [table.testId, table.tagName, table.locale] })
53
- ]);
54
-
55
-
56
- export const TestSectionLocalesTable = mysqlTable('test_section_locales', {
57
- id: int('id').autoincrement().notNull().primaryKey(),
58
- parentId: int('parent_id').notNull(),
59
- locale: varchar('locale', { length: 10 }).notNull(),
60
- contentType: mysqlEnum('content_type', ['ad', 'user']).notNull(),
61
- price: int('price'),
62
- appId: varchar('app_id', { length: 36 }).notNull(),
63
- title: varchar('title', { length: 255 }).notNull(),
64
- photo: varchar('photo', { length: 255 }),
65
- category: varchar('category', { length: 255 }).notNull()
66
- }, (table) => [
67
- unique('test_section_locales_parent_id_locale_unique').on(table.parentId, table.locale)
51
+ primaryKey({ columns: [table.testId, table.tagName] })
68
52
  ]);
69
53
 
70
54
 
@@ -12,7 +12,6 @@ export const env = createEnv({
12
12
  CSRF_TOKEN_SECRET: z.string(),
13
13
  ACCESS_TOKEN_EXPIRATION: z.string(),
14
14
  REFRESH_TOKEN_EXPIRATION: z.string(),
15
- NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
16
15
  },
17
16
 
18
17
  /**
@@ -25,9 +24,14 @@ export const env = createEnv({
25
24
  NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: z.string().optional(),
26
25
  },
27
26
 
27
+ shared: {
28
+ NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
29
+ },
30
+
28
31
  experimental__runtimeEnv: {
29
32
  NEXT_PUBLIC_RICHTEXT_INLINE_PUBLIC_URL: process.env.NEXT_PUBLIC_RICHTEXT_INLINE_PUBLIC_URL,
30
33
  NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY,
34
+ NODE_ENV: process.env.NODE_ENV,
31
35
  },
32
36
  /**
33
37
  * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
@@ -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.26",
73
+ "nextjs-cms": "0.9.28",
74
74
  "plaiceholder": "^3.0.0",
75
75
  "prettier-plugin-tailwindcss": "^0.7.2",
76
76
  "qrcode": "^1.5.4",