create-nextjs-cms 0.5.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.
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +395 -0
- package/dist/lib/utils.d.ts +11 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +48 -0
- package/package.json +44 -0
- package/templates/default/.env +24 -0
- package/templates/default/.env.development +8 -0
- package/templates/default/.eslintrc.json +5 -0
- package/templates/default/.prettierignore +7 -0
- package/templates/default/.prettierrc.json +19 -0
- package/templates/default/CHANGELOG.md +77 -0
- package/templates/default/README.md +45 -0
- package/templates/default/app/(auth)/auth/login/LoginPage.tsx +175 -0
- package/templates/default/app/(auth)/auth/login/page.tsx +12 -0
- package/templates/default/app/(rootLayout)/admins/page.tsx +5 -0
- package/templates/default/app/(rootLayout)/advanced/page.tsx +5 -0
- package/templates/default/app/(rootLayout)/analytics/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/browse/[section]/[page]/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/categorized/[section]/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/dashboard/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/edit/[section]/[itemId]/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/emails/page.tsx +6 -0
- package/templates/default/app/(rootLayout)/layout.tsx +5 -0
- package/templates/default/app/(rootLayout)/loading.tsx +10 -0
- package/templates/default/app/(rootLayout)/log/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/new/[section]/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/page.tsx +9 -0
- package/templates/default/app/(rootLayout)/section/[section]/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/settings/page.tsx +7 -0
- package/templates/default/app/_trpc/client.ts +4 -0
- package/templates/default/app/api/auth/csrf/route.ts +25 -0
- package/templates/default/app/api/auth/refresh/route.ts +10 -0
- package/templates/default/app/api/auth/route.ts +23 -0
- package/templates/default/app/api/auth/session/route.ts +20 -0
- package/templates/default/app/api/editor/photo/route.ts +42 -0
- package/templates/default/app/api/photo/route.ts +27 -0
- package/templates/default/app/api/placeholder/route.ts +7 -0
- package/templates/default/app/api/submit/section/item/[slug]/route.ts +63 -0
- package/templates/default/app/api/submit/section/item/route.ts +53 -0
- package/templates/default/app/api/submit/section/simple/route.ts +54 -0
- package/templates/default/app/api/trpc/[trpc]/route.ts +33 -0
- package/templates/default/app/api/video/route.ts +174 -0
- package/templates/default/app/dictionaries.ts +14 -0
- package/templates/default/app/layout.tsx +28 -0
- package/templates/default/app/providers.tsx +151 -0
- package/templates/default/cli.ts +4 -0
- package/templates/default/components/AdminCard.tsx +163 -0
- package/templates/default/components/AdminEditPage.tsx +123 -0
- package/templates/default/components/AdminPrivilegeCard.tsx +184 -0
- package/templates/default/components/AdminsPage.tsx +43 -0
- package/templates/default/components/AdvancedSettingsPage.tsx +167 -0
- package/templates/default/components/AnalyticsPage.tsx +127 -0
- package/templates/default/components/BarChartBox.tsx +43 -0
- package/templates/default/components/BrowsePage.tsx +119 -0
- package/templates/default/components/CategorizedSectionPage.tsx +36 -0
- package/templates/default/components/CategoryDeleteConfirmPage.tsx +129 -0
- package/templates/default/components/CategorySectionSelectInput.tsx +139 -0
- package/templates/default/components/ConditionalFields.tsx +49 -0
- package/templates/default/components/ContainerBox.tsx +24 -0
- package/templates/default/components/DashboardPage.tsx +187 -0
- package/templates/default/components/DashboardPageAlt.tsx +43 -0
- package/templates/default/components/DefaultNavItems.tsx +3 -0
- package/templates/default/components/Dropzone.tsx +153 -0
- package/templates/default/components/EmailCard.tsx +137 -0
- package/templates/default/components/EmailPasswordForm.tsx +84 -0
- package/templates/default/components/EmailQuotaForm.tsx +72 -0
- package/templates/default/components/EmailsPage.tsx +48 -0
- package/templates/default/components/GalleryPhoto.tsx +93 -0
- package/templates/default/components/InfoCard.tsx +94 -0
- package/templates/default/components/ItemEditPage.tsx +217 -0
- package/templates/default/components/Layout.tsx +70 -0
- package/templates/default/components/LoadingSpinners.tsx +67 -0
- package/templates/default/components/LogPage.tsx +17 -0
- package/templates/default/components/Modal.tsx +99 -0
- package/templates/default/components/Navbar.tsx +29 -0
- package/templates/default/components/NavbarAlt.tsx +182 -0
- package/templates/default/components/NewAdminForm.tsx +172 -0
- package/templates/default/components/NewEmailForm.tsx +131 -0
- package/templates/default/components/NewPage.tsx +206 -0
- package/templates/default/components/NewVariantComponent.tsx +228 -0
- package/templates/default/components/PhotoGallery.tsx +35 -0
- package/templates/default/components/PieChartBox.tsx +101 -0
- package/templates/default/components/ProgressBar.tsx +24 -0
- package/templates/default/components/ProtectedDocument.tsx +78 -0
- package/templates/default/components/ProtectedImage.tsx +143 -0
- package/templates/default/components/ProtectedVideo.tsx +76 -0
- package/templates/default/components/SectionItemCard.tsx +143 -0
- package/templates/default/components/SectionItemStatusBadge.tsx +16 -0
- package/templates/default/components/SectionPage.tsx +124 -0
- package/templates/default/components/SelectBox.tsx +99 -0
- package/templates/default/components/SelectInputButtons.tsx +124 -0
- package/templates/default/components/SettingsPage.tsx +238 -0
- package/templates/default/components/Sidebar.tsx +209 -0
- package/templates/default/components/SidebarDropdownItem.tsx +74 -0
- package/templates/default/components/SidebarItem.tsx +19 -0
- package/templates/default/components/TempPage.tsx +12 -0
- package/templates/default/components/ThemeProvider.tsx +8 -0
- package/templates/default/components/TooltipComponent.tsx +27 -0
- package/templates/default/components/VariantCard.tsx +123 -0
- package/templates/default/components/VariantEditPage.tsx +229 -0
- package/templates/default/components/analytics/BounceRate.tsx +69 -0
- package/templates/default/components/analytics/LivePageViews.tsx +54 -0
- package/templates/default/components/analytics/LiveUsersCount.tsx +32 -0
- package/templates/default/components/analytics/MonthlyPageViews.tsx +41 -0
- package/templates/default/components/analytics/TopCountries.tsx +51 -0
- package/templates/default/components/analytics/TopDevices.tsx +45 -0
- package/templates/default/components/analytics/TopMediums.tsx +57 -0
- package/templates/default/components/analytics/TopSources.tsx +44 -0
- package/templates/default/components/analytics/TotalPageViews.tsx +40 -0
- package/templates/default/components/analytics/TotalSessions.tsx +40 -0
- package/templates/default/components/analytics/TotalUniqueUsers.tsx +40 -0
- package/templates/default/components/custom/RightHomeRoomVariantCard.tsx +137 -0
- package/templates/default/components/dndKit/Draggable.tsx +21 -0
- package/templates/default/components/dndKit/Droppable.tsx +20 -0
- package/templates/default/components/dndKit/SortableItem.tsx +18 -0
- package/templates/default/components/form/DateRangeFormInput.tsx +55 -0
- package/templates/default/components/form/Form.tsx +298 -0
- package/templates/default/components/form/FormInputElement.tsx +68 -0
- package/templates/default/components/form/FormInputs.tsx +108 -0
- package/templates/default/components/form/helpers/util.ts +20 -0
- package/templates/default/components/form/inputs/CheckboxFormInput.tsx +33 -0
- package/templates/default/components/form/inputs/ColorFormInput.tsx +44 -0
- package/templates/default/components/form/inputs/DateFormInput.tsx +107 -0
- package/templates/default/components/form/inputs/DocumentFormInput.tsx +124 -0
- package/templates/default/components/form/inputs/MapFormInput.tsx +139 -0
- package/templates/default/components/form/inputs/MultipleSelectFormInput.tsx +150 -0
- package/templates/default/components/form/inputs/NumberFormInput.tsx +42 -0
- package/templates/default/components/form/inputs/PasswordFormInput.tsx +47 -0
- package/templates/default/components/form/inputs/PhotoFormInput.tsx +218 -0
- package/templates/default/components/form/inputs/RichTextFormInput.tsx +133 -0
- package/templates/default/components/form/inputs/SelectFormInput.tsx +164 -0
- package/templates/default/components/form/inputs/TagsFormInput.tsx +63 -0
- package/templates/default/components/form/inputs/TextFormInput.tsx +48 -0
- package/templates/default/components/form/inputs/TextareaFormInput.tsx +47 -0
- package/templates/default/components/form/inputs/VideoFormInput.tsx +117 -0
- package/templates/default/components/pagination/Pagination.tsx +36 -0
- package/templates/default/components/pagination/PaginationButtons.tsx +145 -0
- package/templates/default/components/ui/accordion.tsx +57 -0
- package/templates/default/components/ui/alert.tsx +46 -0
- package/templates/default/components/ui/badge.tsx +33 -0
- package/templates/default/components/ui/button.tsx +57 -0
- package/templates/default/components/ui/calendar.tsx +68 -0
- package/templates/default/components/ui/card.tsx +76 -0
- package/templates/default/components/ui/checkbox.tsx +29 -0
- package/templates/default/components/ui/dropdown-menu.tsx +205 -0
- package/templates/default/components/ui/input.tsx +25 -0
- package/templates/default/components/ui/label.tsx +26 -0
- package/templates/default/components/ui/popover.tsx +31 -0
- package/templates/default/components/ui/scroll-area.tsx +42 -0
- package/templates/default/components/ui/select.tsx +164 -0
- package/templates/default/components/ui/sheet.tsx +107 -0
- package/templates/default/components/ui/switch.tsx +29 -0
- package/templates/default/components/ui/table.tsx +120 -0
- package/templates/default/components/ui/tabs.tsx +55 -0
- package/templates/default/components/ui/toast.tsx +113 -0
- package/templates/default/components/ui/toaster.tsx +35 -0
- package/templates/default/components/ui/tooltip.tsx +30 -0
- package/templates/default/components/ui/use-toast.ts +188 -0
- package/templates/default/components.json +16 -0
- package/templates/default/context/ModalProvider.tsx +53 -0
- package/templates/default/drizzle.config.ts +4 -0
- package/templates/default/dynamic-schemas/schema.ts +373 -0
- package/templates/default/env/env.js +130 -0
- package/templates/default/envConfig.ts +4 -0
- package/templates/default/hooks/useModal.ts +8 -0
- package/templates/default/lib/apiHelpers.ts +106 -0
- package/templates/default/lz.config.ts +40 -0
- package/templates/default/middleware.ts +33 -0
- package/templates/default/next.config.ts +46 -0
- package/templates/default/package.json +134 -0
- package/templates/default/postcss.config.js +6 -0
- package/templates/default/postinstall.js +14 -0
- package/templates/default/public/blank_avatar.png +0 -0
- package/templates/default/public/favicon.ico +0 -0
- package/templates/default/public/img/placeholder.svg +1 -0
- package/templates/default/public/lazemni_logo.png +0 -0
- package/templates/default/public/next.svg +1 -0
- package/templates/default/public/vercel.svg +1 -0
- package/templates/default/section-tests.ts +92 -0
- package/templates/default/styles/globals.css +88 -0
- package/templates/default/tailwind.config.js +95 -0
- package/templates/default/test.ts +77 -0
- package/templates/default/tsconfig.json +44 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
export const revalidate = 1
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import ContainerBox from '@/components/ContainerBox'
|
|
5
|
+
import getString from 'nextjs-cms/translations'
|
|
6
|
+
import FormInputs from '@/components/form/FormInputs'
|
|
7
|
+
import Dropzone, { DropzoneHandles } from '@/components/Dropzone'
|
|
8
|
+
import NewVariantComponent, { VariantHandles } from '@/components/NewVariantComponent'
|
|
9
|
+
import classNames from 'classnames'
|
|
10
|
+
import ProgressBar from '@/components/ProgressBar'
|
|
11
|
+
import React, { RefObject, useEffect } from 'react'
|
|
12
|
+
import { RouterOutputs } from 'nextjs-cms/api'
|
|
13
|
+
import * as z from 'zod'
|
|
14
|
+
import { zodResolver } from '@hookform/resolvers/zod'
|
|
15
|
+
import { useForm, FormProvider } from 'react-hook-form'
|
|
16
|
+
import {
|
|
17
|
+
CheckboxFieldClientConfig,
|
|
18
|
+
ColorFieldClientConfig,
|
|
19
|
+
DateFieldClientConfig,
|
|
20
|
+
DocumentFieldClientConfig,
|
|
21
|
+
MapFieldClientConfig,
|
|
22
|
+
NumberFieldClientConfig,
|
|
23
|
+
PasswordFieldClientConfig,
|
|
24
|
+
PhotoFieldClientConfig,
|
|
25
|
+
RichTextFieldClientConfig,
|
|
26
|
+
SelectFieldClientConfig,
|
|
27
|
+
SelectMultipleFieldClientConfig,
|
|
28
|
+
TextAreaFieldClientConfig,
|
|
29
|
+
TextFieldClientConfig,
|
|
30
|
+
VideoFieldClientConfig,
|
|
31
|
+
} from 'nextjs-cms/core/fields'
|
|
32
|
+
|
|
33
|
+
import { numberFieldSchema,
|
|
34
|
+
textFieldSchema,
|
|
35
|
+
selectFieldSchema,
|
|
36
|
+
selectMultipleFieldSchema,
|
|
37
|
+
dateFieldSchema,
|
|
38
|
+
checkboxFieldSchema,
|
|
39
|
+
textareaFieldSchema,
|
|
40
|
+
richTextFieldSchema,
|
|
41
|
+
photoFieldSchema,
|
|
42
|
+
documentFieldSchema,
|
|
43
|
+
videoFieldSchema,
|
|
44
|
+
colorFieldSchema,
|
|
45
|
+
mapFieldSchema,
|
|
46
|
+
passwordFieldSchema
|
|
47
|
+
} from 'nextjs-cms/validators'
|
|
48
|
+
|
|
49
|
+
import { ConditionalField, FieldType } from 'nextjs-cms/core/types'
|
|
50
|
+
import { sectionSchemaLastUpdated } from '@/components/form/helpers/util'
|
|
51
|
+
|
|
52
|
+
export default function Form({
|
|
53
|
+
formType,
|
|
54
|
+
data,
|
|
55
|
+
dropzoneRef,
|
|
56
|
+
variantRef,
|
|
57
|
+
handleSubmit,
|
|
58
|
+
isSubmitting,
|
|
59
|
+
response,
|
|
60
|
+
progress,
|
|
61
|
+
progressVariant,
|
|
62
|
+
buttonType = 'big',
|
|
63
|
+
}: {
|
|
64
|
+
formType?: 'new' | 'edit'
|
|
65
|
+
data:
|
|
66
|
+
| RouterOutputs['hasItemsSections']['newItem']
|
|
67
|
+
| RouterOutputs['hasItemsSections']['editItem']
|
|
68
|
+
| RouterOutputs['simpleSections']['create']
|
|
69
|
+
| {
|
|
70
|
+
section: {
|
|
71
|
+
name: string
|
|
72
|
+
gallery?: boolean
|
|
73
|
+
}
|
|
74
|
+
inputGroups:
|
|
75
|
+
| {
|
|
76
|
+
groupId: number | undefined
|
|
77
|
+
groupTitle: string
|
|
78
|
+
groupOrder: number
|
|
79
|
+
inputs: {
|
|
80
|
+
type: FieldType
|
|
81
|
+
name: string
|
|
82
|
+
label: string
|
|
83
|
+
required: boolean
|
|
84
|
+
conditionalFields: ConditionalField[]
|
|
85
|
+
placeholder?: string
|
|
86
|
+
readonly: boolean
|
|
87
|
+
value: any
|
|
88
|
+
}[]
|
|
89
|
+
}[]
|
|
90
|
+
| undefined
|
|
91
|
+
}
|
|
92
|
+
dropzoneRef?: RefObject<DropzoneHandles | null>
|
|
93
|
+
variantRef?: RefObject<VariantHandles[]>
|
|
94
|
+
handleSubmit: any
|
|
95
|
+
isSubmitting: boolean
|
|
96
|
+
response?: any
|
|
97
|
+
progress?: number
|
|
98
|
+
progressVariant?: 'determinate' | 'query'
|
|
99
|
+
buttonType?: 'big' | 'small'
|
|
100
|
+
}) {
|
|
101
|
+
let schema = z.object({})
|
|
102
|
+
/**
|
|
103
|
+
* Construct the schema for the form
|
|
104
|
+
*/
|
|
105
|
+
data.inputGroups?.forEach((inputGroup) => {
|
|
106
|
+
inputGroup.inputs.forEach((input) => {
|
|
107
|
+
if (input.readonly) return
|
|
108
|
+
switch (input.type) {
|
|
109
|
+
case 'select_multiple':
|
|
110
|
+
schema = schema.extend({
|
|
111
|
+
[input.name]: selectMultipleFieldSchema(input as SelectMultipleFieldClientConfig),
|
|
112
|
+
})
|
|
113
|
+
break
|
|
114
|
+
case 'select':
|
|
115
|
+
schema = schema.extend({
|
|
116
|
+
[input.name]: selectFieldSchema(input as SelectFieldClientConfig),
|
|
117
|
+
})
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
case 'date':
|
|
121
|
+
schema = schema.extend({
|
|
122
|
+
[input.name]: dateFieldSchema(input as DateFieldClientConfig),
|
|
123
|
+
})
|
|
124
|
+
break
|
|
125
|
+
|
|
126
|
+
case 'checkbox':
|
|
127
|
+
schema = schema.extend({
|
|
128
|
+
[input.name]: checkboxFieldSchema(input as CheckboxFieldClientConfig),
|
|
129
|
+
})
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
case 'text':
|
|
133
|
+
schema = schema.extend({
|
|
134
|
+
[input.name]: textFieldSchema(input as TextFieldClientConfig),
|
|
135
|
+
})
|
|
136
|
+
break
|
|
137
|
+
|
|
138
|
+
case 'textarea':
|
|
139
|
+
schema = schema.extend({
|
|
140
|
+
[input.name]: textareaFieldSchema(input as TextAreaFieldClientConfig),
|
|
141
|
+
})
|
|
142
|
+
break
|
|
143
|
+
|
|
144
|
+
case 'rich_text':
|
|
145
|
+
schema = schema.extend({
|
|
146
|
+
[input.name]: richTextFieldSchema(input as RichTextFieldClientConfig),
|
|
147
|
+
})
|
|
148
|
+
break
|
|
149
|
+
|
|
150
|
+
case 'photo':
|
|
151
|
+
if (formType === 'edit' && !!input.value) break
|
|
152
|
+
schema = schema.extend({
|
|
153
|
+
[input.name]: photoFieldSchema(input as PhotoFieldClientConfig),
|
|
154
|
+
})
|
|
155
|
+
break
|
|
156
|
+
|
|
157
|
+
case 'document':
|
|
158
|
+
if (formType === 'edit' && !!input.value) break
|
|
159
|
+
schema = schema.extend({
|
|
160
|
+
[input.name]: documentFieldSchema(input as DocumentFieldClientConfig),
|
|
161
|
+
})
|
|
162
|
+
break
|
|
163
|
+
|
|
164
|
+
case 'video':
|
|
165
|
+
if (formType === 'edit' && !!input.value) break
|
|
166
|
+
schema = schema.extend({
|
|
167
|
+
[input.name]: videoFieldSchema(input as VideoFieldClientConfig),
|
|
168
|
+
})
|
|
169
|
+
break
|
|
170
|
+
|
|
171
|
+
case 'number':
|
|
172
|
+
schema = schema.extend({
|
|
173
|
+
[input.name]: numberFieldSchema(input as NumberFieldClientConfig),
|
|
174
|
+
})
|
|
175
|
+
break
|
|
176
|
+
case 'color':
|
|
177
|
+
schema = schema.extend({
|
|
178
|
+
[input.name]: colorFieldSchema(input as ColorFieldClientConfig),
|
|
179
|
+
})
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
case 'map':
|
|
183
|
+
schema = schema.extend({
|
|
184
|
+
[input.name]: mapFieldSchema(input as MapFieldClientConfig),
|
|
185
|
+
})
|
|
186
|
+
break
|
|
187
|
+
|
|
188
|
+
case 'password':
|
|
189
|
+
schema = schema.extend({
|
|
190
|
+
[input.name]: passwordFieldSchema(input as PasswordFieldClientConfig),
|
|
191
|
+
})
|
|
192
|
+
break
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
const methods = useForm({
|
|
198
|
+
resolver: zodResolver(schema),
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<FormProvider {...methods}>
|
|
203
|
+
<form
|
|
204
|
+
method='post'
|
|
205
|
+
onSubmit={(e) => {
|
|
206
|
+
e.preventDefault()
|
|
207
|
+
// e.stopPropagation()
|
|
208
|
+
methods.handleSubmit((data) => {
|
|
209
|
+
handleSubmit(new FormData(e.target as HTMLFormElement))
|
|
210
|
+
})(e)
|
|
211
|
+
}}
|
|
212
|
+
encType='multipart/form-data'
|
|
213
|
+
>
|
|
214
|
+
<div className='w-full' data-section-schema-version={sectionSchemaLastUpdated}>
|
|
215
|
+
{/*<ContainerBox title={formType ? getString(formType === 'new' ? 'add_new' : 'edit') : undefined}>*/}
|
|
216
|
+
<div className='p-4'>
|
|
217
|
+
<div className=''>
|
|
218
|
+
{data.inputGroups ? (
|
|
219
|
+
<div className='flex flex-col gap-4'>
|
|
220
|
+
{data.inputGroups.length > 0
|
|
221
|
+
? data.inputGroups.map((inputGroup, index: number) => {
|
|
222
|
+
return (
|
|
223
|
+
<ContainerBox title={inputGroup.groupTitle} key={index}>
|
|
224
|
+
<FormInputs
|
|
225
|
+
inputs={inputGroup.inputs}
|
|
226
|
+
sectionName={data.section.name}
|
|
227
|
+
/>
|
|
228
|
+
</ContainerBox>
|
|
229
|
+
)
|
|
230
|
+
})
|
|
231
|
+
: null}
|
|
232
|
+
{data.section.gallery ? (
|
|
233
|
+
<div className='w-full'>
|
|
234
|
+
<Dropzone ref={dropzoneRef} />
|
|
235
|
+
</div>
|
|
236
|
+
) : null}
|
|
237
|
+
|
|
238
|
+
{/*{data.section.variants && data.section.variants.length > 0 ? (
|
|
239
|
+
<div className='w-full'>
|
|
240
|
+
<div className='flex flex-col gap-4'>
|
|
241
|
+
{data.section.variants.map((variant, index) => {
|
|
242
|
+
// Only one variant is allowed for now
|
|
243
|
+
// I have to find a way to make multiple variantRef in order to handle multiple variants
|
|
244
|
+
if (index > 0) return
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<NewVariantComponent
|
|
248
|
+
ref={(el) => (variantRef.current[index] = el)}
|
|
249
|
+
key={index}
|
|
250
|
+
section={section}
|
|
251
|
+
variantInfo={variant.info}
|
|
252
|
+
xsrfToken={xsrfToken}
|
|
253
|
+
/>
|
|
254
|
+
)
|
|
255
|
+
})}
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
) : null}*/}
|
|
259
|
+
|
|
260
|
+
<div className='flex flex-col gap-3 pb-4'>
|
|
261
|
+
<div className=''>
|
|
262
|
+
<button
|
|
263
|
+
className={classNames({
|
|
264
|
+
'w-full': buttonType === 'big',
|
|
265
|
+
'float-end': buttonType === 'small',
|
|
266
|
+
'rounded bg-gradient-to-r px-4 py-2 font-bold text-white drop-shadow':
|
|
267
|
+
true,
|
|
268
|
+
'from-emerald-700 via-green-700 to-green-500 dark:from-blue-800 dark:via-sky-800 dark:to-slate-500':
|
|
269
|
+
!isSubmitting,
|
|
270
|
+
'from-gray-600 via-gray-500 to-gray-400': isSubmitting,
|
|
271
|
+
})}
|
|
272
|
+
type='submit'
|
|
273
|
+
disabled={isSubmitting}
|
|
274
|
+
>
|
|
275
|
+
{isSubmitting
|
|
276
|
+
? getString('loading')
|
|
277
|
+
: getString(formType === 'new' ? 'create' : 'save')}
|
|
278
|
+
</button>
|
|
279
|
+
{progressVariant && progress ? (
|
|
280
|
+
isSubmitting ? (
|
|
281
|
+
<div className='mt-0.5'>
|
|
282
|
+
<ProgressBar variant={progressVariant} value={progress} />
|
|
283
|
+
</div>
|
|
284
|
+
) : null
|
|
285
|
+
) : null}
|
|
286
|
+
</div>
|
|
287
|
+
{response ? <div className='w-full'>{response}</div> : null}
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
) : null}
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
{/*</ContainerBox>*/}
|
|
294
|
+
</div>
|
|
295
|
+
</form>
|
|
296
|
+
</FormProvider>
|
|
297
|
+
)
|
|
298
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
import { Badge } from '@/components/ui/badge'
|
|
4
|
+
import { FieldError } from 'react-hook-form'
|
|
5
|
+
|
|
6
|
+
export default function FormInputElement({
|
|
7
|
+
label,
|
|
8
|
+
required = false,
|
|
9
|
+
readonly = false,
|
|
10
|
+
classes = '',
|
|
11
|
+
direction = 'row',
|
|
12
|
+
value,
|
|
13
|
+
children,
|
|
14
|
+
validationError,
|
|
15
|
+
}: {
|
|
16
|
+
label: string
|
|
17
|
+
required: boolean | string
|
|
18
|
+
readonly?: boolean
|
|
19
|
+
classes?: string
|
|
20
|
+
direction?: 'row' | 'col'
|
|
21
|
+
value?: any
|
|
22
|
+
children: React.ReactNode
|
|
23
|
+
validationError: FieldError | undefined
|
|
24
|
+
}) {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
className={classNames({
|
|
28
|
+
'items-center py-2': true,
|
|
29
|
+
'flex flex-col': direction === 'col',
|
|
30
|
+
'items-center px-3 md-sidebar:grid md-sidebar:grid-cols-10': direction === 'row',
|
|
31
|
+
classes: classes,
|
|
32
|
+
})}
|
|
33
|
+
>
|
|
34
|
+
<label
|
|
35
|
+
className={classNames({
|
|
36
|
+
'block pe-3 font-bold text-foreground': true,
|
|
37
|
+
'w-full text-start': direction === 'col',
|
|
38
|
+
'col-span-10 pb-1 md-sidebar:col-span-2 md-sidebar:pb-0 md-sidebar:text-end': direction === 'row',
|
|
39
|
+
})}
|
|
40
|
+
>
|
|
41
|
+
{label} {['true', 1, true].includes(required) ? '*' : null}
|
|
42
|
+
{readonly ? (
|
|
43
|
+
<>
|
|
44
|
+
{' '}
|
|
45
|
+
<Badge variant={'primary'}>Readonly</Badge>
|
|
46
|
+
</>
|
|
47
|
+
) : null}
|
|
48
|
+
</label>
|
|
49
|
+
<div
|
|
50
|
+
className={classNames({
|
|
51
|
+
'col-span-8': direction === 'row',
|
|
52
|
+
'w-full': direction === 'col',
|
|
53
|
+
})}
|
|
54
|
+
>
|
|
55
|
+
{readonly ? (
|
|
56
|
+
<div className='w-full rounded bg-input p-3 text-foreground shadow-sm outline-0'>{value}</div>
|
|
57
|
+
) : (
|
|
58
|
+
<>
|
|
59
|
+
{children}
|
|
60
|
+
{validationError ? (
|
|
61
|
+
<div className='mt-1 text-sm text-red-500'>{validationError.message}</div>
|
|
62
|
+
) : null}
|
|
63
|
+
</>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import ColorFormInput from '@/components/form/inputs/ColorFormInput'
|
|
3
|
+
import NumberFormInput from '@/components/form/inputs/NumberFormInput'
|
|
4
|
+
import TextFormInput from '@/components/form/inputs/TextFormInput'
|
|
5
|
+
import DateFormInput from '@/components/form/inputs/DateFormInput'
|
|
6
|
+
import RichTextFormInput from '@/components/form/inputs/RichTextFormInput'
|
|
7
|
+
import SelectFormInput from '@/components/form/inputs/SelectFormInput'
|
|
8
|
+
import MultipleSelectFormInput from '@/components/form/inputs/MultipleSelectFormInput'
|
|
9
|
+
import PhotoFormInput from '@/components/form/inputs/PhotoFormInput'
|
|
10
|
+
import DocumentFormInput from '@/components/form/inputs/DocumentFormInput'
|
|
11
|
+
import TagsFormInput from '@/components/form/inputs/TagsFormInput'
|
|
12
|
+
import CheckboxFormInput from '@/components/form/inputs/CheckboxFormInput'
|
|
13
|
+
import MapFormInput from '@/components/form/inputs/MapFormInput'
|
|
14
|
+
import PasswordFormInput from '@/components/form/inputs/PasswordFormInput'
|
|
15
|
+
import TextareaFormInput from '@/components/form/inputs/TextareaFormInput'
|
|
16
|
+
import VideoFormInput from '@/components/form/inputs/VideoFormInput'
|
|
17
|
+
import type { FieldClientConfig } from 'nextjs-cms/core/fields'
|
|
18
|
+
import {
|
|
19
|
+
CheckboxFieldClientConfig,
|
|
20
|
+
ColorFieldClientConfig,
|
|
21
|
+
DateFieldClientConfig,
|
|
22
|
+
DocumentFieldClientConfig,
|
|
23
|
+
MapFieldClientConfig,
|
|
24
|
+
NumberFieldClientConfig,
|
|
25
|
+
PasswordFieldClientConfig,
|
|
26
|
+
PhotoFieldClientConfig,
|
|
27
|
+
RichTextFieldClientConfig,
|
|
28
|
+
SelectFieldClientConfig,
|
|
29
|
+
SelectMultipleFieldClientConfig,
|
|
30
|
+
TagsFieldClientConfig,
|
|
31
|
+
TextAreaFieldClientConfig,
|
|
32
|
+
TextFieldClientConfig,
|
|
33
|
+
VideoFieldClientConfig,
|
|
34
|
+
} from 'nextjs-cms/core/fields'
|
|
35
|
+
|
|
36
|
+
export default function FormInputs({ inputs, sectionName }: { inputs: FieldClientConfig[]; sectionName: string }) {
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
{inputs?.length > 0 &&
|
|
40
|
+
inputs?.map((input) => {
|
|
41
|
+
switch (input.type) {
|
|
42
|
+
case 'color':
|
|
43
|
+
return <ColorFormInput input={input as ColorFieldClientConfig} key={input.name} />
|
|
44
|
+
case 'number':
|
|
45
|
+
return <NumberFormInput input={input as NumberFieldClientConfig} key={input.name} />
|
|
46
|
+
case 'text':
|
|
47
|
+
return <TextFormInput input={input as TextFieldClientConfig} key={input.name} />
|
|
48
|
+
case 'date':
|
|
49
|
+
return <DateFormInput input={input as DateFieldClientConfig} key={input.name} />
|
|
50
|
+
case 'rich_text':
|
|
51
|
+
return <RichTextFormInput input={input as RichTextFieldClientConfig} key={input.name} />
|
|
52
|
+
case 'textarea':
|
|
53
|
+
return <TextareaFormInput input={input as TextAreaFieldClientConfig} key={input.name} />
|
|
54
|
+
case 'select':
|
|
55
|
+
return (
|
|
56
|
+
<SelectFormInput
|
|
57
|
+
sectionName={sectionName}
|
|
58
|
+
input={input as SelectFieldClientConfig}
|
|
59
|
+
key={input.name}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
case 'select_multiple':
|
|
63
|
+
return (
|
|
64
|
+
<MultipleSelectFormInput
|
|
65
|
+
input={input as SelectMultipleFieldClientConfig}
|
|
66
|
+
key={input.name}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
case 'photo':
|
|
70
|
+
return (
|
|
71
|
+
<PhotoFormInput
|
|
72
|
+
sectionName={sectionName}
|
|
73
|
+
input={input as PhotoFieldClientConfig}
|
|
74
|
+
key={input.name}
|
|
75
|
+
/>
|
|
76
|
+
)
|
|
77
|
+
case 'video':
|
|
78
|
+
return (
|
|
79
|
+
<VideoFormInput
|
|
80
|
+
sectionName={sectionName}
|
|
81
|
+
input={input as VideoFieldClientConfig}
|
|
82
|
+
key={input.name}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
case 'document':
|
|
86
|
+
return (
|
|
87
|
+
<DocumentFormInput
|
|
88
|
+
sectionName={sectionName}
|
|
89
|
+
input={input as DocumentFieldClientConfig}
|
|
90
|
+
key={input.name}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
case 'password':
|
|
94
|
+
return <PasswordFormInput input={input as PasswordFieldClientConfig} key={input.name} />
|
|
95
|
+
case 'tags':
|
|
96
|
+
return <TagsFormInput input={input as TagsFieldClientConfig} key={input.name} />
|
|
97
|
+
case 'map':
|
|
98
|
+
return <MapFormInput input={input as MapFieldClientConfig} key={input.name} />
|
|
99
|
+
case 'checkbox':
|
|
100
|
+
// case 'permission':
|
|
101
|
+
return <CheckboxFormInput input={input as CheckboxFieldClientConfig} key={input.name} />
|
|
102
|
+
default:
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
})}
|
|
106
|
+
</>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Safe import wrapper for sectionSchemaLastUpdated
|
|
2
|
+
// This prevents errors if the auto-generated file is deleted by end-users
|
|
3
|
+
// The file is auto-generated by SectionFactory and can be safely deleted
|
|
4
|
+
// without breaking the application - it will fall back to current timestamp
|
|
5
|
+
|
|
6
|
+
let sectionSchemaLastUpdated: number
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
// Try to import the auto-generated file from root
|
|
10
|
+
// Using dynamic require to handle missing file gracefully
|
|
11
|
+
// @/ alias resolves to the app root directory
|
|
12
|
+
const sectionInfo = require('@/next-cms-sectioninfo')
|
|
13
|
+
sectionSchemaLastUpdated = sectionInfo?.sectionSchemaLastUpdated ?? Date.now()
|
|
14
|
+
} catch {
|
|
15
|
+
// If file doesn't exist or can't be loaded, use current timestamp as fallback
|
|
16
|
+
// This ensures the app continues to work even if the file is deleted
|
|
17
|
+
sectionSchemaLastUpdated = Date.now()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { sectionSchemaLastUpdated }
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import FormInputElement from '@/components/form/FormInputElement'
|
|
3
|
+
import { CheckboxFieldClientConfig } from 'nextjs-cms/core/fields'
|
|
4
|
+
import { useController, useFormContext } from 'react-hook-form'
|
|
5
|
+
import { Switch } from '@/components/ui/switch'
|
|
6
|
+
|
|
7
|
+
export default function CheckboxFormInput({ input }: { input: CheckboxFieldClientConfig }) {
|
|
8
|
+
const { control } = useFormContext()
|
|
9
|
+
const {
|
|
10
|
+
field,
|
|
11
|
+
fieldState: { invalid, isTouched, isDirty, error },
|
|
12
|
+
} = useController({
|
|
13
|
+
name: input.name,
|
|
14
|
+
control,
|
|
15
|
+
defaultValue: input.value === 1,
|
|
16
|
+
})
|
|
17
|
+
return (
|
|
18
|
+
<FormInputElement
|
|
19
|
+
validationError={error}
|
|
20
|
+
value={input.value}
|
|
21
|
+
readonly={input.readonly}
|
|
22
|
+
label={input.label}
|
|
23
|
+
required={input.required}
|
|
24
|
+
>
|
|
25
|
+
<Switch
|
|
26
|
+
ref={field.ref}
|
|
27
|
+
onCheckedChange={field.onChange}
|
|
28
|
+
defaultChecked={input.value === 1}
|
|
29
|
+
name={field.name}
|
|
30
|
+
/>
|
|
31
|
+
</FormInputElement>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import FormInputElement from '@/components/form/FormInputElement'
|
|
3
|
+
import { ColorFieldClientConfig } from 'nextjs-cms/core/fields'
|
|
4
|
+
import { useController, useFormContext } from 'react-hook-form'
|
|
5
|
+
|
|
6
|
+
export default function ColorFormInput({ input }: { input: ColorFieldClientConfig }) {
|
|
7
|
+
const { control } = useFormContext()
|
|
8
|
+
const {
|
|
9
|
+
field,
|
|
10
|
+
fieldState: { invalid, isTouched, isDirty, error },
|
|
11
|
+
} = useController({
|
|
12
|
+
name: input.name,
|
|
13
|
+
control,
|
|
14
|
+
defaultValue: input.value ?? '#000000',
|
|
15
|
+
disabled: input.readonly,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const colorSpan = (
|
|
19
|
+
<>
|
|
20
|
+
<span style={{ padding: '2px 40px', backgroundColor: input.value }}></span>
|
|
21
|
+
</>
|
|
22
|
+
)
|
|
23
|
+
return (
|
|
24
|
+
<FormInputElement
|
|
25
|
+
validationError={error}
|
|
26
|
+
value={colorSpan}
|
|
27
|
+
readonly={input.readonly}
|
|
28
|
+
label={input.label}
|
|
29
|
+
required={input.required}
|
|
30
|
+
>
|
|
31
|
+
<input
|
|
32
|
+
type='color'
|
|
33
|
+
readOnly={input.readonly}
|
|
34
|
+
disabled={field.disabled}
|
|
35
|
+
name={field.name}
|
|
36
|
+
onChange={field.onChange}
|
|
37
|
+
onBlur={field.onBlur}
|
|
38
|
+
value={field.value}
|
|
39
|
+
ref={field.ref}
|
|
40
|
+
className='h-10 w-full rounded bg-input p-1 text-foreground shadow-sm outline-0 ring-2 ring-blue-300 focus:ring-blue-500'
|
|
41
|
+
/>
|
|
42
|
+
</FormInputElement>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import dayjs from 'dayjs'
|
|
3
|
+
import FormInputElement from '@/components/form/FormInputElement'
|
|
4
|
+
import { format } from 'date-fns'
|
|
5
|
+
import { cn } from 'nextjs-cms/utils'
|
|
6
|
+
import { Button } from '@/components/ui/button'
|
|
7
|
+
import { Calendar } from '@/components/ui/calendar'
|
|
8
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
|
9
|
+
import { CalendarIcon } from '@radix-ui/react-icons'
|
|
10
|
+
import getString from 'nextjs-cms/translations'
|
|
11
|
+
import { DateFieldClientConfig } from 'nextjs-cms/core/fields'
|
|
12
|
+
import { useController, useFormContext } from 'react-hook-form'
|
|
13
|
+
|
|
14
|
+
const seasonEmoji: Record<string, string> = {
|
|
15
|
+
winter: '⛄️',
|
|
16
|
+
spring: '🌸',
|
|
17
|
+
summer: '🌻',
|
|
18
|
+
autumn: '🍂',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const getSeason = (month: Date): string => {
|
|
22
|
+
const monthNumber = month.getMonth()
|
|
23
|
+
if (monthNumber >= 0 && monthNumber < 3) return 'winter'
|
|
24
|
+
if (monthNumber >= 3 && monthNumber < 6) return 'spring'
|
|
25
|
+
if (monthNumber >= 6 && monthNumber < 9) return 'summer'
|
|
26
|
+
else return 'autumn'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const formatCaption = (month: Date, options?: any) => {
|
|
30
|
+
const season = getSeason(month)
|
|
31
|
+
return seasonEmoji[season] + ' ' + format(month, 'LLLL', { locale: options?.locale })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default function DateFormInput({ input }: { input: DateFieldClientConfig }) {
|
|
35
|
+
const { control } = useFormContext()
|
|
36
|
+
const {
|
|
37
|
+
field,
|
|
38
|
+
fieldState: { invalid, isTouched, isDirty, error },
|
|
39
|
+
} = useController({
|
|
40
|
+
name: input.name,
|
|
41
|
+
control,
|
|
42
|
+
// defaultValue: input.value ?? undefined,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const [date, setDate] = React.useState<Date | undefined>(
|
|
46
|
+
dayjs(input.value).isValid() ? dayjs(input.value).toDate() : undefined,
|
|
47
|
+
)
|
|
48
|
+
return (
|
|
49
|
+
<FormInputElement
|
|
50
|
+
validationError={error}
|
|
51
|
+
value={input.value}
|
|
52
|
+
readonly={input.readonly}
|
|
53
|
+
label={input.label}
|
|
54
|
+
required={input.required}
|
|
55
|
+
>
|
|
56
|
+
<input
|
|
57
|
+
type='hidden'
|
|
58
|
+
disabled={field.disabled}
|
|
59
|
+
name={field.name}
|
|
60
|
+
onBlur={field.onBlur}
|
|
61
|
+
value={date ? format(date, 'yyyy-MM-dd') : undefined}
|
|
62
|
+
ref={field.ref}
|
|
63
|
+
className='rounded border p-2'
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
<div className='flex flex-col items-start gap-2'>
|
|
67
|
+
<Popover>
|
|
68
|
+
<PopoverTrigger asChild>
|
|
69
|
+
<Button
|
|
70
|
+
variant={'outline'}
|
|
71
|
+
className={cn(
|
|
72
|
+
'min-w-[250px] justify-start rounded bg-input px-3 py-6 text-start font-normal text-foreground shadow-sm outline-0 ring-2 ring-gray-300 hover:ring-gray-400 focus:ring-blue-500',
|
|
73
|
+
!date && 'text-muted-foreground',
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
76
|
+
<CalendarIcon className='mr-2 h-4 w-4' />
|
|
77
|
+
{date ? format(date, 'PPP') : <span>Pick a date</span>}
|
|
78
|
+
</Button>
|
|
79
|
+
</PopoverTrigger>
|
|
80
|
+
<PopoverContent className='w-auto p-0'>
|
|
81
|
+
<Calendar
|
|
82
|
+
mode='single'
|
|
83
|
+
selected={date}
|
|
84
|
+
onSelect={(date) => {
|
|
85
|
+
setDate(date)
|
|
86
|
+
field.onChange(date)
|
|
87
|
+
}}
|
|
88
|
+
initialFocus
|
|
89
|
+
formatters={{ formatCaption }}
|
|
90
|
+
/>
|
|
91
|
+
</PopoverContent>
|
|
92
|
+
</Popover>
|
|
93
|
+
<Button
|
|
94
|
+
onClick={() => {
|
|
95
|
+
setDate(undefined)
|
|
96
|
+
field.onChange(undefined)
|
|
97
|
+
}}
|
|
98
|
+
variant='default'
|
|
99
|
+
type='button'
|
|
100
|
+
size='sm'
|
|
101
|
+
>
|
|
102
|
+
{getString('cancel')}
|
|
103
|
+
</Button>
|
|
104
|
+
</div>
|
|
105
|
+
</FormInputElement>
|
|
106
|
+
)
|
|
107
|
+
}
|