create-nextjs-cms 0.9.6 → 0.9.8

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 (184) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +71 -71
  3. package/dist/helpers/check-directory.d.ts +1 -0
  4. package/dist/helpers/check-directory.d.ts.map +1 -1
  5. package/dist/helpers/check-directory.js +98 -23
  6. package/dist/helpers/utils.js +16 -16
  7. package/dist/index.js +13 -8
  8. package/dist/lib/create-project.js +1 -1
  9. package/dist/lib/section-creators.js +166 -166
  10. package/package.json +1 -1
  11. package/templates/default/.eslintrc.json +5 -5
  12. package/templates/default/.prettierignore +7 -7
  13. package/templates/default/.prettierrc.json +27 -27
  14. package/templates/default/CHANGELOG.md +140 -140
  15. package/templates/default/_gitignore +57 -57
  16. package/templates/default/app/(auth)/auth/login/LoginPage.tsx +192 -192
  17. package/templates/default/app/(auth)/auth/login/page.tsx +11 -11
  18. package/templates/default/app/(auth)/auth-language-provider.tsx +34 -34
  19. package/templates/default/app/(auth)/layout.tsx +81 -81
  20. package/templates/default/app/(rootLayout)/(plugins)/[...slug]/page.tsx +40 -40
  21. package/templates/default/app/(rootLayout)/(plugins)/[...slug]/plugin-server-registry.ts +16 -16
  22. package/templates/default/app/(rootLayout)/admins/page.tsx +10 -10
  23. package/templates/default/app/(rootLayout)/browse/[section]/[page]/page.tsx +22 -22
  24. package/templates/default/app/(rootLayout)/categorized/[section]/page.tsx +15 -15
  25. package/templates/default/app/(rootLayout)/dashboard/page.tsx +63 -63
  26. package/templates/default/app/(rootLayout)/edit/[section]/[itemId]/page.tsx +20 -20
  27. package/templates/default/app/(rootLayout)/layout.tsx +81 -81
  28. package/templates/default/app/(rootLayout)/loading.tsx +10 -10
  29. package/templates/default/app/(rootLayout)/log/page.tsx +7 -7
  30. package/templates/default/app/(rootLayout)/new/[section]/page.tsx +15 -15
  31. package/templates/default/app/(rootLayout)/section/[section]/page.tsx +19 -19
  32. package/templates/default/app/(rootLayout)/settings/page.tsx +13 -13
  33. package/templates/default/app/_trpc/client.ts +3 -3
  34. package/templates/default/app/api/auth/csrf/route.ts +25 -25
  35. package/templates/default/app/api/auth/refresh/route.ts +10 -10
  36. package/templates/default/app/api/auth/route.ts +49 -49
  37. package/templates/default/app/api/auth/session/route.ts +20 -20
  38. package/templates/default/app/api/document/route.ts +165 -165
  39. package/templates/default/app/api/editor/photo/route.ts +49 -49
  40. package/templates/default/app/api/photo/route.ts +27 -27
  41. package/templates/default/app/api/submit/section/item/[slug]/route.ts +95 -95
  42. package/templates/default/app/api/submit/section/item/route.ts +56 -56
  43. package/templates/default/app/api/submit/section/simple/route.ts +86 -86
  44. package/templates/default/app/api/trpc/[trpc]/route.ts +33 -33
  45. package/templates/default/app/api/video/route.ts +174 -174
  46. package/templates/default/app/globals.css +228 -228
  47. package/templates/default/app/providers.tsx +152 -152
  48. package/templates/default/cms.config.ts +57 -58
  49. package/templates/default/components/AdminCard.tsx +166 -166
  50. package/templates/default/components/AdminEditPage.tsx +124 -124
  51. package/templates/default/components/AdminPrivilegeCard.tsx +185 -185
  52. package/templates/default/components/AdminsPage.tsx +43 -43
  53. package/templates/default/components/AnalyticsPage.tsx +144 -144
  54. package/templates/default/components/BarChartBox.tsx +42 -42
  55. package/templates/default/components/BrowsePage.tsx +106 -106
  56. package/templates/default/components/CategorizedSectionPage.tsx +31 -31
  57. package/templates/default/components/CategoryDeleteConfirmPage.tsx +130 -130
  58. package/templates/default/components/CategorySectionSelectInput.tsx +140 -140
  59. package/templates/default/components/ConditionalFields.tsx +49 -49
  60. package/templates/default/components/ContainerBox.tsx +24 -24
  61. package/templates/default/components/DashboardPageAlt.tsx +45 -45
  62. package/templates/default/components/DefaultNavItems.tsx +3 -3
  63. package/templates/default/components/Dropzone.tsx +154 -154
  64. package/templates/default/components/ErrorComponent.tsx +16 -16
  65. package/templates/default/components/GalleryPhoto.tsx +93 -93
  66. package/templates/default/components/InfoCard.tsx +93 -93
  67. package/templates/default/components/ItemEditPage.tsx +294 -294
  68. package/templates/default/components/Layout.tsx +84 -84
  69. package/templates/default/components/LoadingSpinners.tsx +67 -67
  70. package/templates/default/components/LocaleSwitcher.tsx +89 -89
  71. package/templates/default/components/LogPage.tsx +107 -107
  72. package/templates/default/components/Modal.tsx +166 -166
  73. package/templates/default/components/Navbar.tsx +258 -258
  74. package/templates/default/components/NewAdminForm.tsx +173 -173
  75. package/templates/default/components/NewPage.tsx +206 -206
  76. package/templates/default/components/NewVariantComponent.tsx +229 -229
  77. package/templates/default/components/PhotoGallery.tsx +35 -35
  78. package/templates/default/components/PieChartBox.tsx +101 -101
  79. package/templates/default/components/ProgressBar.tsx +48 -48
  80. package/templates/default/components/ProtectedDocument.tsx +44 -44
  81. package/templates/default/components/ProtectedImage.tsx +143 -143
  82. package/templates/default/components/ProtectedVideo.tsx +76 -76
  83. package/templates/default/components/SectionIcon.tsx +8 -8
  84. package/templates/default/components/SectionItemCard.tsx +144 -144
  85. package/templates/default/components/SectionItemStatusBadge.tsx +17 -17
  86. package/templates/default/components/SectionPage.tsx +205 -205
  87. package/templates/default/components/SelectBox.tsx +98 -98
  88. package/templates/default/components/SelectInputButtons.tsx +125 -125
  89. package/templates/default/components/SettingsPage.tsx +232 -232
  90. package/templates/default/components/Sidebar.tsx +204 -204
  91. package/templates/default/components/SidebarDropdownItem.tsx +83 -83
  92. package/templates/default/components/SidebarItem.tsx +24 -24
  93. package/templates/default/components/ThemeProvider.tsx +8 -8
  94. package/templates/default/components/TooltipComponent.tsx +27 -27
  95. package/templates/default/components/VariantCard.tsx +124 -124
  96. package/templates/default/components/VariantEditPage.tsx +230 -230
  97. package/templates/default/components/analytics/BounceRate.tsx +70 -70
  98. package/templates/default/components/analytics/LivePageViews.tsx +55 -55
  99. package/templates/default/components/analytics/LiveUsersCount.tsx +33 -33
  100. package/templates/default/components/analytics/MonthlyPageViews.tsx +42 -42
  101. package/templates/default/components/analytics/TopCountries.tsx +52 -52
  102. package/templates/default/components/analytics/TopDevices.tsx +46 -46
  103. package/templates/default/components/analytics/TopMediums.tsx +58 -58
  104. package/templates/default/components/analytics/TopSources.tsx +45 -45
  105. package/templates/default/components/analytics/TotalPageViews.tsx +41 -41
  106. package/templates/default/components/analytics/TotalSessions.tsx +41 -41
  107. package/templates/default/components/analytics/TotalUniqueUsers.tsx +41 -41
  108. package/templates/default/components/custom/RightHomeRoomVariantCard.tsx +138 -138
  109. package/templates/default/components/dndKit/Draggable.tsx +21 -21
  110. package/templates/default/components/dndKit/Droppable.tsx +20 -20
  111. package/templates/default/components/dndKit/SortableItem.tsx +18 -18
  112. package/templates/default/components/form/Form.tsx +370 -370
  113. package/templates/default/components/form/FormInputElement.tsx +70 -70
  114. package/templates/default/components/form/FormInputs.tsx +136 -136
  115. package/templates/default/components/form/helpers/_section-hot-reload.js +1 -1
  116. package/templates/default/components/form/helpers/util.ts +17 -17
  117. package/templates/default/components/form/inputs/CheckboxFormInput.tsx +46 -46
  118. package/templates/default/components/form/inputs/ColorFormInput.tsx +44 -44
  119. package/templates/default/components/form/inputs/DateFormInput.tsx +167 -110
  120. package/templates/default/components/form/inputs/DateRangeFormInput.tsx +46 -6
  121. package/templates/default/components/form/inputs/DocumentFormInput.tsx +222 -222
  122. package/templates/default/components/form/inputs/MapFormInput.tsx +140 -140
  123. package/templates/default/components/form/inputs/MultipleSelectFormInput.tsx +85 -85
  124. package/templates/default/components/form/inputs/NumberFormInput.tsx +43 -43
  125. package/templates/default/components/form/inputs/PasswordFormInput.tsx +47 -47
  126. package/templates/default/components/form/inputs/PhotoFormInput.tsx +275 -275
  127. package/templates/default/components/form/inputs/RichTextFormInput.tsx +138 -138
  128. package/templates/default/components/form/inputs/SelectFormInput.tsx +175 -175
  129. package/templates/default/components/form/inputs/SlugFormInput.tsx +131 -131
  130. package/templates/default/components/form/inputs/TagsFormInput.tsx +265 -265
  131. package/templates/default/components/form/inputs/TextFormInput.tsx +51 -51
  132. package/templates/default/components/form/inputs/TextareaFormInput.tsx +50 -50
  133. package/templates/default/components/form/inputs/VideoFormInput.tsx +118 -118
  134. package/templates/default/components/multi-select.tsx +1146 -1146
  135. package/templates/default/components/pagination/Pagination.tsx +36 -36
  136. package/templates/default/components/pagination/PaginationButtons.tsx +147 -147
  137. package/templates/default/components/theme-toggle.tsx +39 -39
  138. package/templates/default/components/ui/accordion.tsx +53 -53
  139. package/templates/default/components/ui/alert-dialog.tsx +157 -157
  140. package/templates/default/components/ui/alert.tsx +47 -47
  141. package/templates/default/components/ui/badge.tsx +38 -38
  142. package/templates/default/components/ui/button.tsx +62 -62
  143. package/templates/default/components/ui/calendar.tsx +166 -166
  144. package/templates/default/components/ui/card.tsx +43 -43
  145. package/templates/default/components/ui/checkbox.tsx +29 -29
  146. package/templates/default/components/ui/command.tsx +137 -137
  147. package/templates/default/components/ui/custom-alert-dialog.tsx +113 -113
  148. package/templates/default/components/ui/custom-dialog.tsx +123 -123
  149. package/templates/default/components/ui/dialog.tsx +123 -123
  150. package/templates/default/components/ui/direction.tsx +22 -22
  151. package/templates/default/components/ui/dropdown-menu.tsx +182 -182
  152. package/templates/default/components/ui/input-group.tsx +54 -54
  153. package/templates/default/components/ui/input.tsx +22 -22
  154. package/templates/default/components/ui/label.tsx +19 -19
  155. package/templates/default/components/ui/popover.tsx +42 -42
  156. package/templates/default/components/ui/progress.tsx +31 -31
  157. package/templates/default/components/ui/scroll-area.tsx +42 -42
  158. package/templates/default/components/ui/select.tsx +165 -165
  159. package/templates/default/components/ui/separator.tsx +28 -28
  160. package/templates/default/components/ui/sheet.tsx +103 -103
  161. package/templates/default/components/ui/spinner.tsx +16 -16
  162. package/templates/default/components/ui/switch.tsx +29 -29
  163. package/templates/default/components/ui/table.tsx +83 -83
  164. package/templates/default/components/ui/tabs.tsx +55 -55
  165. package/templates/default/components/ui/toast.tsx +113 -113
  166. package/templates/default/components/ui/toaster.tsx +35 -35
  167. package/templates/default/components/ui/tooltip.tsx +30 -30
  168. package/templates/default/components/ui/use-toast.ts +188 -188
  169. package/templates/default/components.json +21 -21
  170. package/templates/default/context/ModalProvider.tsx +53 -53
  171. package/templates/default/drizzle.config.ts +4 -4
  172. package/templates/default/dynamic-schemas/schema.ts +475 -0
  173. package/templates/default/env/env.js +130 -130
  174. package/templates/default/envConfig.ts +4 -4
  175. package/templates/default/hooks/useModal.ts +8 -8
  176. package/templates/default/lib/apiHelpers.ts +92 -92
  177. package/templates/default/lib/postinstall.js +14 -14
  178. package/templates/default/lib/utils.ts +6 -6
  179. package/templates/default/next-env.d.ts +6 -6
  180. package/templates/default/next.config.ts +23 -23
  181. package/templates/default/package.json +1 -1
  182. package/templates/default/postcss.config.mjs +6 -6
  183. package/templates/default/proxy.ts +32 -32
  184. package/templates/default/tsconfig.json +48 -48
@@ -1,370 +1,370 @@
1
- export const revalidate = 1
2
-
3
- import ContainerBox from '@/components/ContainerBox'
4
- import { useI18n } from 'nextjs-cms/translations/client'
5
- import FormInputs from '@/components/form/FormInputs'
6
- import Dropzone, { DropzoneHandles } from '@/components/Dropzone'
7
- import NewVariantComponent, { VariantHandles } from '@/components/NewVariantComponent'
8
- import classNames from 'classnames'
9
- import ProgressBar from '@/components/ProgressBar'
10
- import React, { RefObject, useCallback, useEffect } from 'react'
11
- import type { RouterOutputs } from 'nextjs-cms/api'
12
- import * as z from 'zod'
13
- import { zodResolver } from '@hookform/resolvers/zod'
14
- import { useForm, FormProvider } from 'react-hook-form'
15
- import {
16
- CheckboxFieldClientConfig,
17
- ColorFieldClientConfig,
18
- DateFieldClientConfig,
19
- DateRangeFieldClientConfig,
20
- DocumentFieldClientConfig,
21
- MapFieldClientConfig,
22
- NumberFieldClientConfig,
23
- PasswordFieldClientConfig,
24
- PhotoFieldClientConfig,
25
- RichTextFieldClientConfig,
26
- SelectFieldClientConfig,
27
- SelectMultipleFieldClientConfig,
28
- SlugFieldClientConfig,
29
- TextAreaFieldClientConfig,
30
- TextFieldClientConfig,
31
- VideoFieldClientConfig,
32
- } from 'nextjs-cms/core/fields'
33
-
34
- import {
35
- numberFieldSchema,
36
- textFieldSchema,
37
- selectFieldSchema,
38
- selectMultipleFieldSchema,
39
- dateFieldSchema,
40
- dateRangeFieldSchema,
41
- checkboxFieldSchema,
42
- textareaFieldSchema,
43
- richTextFieldSchema,
44
- photoFieldSchema,
45
- documentFieldSchema,
46
- videoFieldSchema,
47
- colorFieldSchema,
48
- mapFieldSchema,
49
- passwordFieldSchema,
50
- slugFieldSchema,
51
- } from 'nextjs-cms/validators'
52
-
53
- import { ConditionalField, FieldType } from 'nextjs-cms/core/types'
54
- import { configLastUpdated } from '@/components/form/helpers/util'
55
- import PhotoGallery from '../PhotoGallery'
56
- import { useSession } from 'nextjs-cms/auth/react'
57
- import { LocalizationProvider } from '@/components/form/ContentLocaleContext'
58
-
59
- export default function Form({
60
- formType,
61
- data,
62
- dropzoneRef,
63
- variantRef,
64
- handleSubmit,
65
- isSubmitting,
66
- response,
67
- progress,
68
- progressVariant,
69
- buttonType = 'big',
70
- submitSuccessCount = 0,
71
- contentLocale,
72
- defaultLocale,
73
- onDirtyChange,
74
- }: {
75
- formType?: 'new' | 'edit'
76
- data:
77
- | RouterOutputs['hasItemsSections']['newItem']
78
- | RouterOutputs['hasItemsSections']['editItem']
79
- | RouterOutputs['simpleSections']['create']
80
- | {
81
- section: {
82
- name: string
83
- gallery?: boolean
84
- title?: { section?: string; singular?: string; plural?: string }
85
- configFile?: string
86
- }
87
- inputGroups:
88
- | {
89
- groupId: number | undefined
90
- groupTitle: string
91
- groupOrder: number
92
- inputs: {
93
- type: FieldType
94
- name: string
95
- label: string
96
- required: boolean
97
- conditionalFields: ConditionalField[]
98
- placeholder?: string
99
- readonly: boolean
100
- value: any
101
- }[]
102
- }[]
103
- | undefined
104
- }
105
- dropzoneRef?: RefObject<DropzoneHandles | null>
106
- variantRef?: RefObject<VariantHandles[]>
107
- handleSubmit: any
108
- isSubmitting: boolean
109
- response?: any
110
- progress?: number
111
- progressVariant?: 'determinate' | 'query'
112
- buttonType?: 'big' | 'small'
113
- submitSuccessCount?: number
114
- contentLocale?: { code: string; label: string; rtl?: boolean }
115
- defaultLocale?: { code: string; label: string; rtl?: boolean }
116
- onDirtyChange?: (isDirty: boolean) => void
117
- }) {
118
- const t = useI18n()
119
- const session = useSession()
120
- const language = session?.data?.user?.language
121
-
122
- // When editing a non-default locale, only show fields marked as localized
123
- const isTranslationMode = !!(contentLocale && defaultLocale && contentLocale.code !== defaultLocale.code)
124
-
125
- const filterInputsForLocale = <T,>(inputs: T[]): T[] => {
126
- if (!isTranslationMode) return inputs
127
- return inputs.filter((input) => (input as any).localized)
128
- }
129
-
130
- const hasNoLocalizedFields =
131
- isTranslationMode &&
132
- (data.inputGroups?.every((g) => filterInputsForLocale(g.inputs as any[]).length === 0) ?? true)
133
-
134
- let schema = z.object({})
135
- /**
136
- * Construct the schema for the form
137
- */
138
- data.inputGroups?.forEach((inputGroup) => {
139
- filterInputsForLocale(inputGroup.inputs as any[]).forEach((input: any) => {
140
- if (input.readonly) return
141
- switch (input.type) {
142
- case 'select_multiple':
143
- schema = schema.extend({
144
- [input.name]: selectMultipleFieldSchema(input as SelectMultipleFieldClientConfig, language),
145
- })
146
- break
147
- case 'select':
148
- schema = schema.extend({
149
- [input.name]: selectFieldSchema(input as SelectFieldClientConfig, language),
150
- })
151
- break
152
-
153
- case 'date':
154
- schema = schema.extend({
155
- [input.name]: dateFieldSchema(input as DateFieldClientConfig, language),
156
- })
157
- break
158
-
159
- case 'date_range':
160
- schema = schema.extend(
161
- dateRangeFieldSchema(input as DateRangeFieldClientConfig, language),
162
- )
163
- break
164
-
165
- case 'checkbox':
166
- schema = schema.extend({
167
- [input.name]: checkboxFieldSchema(input as CheckboxFieldClientConfig, language),
168
- })
169
- break
170
-
171
- case 'text':
172
- schema = schema.extend({
173
- [input.name]: textFieldSchema(input as TextFieldClientConfig, language),
174
- })
175
- break
176
-
177
- case 'textarea':
178
- schema = schema.extend({
179
- [input.name]: textareaFieldSchema(input as TextAreaFieldClientConfig, language),
180
- })
181
- break
182
-
183
- case 'rich_text':
184
- schema = schema.extend({
185
- [input.name]: richTextFieldSchema(input as RichTextFieldClientConfig, language),
186
- })
187
- break
188
-
189
- case 'photo':
190
- if (formType === 'edit' && !!input.value) break
191
- schema = schema.extend({
192
- [input.name]: photoFieldSchema(input as PhotoFieldClientConfig, language),
193
- })
194
- break
195
-
196
- case 'document':
197
- if (formType === 'edit' && !!input.value) break
198
- schema = schema.extend({
199
- [input.name]: documentFieldSchema(input as DocumentFieldClientConfig, language),
200
- })
201
- break
202
-
203
- case 'video':
204
- if (formType === 'edit' && !!input.value) break
205
- schema = schema.extend({
206
- [input.name]: videoFieldSchema(input as VideoFieldClientConfig, language),
207
- })
208
- break
209
-
210
- case 'number':
211
- schema = schema.extend({
212
- [input.name]: numberFieldSchema(input as NumberFieldClientConfig, language),
213
- })
214
- break
215
- case 'color':
216
- schema = schema.extend({
217
- [input.name]: colorFieldSchema(input as ColorFieldClientConfig, language),
218
- })
219
- break
220
-
221
- case 'map':
222
- schema = schema.extend({
223
- [input.name]: mapFieldSchema(input as MapFieldClientConfig, language),
224
- })
225
- break
226
-
227
- case 'password':
228
- schema = schema.extend({
229
- [input.name]: passwordFieldSchema(input as PasswordFieldClientConfig, language),
230
- })
231
- break
232
-
233
- case 'slug':
234
- schema = schema.extend({
235
- [input.name]: slugFieldSchema(input as SlugFieldClientConfig, language),
236
- })
237
- break
238
- }
239
- })
240
- })
241
-
242
- const methods = useForm({
243
- resolver: zodResolver(schema),
244
- })
245
-
246
- const { dirtyFields } = methods.formState
247
- const hasDirtyFields = Object.keys(dirtyFields).length > 0
248
- useEffect(() => {
249
- onDirtyChange?.(hasDirtyFields)
250
- }, [hasDirtyFields, onDirtyChange])
251
-
252
- return (
253
- <LocalizationProvider value={contentLocale ?? null}>
254
- <FormProvider {...methods}>
255
- <form
256
- method='post'
257
- onSubmit={(e) => {
258
- e.preventDefault()
259
- // e.stopPropagation()
260
- methods.handleSubmit((data) => {
261
- handleSubmit(new FormData(e.target as HTMLFormElement))
262
- })(e)
263
- }}
264
- encType='multipart/form-data'
265
- >
266
- <div className='w-full' data-section-schema-version={configLastUpdated}>
267
- {/*<ContainerBox title={formType ? t(formType === 'new' ? 'add_new' : 'edit') : undefined}>*/}
268
- <div className='p-4'>
269
- <div className=''>
270
- {data.inputGroups ? (
271
- <div className='flex flex-col gap-4'>
272
- {hasNoLocalizedFields ? (
273
- <div className='rounded border border-amber-300 bg-amber-50 p-4 text-amber-800 dark:border-amber-700 dark:bg-amber-950 dark:text-amber-300'>
274
- <h3 className='font-semibold'>{t('noLocalizedFields')}</h3>
275
- <p className='mt-1'>
276
- {t('noLocalizedFieldsHint', { section: typeof data.section.title === 'object' ? (data.section.title?.section ?? '') : '', file: data.section.configFile })}
277
- </p>
278
- </div>
279
- ) : (
280
- <>
281
- {data.inputGroups.length > 0
282
- ? data.inputGroups.map((inputGroup, index: number) => {
283
- const filteredInputs = filterInputsForLocale(inputGroup.inputs as any[])
284
- if (filteredInputs.length === 0) return null
285
- return (
286
- <ContainerBox title={inputGroup.groupTitle} key={index}>
287
- <FormInputs
288
- inputs={filteredInputs}
289
- sectionName={data.section.name}
290
- submitSuccessCount={submitSuccessCount}
291
- />
292
- </ContainerBox>
293
- )
294
- })
295
- : null}
296
- {data.section.gallery && !isTranslationMode ? (
297
- <>
298
- <div className='w-full'>
299
- <PhotoGallery sectionName={data.section.name} gallery={'gallery' in data ? data.gallery : []} />
300
- </div>
301
- <div className='w-full'>
302
- <Dropzone ref={dropzoneRef} />
303
- </div>
304
- </>
305
- ) : null}
306
-
307
- {/*{data.section.variants && data.section.variants.length > 0 ? (
308
- <div className='w-full'>
309
- <div className='flex flex-col gap-4'>
310
- {data.section.variants.map((variant, index) => {
311
- // Only one variant is allowed for now
312
- // I have to find a way to make multiple variantRef in order to handle multiple variants
313
- if (index > 0) return
314
-
315
- return (
316
- <NewVariantComponent
317
- ref={(el) => (variantRef.current[index] = el)}
318
- key={index}
319
- section={section}
320
- variantInfo={variant.info}
321
- xsrfToken={xsrfToken}
322
- />
323
- )
324
- })}
325
- </div>
326
- </div>
327
- ) : null}*/}
328
-
329
- <div className='flex flex-col gap-3 pb-4'>
330
- <div className=''>
331
- <button
332
- className={classNames({
333
- 'w-full': buttonType === 'big',
334
- 'float-end': buttonType === 'small',
335
- 'rounded bg-linear-to-r px-4 py-2 font-bold text-white drop-shadow-sm':
336
- true,
337
- 'from-emerald-700 via-green-700 to-green-500 dark:from-blue-800 dark:via-sky-800 dark:to-slate-500':
338
- !isSubmitting,
339
- 'from-gray-600 via-gray-500 to-gray-400': isSubmitting,
340
- })}
341
- type='submit'
342
- disabled={isSubmitting}
343
- >
344
- {isSubmitting
345
- ? t('loading')
346
- : t(formType === 'new' ? 'create' : 'save')}
347
- </button>
348
- {progressVariant && progress ? (
349
- isSubmitting ? (
350
- <div className='mt-0.5'>
351
- <ProgressBar variant={progressVariant} value={progress} />
352
- </div>
353
- ) : null
354
- ) : null}
355
- </div>
356
- {response ? <div className='w-full'>{response}</div> : null}
357
- </div>
358
- </>
359
- )}
360
- </div>
361
- ) : null}
362
- </div>
363
- </div>
364
- {/*</ContainerBox>*/}
365
- </div>
366
- </form>
367
- </FormProvider>
368
- </LocalizationProvider>
369
- )
370
- }
1
+ export const revalidate = 1
2
+
3
+ import ContainerBox from '@/components/ContainerBox'
4
+ import { useI18n } from 'nextjs-cms/translations/client'
5
+ import FormInputs from '@/components/form/FormInputs'
6
+ import Dropzone, { DropzoneHandles } from '@/components/Dropzone'
7
+ import NewVariantComponent, { VariantHandles } from '@/components/NewVariantComponent'
8
+ import classNames from 'classnames'
9
+ import ProgressBar from '@/components/ProgressBar'
10
+ import React, { RefObject, useCallback, useEffect } from 'react'
11
+ import type { RouterOutputs } from 'nextjs-cms/api'
12
+ import * as z from 'zod'
13
+ import { zodResolver } from '@hookform/resolvers/zod'
14
+ import { useForm, FormProvider } from 'react-hook-form'
15
+ import {
16
+ CheckboxFieldClientConfig,
17
+ ColorFieldClientConfig,
18
+ DateFieldClientConfig,
19
+ DateRangeFieldClientConfig,
20
+ DocumentFieldClientConfig,
21
+ MapFieldClientConfig,
22
+ NumberFieldClientConfig,
23
+ PasswordFieldClientConfig,
24
+ PhotoFieldClientConfig,
25
+ RichTextFieldClientConfig,
26
+ SelectFieldClientConfig,
27
+ SelectMultipleFieldClientConfig,
28
+ SlugFieldClientConfig,
29
+ TextAreaFieldClientConfig,
30
+ TextFieldClientConfig,
31
+ VideoFieldClientConfig,
32
+ } from 'nextjs-cms/core/fields'
33
+
34
+ import {
35
+ numberFieldSchema,
36
+ textFieldSchema,
37
+ selectFieldSchema,
38
+ selectMultipleFieldSchema,
39
+ dateFieldSchema,
40
+ dateRangeFieldSchema,
41
+ checkboxFieldSchema,
42
+ textareaFieldSchema,
43
+ richTextFieldSchema,
44
+ photoFieldSchema,
45
+ documentFieldSchema,
46
+ videoFieldSchema,
47
+ colorFieldSchema,
48
+ mapFieldSchema,
49
+ passwordFieldSchema,
50
+ slugFieldSchema,
51
+ } from 'nextjs-cms/validators'
52
+
53
+ import { ConditionalField, FieldType } from 'nextjs-cms/core/types'
54
+ import { configLastUpdated } from '@/components/form/helpers/util'
55
+ import PhotoGallery from '../PhotoGallery'
56
+ import { useSession } from 'nextjs-cms/auth/react'
57
+ import { LocalizationProvider } from '@/components/form/ContentLocaleContext'
58
+
59
+ export default function Form({
60
+ formType,
61
+ data,
62
+ dropzoneRef,
63
+ variantRef,
64
+ handleSubmit,
65
+ isSubmitting,
66
+ response,
67
+ progress,
68
+ progressVariant,
69
+ buttonType = 'big',
70
+ submitSuccessCount = 0,
71
+ contentLocale,
72
+ defaultLocale,
73
+ onDirtyChange,
74
+ }: {
75
+ formType?: 'new' | 'edit'
76
+ data:
77
+ | RouterOutputs['hasItemsSections']['newItem']
78
+ | RouterOutputs['hasItemsSections']['editItem']
79
+ | RouterOutputs['simpleSections']['create']
80
+ | {
81
+ section: {
82
+ name: string
83
+ gallery?: boolean
84
+ title?: { section?: string; singular?: string; plural?: string }
85
+ configFile?: string
86
+ }
87
+ inputGroups:
88
+ | {
89
+ groupId: number | undefined
90
+ groupTitle: string
91
+ groupOrder: number
92
+ inputs: {
93
+ type: FieldType
94
+ name: string
95
+ label: string
96
+ required: boolean
97
+ conditionalFields: ConditionalField[]
98
+ placeholder?: string
99
+ readonly: boolean
100
+ value: any
101
+ }[]
102
+ }[]
103
+ | undefined
104
+ }
105
+ dropzoneRef?: RefObject<DropzoneHandles | null>
106
+ variantRef?: RefObject<VariantHandles[]>
107
+ handleSubmit: any
108
+ isSubmitting: boolean
109
+ response?: any
110
+ progress?: number
111
+ progressVariant?: 'determinate' | 'query'
112
+ buttonType?: 'big' | 'small'
113
+ submitSuccessCount?: number
114
+ contentLocale?: { code: string; label: string; rtl?: boolean }
115
+ defaultLocale?: { code: string; label: string; rtl?: boolean }
116
+ onDirtyChange?: (isDirty: boolean) => void
117
+ }) {
118
+ const t = useI18n()
119
+ const session = useSession()
120
+ const language = session?.data?.user?.language
121
+
122
+ // When editing a non-default locale, only show fields marked as localized
123
+ const isTranslationMode = !!(contentLocale && defaultLocale && contentLocale.code !== defaultLocale.code)
124
+
125
+ const filterInputsForLocale = <T,>(inputs: T[]): T[] => {
126
+ if (!isTranslationMode) return inputs
127
+ return inputs.filter((input) => (input as any).localized)
128
+ }
129
+
130
+ const hasNoLocalizedFields =
131
+ isTranslationMode &&
132
+ (data.inputGroups?.every((g) => filterInputsForLocale(g.inputs as any[]).length === 0) ?? true)
133
+
134
+ let schema = z.object({})
135
+ /**
136
+ * Construct the schema for the form
137
+ */
138
+ data.inputGroups?.forEach((inputGroup) => {
139
+ filterInputsForLocale(inputGroup.inputs as any[]).forEach((input: any) => {
140
+ if (input.readonly) return
141
+ switch (input.type) {
142
+ case 'select_multiple':
143
+ schema = schema.extend({
144
+ [input.name]: selectMultipleFieldSchema(input as SelectMultipleFieldClientConfig, language),
145
+ })
146
+ break
147
+ case 'select':
148
+ schema = schema.extend({
149
+ [input.name]: selectFieldSchema(input as SelectFieldClientConfig, language),
150
+ })
151
+ break
152
+
153
+ case 'date':
154
+ schema = schema.extend({
155
+ [input.name]: dateFieldSchema(input as DateFieldClientConfig, language),
156
+ })
157
+ break
158
+
159
+ case 'date_range':
160
+ schema = schema.extend(
161
+ dateRangeFieldSchema(input as DateRangeFieldClientConfig, language),
162
+ )
163
+ break
164
+
165
+ case 'checkbox':
166
+ schema = schema.extend({
167
+ [input.name]: checkboxFieldSchema(input as CheckboxFieldClientConfig, language),
168
+ })
169
+ break
170
+
171
+ case 'text':
172
+ schema = schema.extend({
173
+ [input.name]: textFieldSchema(input as TextFieldClientConfig, language),
174
+ })
175
+ break
176
+
177
+ case 'textarea':
178
+ schema = schema.extend({
179
+ [input.name]: textareaFieldSchema(input as TextAreaFieldClientConfig, language),
180
+ })
181
+ break
182
+
183
+ case 'rich_text':
184
+ schema = schema.extend({
185
+ [input.name]: richTextFieldSchema(input as RichTextFieldClientConfig, language),
186
+ })
187
+ break
188
+
189
+ case 'photo':
190
+ if (formType === 'edit' && !!input.value) break
191
+ schema = schema.extend({
192
+ [input.name]: photoFieldSchema(input as PhotoFieldClientConfig, language),
193
+ })
194
+ break
195
+
196
+ case 'document':
197
+ if (formType === 'edit' && !!input.value) break
198
+ schema = schema.extend({
199
+ [input.name]: documentFieldSchema(input as DocumentFieldClientConfig, language),
200
+ })
201
+ break
202
+
203
+ case 'video':
204
+ if (formType === 'edit' && !!input.value) break
205
+ schema = schema.extend({
206
+ [input.name]: videoFieldSchema(input as VideoFieldClientConfig, language),
207
+ })
208
+ break
209
+
210
+ case 'number':
211
+ schema = schema.extend({
212
+ [input.name]: numberFieldSchema(input as NumberFieldClientConfig, language),
213
+ })
214
+ break
215
+ case 'color':
216
+ schema = schema.extend({
217
+ [input.name]: colorFieldSchema(input as ColorFieldClientConfig, language),
218
+ })
219
+ break
220
+
221
+ case 'map':
222
+ schema = schema.extend({
223
+ [input.name]: mapFieldSchema(input as MapFieldClientConfig, language),
224
+ })
225
+ break
226
+
227
+ case 'password':
228
+ schema = schema.extend({
229
+ [input.name]: passwordFieldSchema(input as PasswordFieldClientConfig, language),
230
+ })
231
+ break
232
+
233
+ case 'slug':
234
+ schema = schema.extend({
235
+ [input.name]: slugFieldSchema(input as SlugFieldClientConfig, language),
236
+ })
237
+ break
238
+ }
239
+ })
240
+ })
241
+
242
+ const methods = useForm({
243
+ resolver: zodResolver(schema),
244
+ })
245
+
246
+ const { dirtyFields } = methods.formState
247
+ const hasDirtyFields = Object.keys(dirtyFields).length > 0
248
+ useEffect(() => {
249
+ onDirtyChange?.(hasDirtyFields)
250
+ }, [hasDirtyFields, onDirtyChange])
251
+
252
+ return (
253
+ <LocalizationProvider value={contentLocale ?? null}>
254
+ <FormProvider {...methods}>
255
+ <form
256
+ method='post'
257
+ onSubmit={(e) => {
258
+ e.preventDefault()
259
+ // e.stopPropagation()
260
+ methods.handleSubmit((data) => {
261
+ handleSubmit(new FormData(e.target as HTMLFormElement))
262
+ })(e)
263
+ }}
264
+ encType='multipart/form-data'
265
+ >
266
+ <div className='w-full' data-section-schema-version={configLastUpdated}>
267
+ {/*<ContainerBox title={formType ? t(formType === 'new' ? 'add_new' : 'edit') : undefined}>*/}
268
+ <div className='p-4'>
269
+ <div className=''>
270
+ {data.inputGroups ? (
271
+ <div className='flex flex-col gap-4'>
272
+ {hasNoLocalizedFields ? (
273
+ <div className='rounded border border-amber-300 bg-amber-50 p-4 text-amber-800 dark:border-amber-700 dark:bg-amber-950 dark:text-amber-300'>
274
+ <h3 className='font-semibold'>{t('noLocalizedFields')}</h3>
275
+ <p className='mt-1'>
276
+ {t('noLocalizedFieldsHint', { section: typeof data.section.title === 'object' ? (data.section.title?.section ?? '') : '', file: data.section.configFile })}
277
+ </p>
278
+ </div>
279
+ ) : (
280
+ <>
281
+ {data.inputGroups.length > 0
282
+ ? data.inputGroups.map((inputGroup, index: number) => {
283
+ const filteredInputs = filterInputsForLocale(inputGroup.inputs as any[])
284
+ if (filteredInputs.length === 0) return null
285
+ return (
286
+ <ContainerBox title={inputGroup.groupTitle} key={index}>
287
+ <FormInputs
288
+ inputs={filteredInputs}
289
+ sectionName={data.section.name}
290
+ submitSuccessCount={submitSuccessCount}
291
+ />
292
+ </ContainerBox>
293
+ )
294
+ })
295
+ : null}
296
+ {data.section.gallery && !isTranslationMode ? (
297
+ <>
298
+ <div className='w-full'>
299
+ <PhotoGallery sectionName={data.section.name} gallery={'gallery' in data ? data.gallery : []} />
300
+ </div>
301
+ <div className='w-full'>
302
+ <Dropzone ref={dropzoneRef} />
303
+ </div>
304
+ </>
305
+ ) : null}
306
+
307
+ {/*{data.section.variants && data.section.variants.length > 0 ? (
308
+ <div className='w-full'>
309
+ <div className='flex flex-col gap-4'>
310
+ {data.section.variants.map((variant, index) => {
311
+ // Only one variant is allowed for now
312
+ // I have to find a way to make multiple variantRef in order to handle multiple variants
313
+ if (index > 0) return
314
+
315
+ return (
316
+ <NewVariantComponent
317
+ ref={(el) => (variantRef.current[index] = el)}
318
+ key={index}
319
+ section={section}
320
+ variantInfo={variant.info}
321
+ xsrfToken={xsrfToken}
322
+ />
323
+ )
324
+ })}
325
+ </div>
326
+ </div>
327
+ ) : null}*/}
328
+
329
+ <div className='flex flex-col gap-3 pb-4'>
330
+ <div className=''>
331
+ <button
332
+ className={classNames({
333
+ 'w-full': buttonType === 'big',
334
+ 'float-end': buttonType === 'small',
335
+ 'rounded bg-linear-to-r px-4 py-2 font-bold text-white drop-shadow-sm':
336
+ true,
337
+ 'from-emerald-700 via-green-700 to-green-500 dark:from-blue-800 dark:via-sky-800 dark:to-slate-500':
338
+ !isSubmitting,
339
+ 'from-gray-600 via-gray-500 to-gray-400': isSubmitting,
340
+ })}
341
+ type='submit'
342
+ disabled={isSubmitting}
343
+ >
344
+ {isSubmitting
345
+ ? t('loading')
346
+ : t(formType === 'new' ? 'create' : 'save')}
347
+ </button>
348
+ {progressVariant && progress ? (
349
+ isSubmitting ? (
350
+ <div className='mt-0.5'>
351
+ <ProgressBar variant={progressVariant} value={progress} />
352
+ </div>
353
+ ) : null
354
+ ) : null}
355
+ </div>
356
+ {response ? <div className='w-full'>{response}</div> : null}
357
+ </div>
358
+ </>
359
+ )}
360
+ </div>
361
+ ) : null}
362
+ </div>
363
+ </div>
364
+ {/*</ContainerBox>*/}
365
+ </div>
366
+ </form>
367
+ </FormProvider>
368
+ </LocalizationProvider>
369
+ )
370
+ }