create-nextjs-cms 0.9.4 → 0.9.6
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 -21
- package/README.md +71 -71
- package/dist/helpers/utils.js +16 -16
- package/dist/lib/section-creators.js +166 -166
- package/package.json +3 -3
- package/templates/default/.eslintrc.json +5 -5
- package/templates/default/.prettierignore +7 -7
- package/templates/default/.prettierrc.json +27 -27
- package/templates/default/CHANGELOG.md +140 -140
- package/templates/default/_gitignore +57 -57
- package/templates/default/app/(auth)/auth/login/LoginPage.tsx +192 -192
- package/templates/default/app/(auth)/auth/login/page.tsx +11 -11
- package/templates/default/app/(auth)/layout.tsx +81 -81
- package/templates/default/app/(rootLayout)/(plugins)/[...slug]/page.tsx +40 -40
- package/templates/default/app/(rootLayout)/(plugins)/[...slug]/plugin-server-registry.ts +16 -16
- package/templates/default/app/(rootLayout)/admins/page.tsx +10 -10
- package/templates/default/app/(rootLayout)/browse/[section]/[page]/page.tsx +22 -22
- package/templates/default/app/(rootLayout)/categorized/[section]/page.tsx +15 -15
- package/templates/default/app/(rootLayout)/dashboard/page.tsx +63 -63
- package/templates/default/app/(rootLayout)/edit/[section]/[itemId]/page.tsx +20 -20
- package/templates/default/app/(rootLayout)/layout.tsx +81 -81
- package/templates/default/app/(rootLayout)/loading.tsx +10 -10
- package/templates/default/app/(rootLayout)/log/page.tsx +7 -7
- package/templates/default/app/(rootLayout)/new/[section]/page.tsx +15 -15
- package/templates/default/app/(rootLayout)/section/[section]/page.tsx +19 -19
- package/templates/default/app/(rootLayout)/settings/page.tsx +13 -13
- package/templates/default/app/_trpc/client.ts +3 -3
- package/templates/default/app/api/auth/csrf/route.ts +25 -25
- package/templates/default/app/api/auth/refresh/route.ts +10 -10
- package/templates/default/app/api/auth/route.ts +49 -49
- package/templates/default/app/api/auth/session/route.ts +20 -20
- package/templates/default/app/api/document/route.ts +165 -165
- package/templates/default/app/api/editor/photo/route.ts +49 -49
- package/templates/default/app/api/photo/route.ts +27 -27
- package/templates/default/app/api/submit/section/item/[slug]/route.ts +95 -95
- package/templates/default/app/api/submit/section/item/route.ts +56 -56
- package/templates/default/app/api/submit/section/simple/route.ts +86 -86
- package/templates/default/app/api/trpc/[trpc]/route.ts +33 -33
- package/templates/default/app/api/video/route.ts +174 -174
- package/templates/default/app/globals.css +228 -228
- package/templates/default/app/providers.tsx +152 -152
- package/templates/default/cms.config.ts +58 -57
- package/templates/default/components/AdminCard.tsx +166 -166
- package/templates/default/components/AdminEditPage.tsx +124 -124
- package/templates/default/components/AdminPrivilegeCard.tsx +185 -185
- package/templates/default/components/AdminsPage.tsx +43 -43
- package/templates/default/components/AnalyticsPage.tsx +144 -128
- package/templates/default/components/BarChartBox.tsx +42 -42
- package/templates/default/components/BrowsePage.tsx +106 -106
- package/templates/default/components/CategorizedSectionPage.tsx +31 -31
- package/templates/default/components/CategoryDeleteConfirmPage.tsx +130 -130
- package/templates/default/components/CategorySectionSelectInput.tsx +140 -140
- package/templates/default/components/ConditionalFields.tsx +49 -49
- package/templates/default/components/ContainerBox.tsx +24 -24
- package/templates/default/components/DashboardPageAlt.tsx +45 -45
- package/templates/default/components/DefaultNavItems.tsx +3 -3
- package/templates/default/components/Dropzone.tsx +154 -154
- package/templates/default/components/ErrorComponent.tsx +16 -16
- package/templates/default/components/GalleryPhoto.tsx +93 -93
- package/templates/default/components/InfoCard.tsx +93 -93
- package/templates/default/components/ItemEditPage.tsx +294 -294
- package/templates/default/components/Layout.tsx +84 -84
- package/templates/default/components/LoadingSpinners.tsx +67 -67
- package/templates/default/components/LocaleSwitcher.tsx +89 -89
- package/templates/default/components/LogPage.tsx +107 -107
- package/templates/default/components/Modal.tsx +166 -166
- package/templates/default/components/Navbar.tsx +258 -258
- package/templates/default/components/NewAdminForm.tsx +173 -173
- package/templates/default/components/NewPage.tsx +206 -206
- package/templates/default/components/NewVariantComponent.tsx +229 -229
- package/templates/default/components/PhotoGallery.tsx +35 -35
- package/templates/default/components/PieChartBox.tsx +101 -101
- package/templates/default/components/ProgressBar.tsx +48 -48
- package/templates/default/components/ProtectedDocument.tsx +44 -44
- package/templates/default/components/ProtectedImage.tsx +143 -143
- package/templates/default/components/ProtectedVideo.tsx +76 -76
- package/templates/default/components/SectionIcon.tsx +8 -8
- package/templates/default/components/SectionItemCard.tsx +144 -144
- package/templates/default/components/SectionItemStatusBadge.tsx +17 -17
- package/templates/default/components/SectionPage.tsx +205 -205
- package/templates/default/components/SelectBox.tsx +98 -98
- package/templates/default/components/SelectInputButtons.tsx +125 -125
- package/templates/default/components/SettingsPage.tsx +232 -232
- package/templates/default/components/Sidebar.tsx +204 -204
- package/templates/default/components/SidebarDropdownItem.tsx +83 -83
- package/templates/default/components/SidebarItem.tsx +24 -24
- package/templates/default/components/ThemeProvider.tsx +8 -8
- package/templates/default/components/TooltipComponent.tsx +27 -27
- package/templates/default/components/VariantCard.tsx +124 -124
- package/templates/default/components/VariantEditPage.tsx +230 -230
- package/templates/default/components/analytics/BounceRate.tsx +70 -70
- package/templates/default/components/analytics/LivePageViews.tsx +55 -55
- package/templates/default/components/analytics/LiveUsersCount.tsx +33 -33
- package/templates/default/components/analytics/MonthlyPageViews.tsx +42 -42
- package/templates/default/components/analytics/TopCountries.tsx +52 -52
- package/templates/default/components/analytics/TopDevices.tsx +46 -46
- package/templates/default/components/analytics/TopMediums.tsx +58 -58
- package/templates/default/components/analytics/TopSources.tsx +45 -45
- package/templates/default/components/analytics/TotalPageViews.tsx +41 -41
- package/templates/default/components/analytics/TotalSessions.tsx +41 -41
- package/templates/default/components/analytics/TotalUniqueUsers.tsx +41 -41
- package/templates/default/components/custom/RightHomeRoomVariantCard.tsx +138 -138
- package/templates/default/components/dndKit/Draggable.tsx +21 -21
- package/templates/default/components/dndKit/Droppable.tsx +20 -20
- package/templates/default/components/dndKit/SortableItem.tsx +18 -18
- package/templates/default/components/form/Form.tsx +370 -360
- package/templates/default/components/form/FormInputElement.tsx +70 -70
- package/templates/default/components/form/FormInputs.tsx +120 -111
- package/templates/default/components/form/helpers/_section-hot-reload.js +1 -1
- package/templates/default/components/form/helpers/util.ts +17 -17
- package/templates/default/components/form/inputs/CheckboxFormInput.tsx +46 -46
- package/templates/default/components/form/inputs/ColorFormInput.tsx +44 -44
- package/templates/default/components/form/inputs/DateFormInput.tsx +156 -156
- package/templates/default/components/form/inputs/DateRangeFormInput.tsx +153 -0
- package/templates/default/components/form/inputs/DocumentFormInput.tsx +222 -222
- package/templates/default/components/form/inputs/MapFormInput.tsx +140 -140
- package/templates/default/components/form/inputs/MultipleSelectFormInput.tsx +85 -85
- package/templates/default/components/form/inputs/NumberFormInput.tsx +43 -43
- package/templates/default/components/form/inputs/PasswordFormInput.tsx +47 -47
- package/templates/default/components/form/inputs/PhotoFormInput.tsx +275 -275
- package/templates/default/components/form/inputs/RichTextFormInput.tsx +138 -138
- package/templates/default/components/form/inputs/SelectFormInput.tsx +175 -175
- package/templates/default/components/form/inputs/SlugFormInput.tsx +131 -131
- package/templates/default/components/form/inputs/TagsFormInput.tsx +261 -260
- package/templates/default/components/form/inputs/TextFormInput.tsx +51 -51
- package/templates/default/components/form/inputs/TextareaFormInput.tsx +50 -50
- package/templates/default/components/form/inputs/VideoFormInput.tsx +118 -118
- package/templates/default/components/multi-select.tsx +1146 -1146
- package/templates/default/components/pagination/Pagination.tsx +36 -36
- package/templates/default/components/pagination/PaginationButtons.tsx +147 -147
- package/templates/default/components/theme-toggle.tsx +39 -39
- package/templates/default/components/ui/accordion.tsx +53 -53
- package/templates/default/components/ui/alert-dialog.tsx +157 -157
- package/templates/default/components/ui/alert.tsx +47 -47
- package/templates/default/components/ui/badge.tsx +38 -38
- package/templates/default/components/ui/button.tsx +62 -62
- package/templates/default/components/ui/calendar.tsx +166 -166
- package/templates/default/components/ui/card.tsx +43 -43
- package/templates/default/components/ui/checkbox.tsx +29 -29
- package/templates/default/components/ui/command.tsx +137 -137
- package/templates/default/components/ui/custom-alert-dialog.tsx +113 -113
- package/templates/default/components/ui/custom-dialog.tsx +123 -123
- package/templates/default/components/ui/dialog.tsx +123 -123
- package/templates/default/components/ui/direction.tsx +22 -22
- package/templates/default/components/ui/dropdown-menu.tsx +182 -182
- package/templates/default/components/ui/input-group.tsx +54 -54
- package/templates/default/components/ui/input.tsx +22 -22
- package/templates/default/components/ui/label.tsx +19 -19
- package/templates/default/components/ui/popover.tsx +42 -42
- package/templates/default/components/ui/progress.tsx +31 -31
- package/templates/default/components/ui/scroll-area.tsx +42 -42
- package/templates/default/components/ui/select.tsx +165 -165
- package/templates/default/components/ui/separator.tsx +28 -28
- package/templates/default/components/ui/sheet.tsx +103 -103
- package/templates/default/components/ui/spinner.tsx +16 -16
- package/templates/default/components/ui/switch.tsx +29 -29
- package/templates/default/components/ui/table.tsx +83 -83
- package/templates/default/components/ui/tabs.tsx +55 -55
- package/templates/default/components/ui/toast.tsx +113 -113
- package/templates/default/components/ui/toaster.tsx +35 -35
- package/templates/default/components/ui/tooltip.tsx +30 -30
- package/templates/default/components/ui/use-toast.ts +188 -188
- package/templates/default/components.json +21 -21
- package/templates/default/context/ModalProvider.tsx +53 -53
- package/templates/default/drizzle.config.ts +4 -4
- package/templates/default/env/env.js +130 -130
- package/templates/default/envConfig.ts +4 -4
- package/templates/default/hooks/useModal.ts +8 -8
- package/templates/default/lib/apiHelpers.ts +92 -92
- package/templates/default/lib/postinstall.js +14 -14
- package/templates/default/lib/utils.ts +6 -6
- package/templates/default/next-env.d.ts +6 -6
- package/templates/default/next.config.ts +23 -23
- package/templates/default/package.json +3 -3
- package/templates/default/postcss.config.mjs +6 -6
- package/templates/default/proxy.ts +32 -32
- package/templates/default/tsconfig.json +48 -48
- package/templates/default/app/(rootLayout)/dashboard-new/page.tsx +0 -7
- package/templates/default/components/DashboardNewPage.tsx +0 -253
- package/templates/default/components/DashboardPage.tsx +0 -188
- package/templates/default/components/EmailCard.tsx +0 -138
- package/templates/default/components/EmailPasswordForm.tsx +0 -85
- package/templates/default/components/EmailQuotaForm.tsx +0 -73
- package/templates/default/components/EmailsPage.tsx +0 -49
- package/templates/default/components/NewEmailForm.tsx +0 -132
- package/templates/default/components/form/DateRangeFormInput.tsx +0 -57
- package/templates/default/dynamic-schemas/schema.ts +0 -475
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import { FieldClientConfig } from 'nextjs-cms/core/fields'
|
|
2
|
-
import React, { useEffect, useState } from 'react'
|
|
3
|
-
import FormInputs from '@/components/form/FormInputs'
|
|
4
|
-
import { ConditionalField } from 'nextjs-cms/core/types'
|
|
5
|
-
|
|
6
|
-
export function ConditionalFields({
|
|
7
|
-
sectionName,
|
|
8
|
-
conditionalFields,
|
|
9
|
-
value,
|
|
10
|
-
}: {
|
|
11
|
-
sectionName: string
|
|
12
|
-
conditionalFields: ConditionalField[]
|
|
13
|
-
value: string | number | undefined
|
|
14
|
-
}) {
|
|
15
|
-
const [conditionalInputs, setConditionalInputs] = useState<FieldClientConfig[]>([])
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
if (!value) setConditionalInputs([])
|
|
19
|
-
// Check if the value is in the conditional inputs
|
|
20
|
-
const _conditionalFields: FieldClientConfig[] = []
|
|
21
|
-
conditionalFields.filter((conditionalField) => {
|
|
22
|
-
switch (conditionalField.rule.condition) {
|
|
23
|
-
case 'equals':
|
|
24
|
-
if (value?.toString() === conditionalField.rule.value.toString()) {
|
|
25
|
-
// Add the input to the conditional inputs
|
|
26
|
-
_conditionalFields.push(conditionalField.field)
|
|
27
|
-
}
|
|
28
|
-
break
|
|
29
|
-
case 'notEquals':
|
|
30
|
-
if (value?.toString() !== conditionalField.rule.value.toString()) {
|
|
31
|
-
// Add the input to the conditional inputs
|
|
32
|
-
_conditionalFields.push(conditionalField.field)
|
|
33
|
-
}
|
|
34
|
-
break
|
|
35
|
-
default:
|
|
36
|
-
return null
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
if (_conditionalFields && _conditionalFields.length > 0) {
|
|
40
|
-
setConditionalInputs(_conditionalFields)
|
|
41
|
-
} else {
|
|
42
|
-
setConditionalInputs([])
|
|
43
|
-
}
|
|
44
|
-
}, [value])
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<>{conditionalInputs.length > 0 ? <FormInputs inputs={conditionalInputs} sectionName={sectionName} /> : null}</>
|
|
48
|
-
)
|
|
49
|
-
}
|
|
1
|
+
import { FieldClientConfig } from 'nextjs-cms/core/fields'
|
|
2
|
+
import React, { useEffect, useState } from 'react'
|
|
3
|
+
import FormInputs from '@/components/form/FormInputs'
|
|
4
|
+
import { ConditionalField } from 'nextjs-cms/core/types'
|
|
5
|
+
|
|
6
|
+
export function ConditionalFields({
|
|
7
|
+
sectionName,
|
|
8
|
+
conditionalFields,
|
|
9
|
+
value,
|
|
10
|
+
}: {
|
|
11
|
+
sectionName: string
|
|
12
|
+
conditionalFields: ConditionalField[]
|
|
13
|
+
value: string | number | undefined
|
|
14
|
+
}) {
|
|
15
|
+
const [conditionalInputs, setConditionalInputs] = useState<FieldClientConfig[]>([])
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!value) setConditionalInputs([])
|
|
19
|
+
// Check if the value is in the conditional inputs
|
|
20
|
+
const _conditionalFields: FieldClientConfig[] = []
|
|
21
|
+
conditionalFields.filter((conditionalField) => {
|
|
22
|
+
switch (conditionalField.rule.condition) {
|
|
23
|
+
case 'equals':
|
|
24
|
+
if (value?.toString() === conditionalField.rule.value.toString()) {
|
|
25
|
+
// Add the input to the conditional inputs
|
|
26
|
+
_conditionalFields.push(conditionalField.field)
|
|
27
|
+
}
|
|
28
|
+
break
|
|
29
|
+
case 'notEquals':
|
|
30
|
+
if (value?.toString() !== conditionalField.rule.value.toString()) {
|
|
31
|
+
// Add the input to the conditional inputs
|
|
32
|
+
_conditionalFields.push(conditionalField.field)
|
|
33
|
+
}
|
|
34
|
+
break
|
|
35
|
+
default:
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
if (_conditionalFields && _conditionalFields.length > 0) {
|
|
40
|
+
setConditionalInputs(_conditionalFields)
|
|
41
|
+
} else {
|
|
42
|
+
setConditionalInputs([])
|
|
43
|
+
}
|
|
44
|
+
}, [value])
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<>{conditionalInputs.length > 0 ? <FormInputs inputs={conditionalInputs} sectionName={sectionName} /> : null}</>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
// import { Card, CardContent, CardHeader, CardTitle } from './ui/card'
|
|
3
|
-
|
|
4
|
-
export default function ContainerBox({ children, title }: { children: React.ReactNode; title?: string }) {
|
|
5
|
-
return (
|
|
6
|
-
<>
|
|
7
|
-
{/*<Card>
|
|
8
|
-
<CardHeader>
|
|
9
|
-
<CardTitle>{title}</CardTitle>
|
|
10
|
-
</CardHeader>
|
|
11
|
-
<CardContent>{children}</CardContent>
|
|
12
|
-
</Card>*/}
|
|
13
|
-
|
|
14
|
-
<div className='relative flex flex-col rounded-lg border border-slate-300 bg-white shadow-xs dark:border-slate-500 dark:bg-slate-900'>
|
|
15
|
-
{title ? (
|
|
16
|
-
<div className='rounded-t-lg px-4 py-1.5 font-bold text-black dark:bg-slate-800 dark:text-white'>
|
|
17
|
-
{title}
|
|
18
|
-
</div>
|
|
19
|
-
) : null}
|
|
20
|
-
<div className='w-full px-4 py-2'>{children}</div>
|
|
21
|
-
</div>
|
|
22
|
-
</>
|
|
23
|
-
)
|
|
24
|
-
}
|
|
1
|
+
import React from 'react'
|
|
2
|
+
// import { Card, CardContent, CardHeader, CardTitle } from './ui/card'
|
|
3
|
+
|
|
4
|
+
export default function ContainerBox({ children, title }: { children: React.ReactNode; title?: string }) {
|
|
5
|
+
return (
|
|
6
|
+
<>
|
|
7
|
+
{/*<Card>
|
|
8
|
+
<CardHeader>
|
|
9
|
+
<CardTitle>{title}</CardTitle>
|
|
10
|
+
</CardHeader>
|
|
11
|
+
<CardContent>{children}</CardContent>
|
|
12
|
+
</Card>*/}
|
|
13
|
+
|
|
14
|
+
<div className='relative flex flex-col rounded-lg border border-slate-300 bg-white shadow-xs dark:border-slate-500 dark:bg-slate-900'>
|
|
15
|
+
{title ? (
|
|
16
|
+
<div className='rounded-t-lg px-4 py-1.5 font-bold text-black dark:bg-slate-800 dark:text-white'>
|
|
17
|
+
{title}
|
|
18
|
+
</div>
|
|
19
|
+
) : null}
|
|
20
|
+
<div className='w-full px-4 py-2'>{children}</div>
|
|
21
|
+
</div>
|
|
22
|
+
</>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { Metadata } from 'next'
|
|
2
|
-
import Image from 'next/image'
|
|
3
|
-
import { useI18n } from 'nextjs-cms/translations/client'
|
|
4
|
-
|
|
5
|
-
export const metadata: Metadata = {
|
|
6
|
-
title: 'Dashboard',
|
|
7
|
-
description: 'Example dashboard app built using the components.',
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export default function DashboardPage() {
|
|
11
|
-
const t = useI18n()
|
|
12
|
-
return (
|
|
13
|
-
<>
|
|
14
|
-
<div className='md:hidden'>
|
|
15
|
-
<Image
|
|
16
|
-
src='/examples/dashboard-light.png'
|
|
17
|
-
width={1280}
|
|
18
|
-
height={866}
|
|
19
|
-
alt={t('dashboard') as string}
|
|
20
|
-
className='block dark:hidden'
|
|
21
|
-
/>
|
|
22
|
-
<Image
|
|
23
|
-
src='/examples/dashboard-dark.png'
|
|
24
|
-
width={1280}
|
|
25
|
-
height={866}
|
|
26
|
-
alt={t('dashboard') as string}
|
|
27
|
-
className='hidden dark:block'
|
|
28
|
-
/>
|
|
29
|
-
</div>
|
|
30
|
-
<div className='hidden flex-col md:flex'>
|
|
31
|
-
<div className='border-b'>
|
|
32
|
-
<div className='flex h-16 items-center px-4'>
|
|
33
|
-
<div className='ml-auto flex items-center space-x-4'></div>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
<div className='flex-1 space-y-4 p-8 pt-6'>
|
|
37
|
-
<div className='flex items-center justify-between space-y-2'>
|
|
38
|
-
<h2 className='text-3xl font-bold tracking-tight'>{t('dashboard')}</h2>
|
|
39
|
-
<div className='flex items-center space-x-2'></div>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
</>
|
|
44
|
-
)
|
|
45
|
-
}
|
|
1
|
+
import { Metadata } from 'next'
|
|
2
|
+
import Image from 'next/image'
|
|
3
|
+
import { useI18n } from 'nextjs-cms/translations/client'
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: 'Dashboard',
|
|
7
|
+
description: 'Example dashboard app built using the components.',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function DashboardPage() {
|
|
11
|
+
const t = useI18n()
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
<div className='md:hidden'>
|
|
15
|
+
<Image
|
|
16
|
+
src='/examples/dashboard-light.png'
|
|
17
|
+
width={1280}
|
|
18
|
+
height={866}
|
|
19
|
+
alt={t('dashboard') as string}
|
|
20
|
+
className='block dark:hidden'
|
|
21
|
+
/>
|
|
22
|
+
<Image
|
|
23
|
+
src='/examples/dashboard-dark.png'
|
|
24
|
+
width={1280}
|
|
25
|
+
height={866}
|
|
26
|
+
alt={t('dashboard') as string}
|
|
27
|
+
className='hidden dark:block'
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
<div className='hidden flex-col md:flex'>
|
|
31
|
+
<div className='border-b'>
|
|
32
|
+
<div className='flex h-16 items-center px-4'>
|
|
33
|
+
<div className='ml-auto flex items-center space-x-4'></div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
<div className='flex-1 space-y-4 p-8 pt-6'>
|
|
37
|
+
<div className='flex items-center justify-between space-y-2'>
|
|
38
|
+
<h2 className='text-3xl font-bold tracking-tight'>{t('dashboard')}</h2>
|
|
39
|
+
<div className='flex items-center space-x-2'></div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { NavItem } from 'nextjs-cms/core/types'
|
|
2
|
-
|
|
3
|
-
export const DefaultNavItems: NavItem[] = []
|
|
1
|
+
import { NavItem } from 'nextjs-cms/core/types'
|
|
2
|
+
|
|
3
|
+
export const DefaultNavItems: NavItem[] = []
|
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
import React, { CSSProperties, forwardRef, Ref, useEffect, useImperativeHandle, useState } from 'react'
|
|
2
|
-
import { DropEvent, FileRejection, useDropzone } from 'react-dropzone'
|
|
3
|
-
import { DropzoneFile } from 'nextjs-cms/core/types'
|
|
4
|
-
import ContainerBox from '@/components/ContainerBox'
|
|
5
|
-
import { useI18n } from 'nextjs-cms/translations/client'
|
|
6
|
-
import { MinusIcon } from '@radix-ui/react-icons'
|
|
7
|
-
import { useToast } from '@/components/ui/use-toast'
|
|
8
|
-
|
|
9
|
-
const thumb: CSSProperties = {
|
|
10
|
-
display: 'inline-flex',
|
|
11
|
-
borderRadius: 2,
|
|
12
|
-
border: '1px solid #eaeaea',
|
|
13
|
-
marginBottom: 8,
|
|
14
|
-
marginRight: 8,
|
|
15
|
-
width: 100,
|
|
16
|
-
height: 100,
|
|
17
|
-
padding: 4,
|
|
18
|
-
boxSizing: 'border-box',
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const thumbInner: CSSProperties = {
|
|
22
|
-
display: 'flex',
|
|
23
|
-
minWidth: 0,
|
|
24
|
-
overflow: 'hidden',
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const img: CSSProperties = {
|
|
28
|
-
display: 'block',
|
|
29
|
-
width: 'auto',
|
|
30
|
-
height: '100%',
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface DropzoneHandles {
|
|
34
|
-
getFiles: () => File[]
|
|
35
|
-
removeFiles: () => void
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function Dropzone(props: any, ref: Ref<DropzoneHandles>) {
|
|
39
|
-
const t = useI18n()
|
|
40
|
-
const acceptedFileTypes = {
|
|
41
|
-
'image/png': ['.png'],
|
|
42
|
-
'image/jpeg': ['.jpg', '.jpeg'],
|
|
43
|
-
'image/webp': ['.webp'],
|
|
44
|
-
}
|
|
45
|
-
const [files, setFiles] = useState<DropzoneFile[]>([])
|
|
46
|
-
const { toast } = useToast()
|
|
47
|
-
const { getRootProps, getInputProps } = useDropzone({
|
|
48
|
-
accept: acceptedFileTypes,
|
|
49
|
-
maxFiles: 10,
|
|
50
|
-
onDrop: (acceptedFiles: File[]) => {
|
|
51
|
-
// First, check if the file is already in the list
|
|
52
|
-
const filteredFiles = acceptedFiles.filter((file) => !files.some((prevFile) => prevFile.name === file.name))
|
|
53
|
-
setFiles((prevFiles) => [
|
|
54
|
-
...prevFiles,
|
|
55
|
-
...filteredFiles.map((file: File) =>
|
|
56
|
-
Object.assign(file, {
|
|
57
|
-
preview: URL.createObjectURL(file),
|
|
58
|
-
}),
|
|
59
|
-
),
|
|
60
|
-
])
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
onDropRejected: (rejectedFiles: FileRejection[], e: DropEvent) => {
|
|
64
|
-
toast({
|
|
65
|
-
variant: 'destructive',
|
|
66
|
-
title: t('deleteGalleryPhoto'),
|
|
67
|
-
description: rejectedFiles[0]?.errors[0]?.message
|
|
68
|
-
? rejectedFiles[0].errors[0].message
|
|
69
|
-
: t('error'),
|
|
70
|
-
})
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
onError: (error: any) => {
|
|
74
|
-
toast({
|
|
75
|
-
variant: 'destructive',
|
|
76
|
-
title: t('deleteGalleryPhoto'),
|
|
77
|
-
description: error.displayName,
|
|
78
|
-
})
|
|
79
|
-
},
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
useImperativeHandle(ref, () => ({
|
|
83
|
-
getFiles: () => files,
|
|
84
|
-
removeFiles: () => {
|
|
85
|
-
files.forEach((file: any) => URL.revokeObjectURL(file.preview))
|
|
86
|
-
setFiles([])
|
|
87
|
-
},
|
|
88
|
-
}))
|
|
89
|
-
|
|
90
|
-
const thumbs = files.map((file: DropzoneFile, index: number) => (
|
|
91
|
-
<div style={thumb} key={`${file.name}-${index}`} className='relative'>
|
|
92
|
-
{/* Delete Button */}
|
|
93
|
-
<button
|
|
94
|
-
type='button'
|
|
95
|
-
className='absolute -end-1 -top-1 rounded-full bg-red-500 p-1'
|
|
96
|
-
onClick={() => {
|
|
97
|
-
URL.revokeObjectURL(file.preview)
|
|
98
|
-
setFiles((prevFiles) =>
|
|
99
|
-
prevFiles.filter((prevFile) => {
|
|
100
|
-
if (prevFile.name !== file.name) {
|
|
101
|
-
// Create a new preview url for the file
|
|
102
|
-
prevFile.preview = URL.createObjectURL(prevFile)
|
|
103
|
-
return prevFile
|
|
104
|
-
}
|
|
105
|
-
}),
|
|
106
|
-
)
|
|
107
|
-
}}
|
|
108
|
-
>
|
|
109
|
-
<MinusIcon className='text-white' />
|
|
110
|
-
</button>
|
|
111
|
-
<div style={thumbInner}>
|
|
112
|
-
<img
|
|
113
|
-
alt={file.name}
|
|
114
|
-
src={file.preview}
|
|
115
|
-
style={img}
|
|
116
|
-
// Revoke data uri after image is loaded, will prevent memory leaks
|
|
117
|
-
// (see: https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL)
|
|
118
|
-
// Notice: However, I can't use it because I'm re-rendering the thumbs when removing a file from the list
|
|
119
|
-
/*
|
|
120
|
-
onLoad={() => {
|
|
121
|
-
URL.revokeObjectURL(file.preview)
|
|
122
|
-
}}
|
|
123
|
-
*/
|
|
124
|
-
/>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
))
|
|
128
|
-
|
|
129
|
-
useEffect(() => {
|
|
130
|
-
// Make sure to revoke the data uris to avoid memory leaks, will run on unmount
|
|
131
|
-
return () => files.forEach((file: any) => URL.revokeObjectURL(file.preview))
|
|
132
|
-
}, [])
|
|
133
|
-
|
|
134
|
-
return (
|
|
135
|
-
<ContainerBox title={t('uploadPhotosToGallery')}>
|
|
136
|
-
<section className='container mt-4 rounded-2xl border-4 border-dashed border-gray-400 bg-accent py-8'>
|
|
137
|
-
<div {...getRootProps({ className: 'dropzone' })}>
|
|
138
|
-
<input {...getInputProps()} />
|
|
139
|
-
<div className='flex flex-col items-center justify-center'>
|
|
140
|
-
<div className='p-4 text-xl font-semibold text-card-foreground'>
|
|
141
|
-
{t('dropzoneText')}
|
|
142
|
-
</div>
|
|
143
|
-
<div className='text-muted-foreground'>
|
|
144
|
-
{t('accepts')}: {Object.values(acceptedFileTypes).flat().join(', ')}
|
|
145
|
-
</div>
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
{thumbs?.length > 0 && <aside className='mt-4 flex flex-row flex-wrap gap-2'>{thumbs}</aside>}
|
|
149
|
-
</section>
|
|
150
|
-
</ContainerBox>
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export default forwardRef(Dropzone)
|
|
1
|
+
import React, { CSSProperties, forwardRef, Ref, useEffect, useImperativeHandle, useState } from 'react'
|
|
2
|
+
import { DropEvent, FileRejection, useDropzone } from 'react-dropzone'
|
|
3
|
+
import { DropzoneFile } from 'nextjs-cms/core/types'
|
|
4
|
+
import ContainerBox from '@/components/ContainerBox'
|
|
5
|
+
import { useI18n } from 'nextjs-cms/translations/client'
|
|
6
|
+
import { MinusIcon } from '@radix-ui/react-icons'
|
|
7
|
+
import { useToast } from '@/components/ui/use-toast'
|
|
8
|
+
|
|
9
|
+
const thumb: CSSProperties = {
|
|
10
|
+
display: 'inline-flex',
|
|
11
|
+
borderRadius: 2,
|
|
12
|
+
border: '1px solid #eaeaea',
|
|
13
|
+
marginBottom: 8,
|
|
14
|
+
marginRight: 8,
|
|
15
|
+
width: 100,
|
|
16
|
+
height: 100,
|
|
17
|
+
padding: 4,
|
|
18
|
+
boxSizing: 'border-box',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const thumbInner: CSSProperties = {
|
|
22
|
+
display: 'flex',
|
|
23
|
+
minWidth: 0,
|
|
24
|
+
overflow: 'hidden',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const img: CSSProperties = {
|
|
28
|
+
display: 'block',
|
|
29
|
+
width: 'auto',
|
|
30
|
+
height: '100%',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface DropzoneHandles {
|
|
34
|
+
getFiles: () => File[]
|
|
35
|
+
removeFiles: () => void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function Dropzone(props: any, ref: Ref<DropzoneHandles>) {
|
|
39
|
+
const t = useI18n()
|
|
40
|
+
const acceptedFileTypes = {
|
|
41
|
+
'image/png': ['.png'],
|
|
42
|
+
'image/jpeg': ['.jpg', '.jpeg'],
|
|
43
|
+
'image/webp': ['.webp'],
|
|
44
|
+
}
|
|
45
|
+
const [files, setFiles] = useState<DropzoneFile[]>([])
|
|
46
|
+
const { toast } = useToast()
|
|
47
|
+
const { getRootProps, getInputProps } = useDropzone({
|
|
48
|
+
accept: acceptedFileTypes,
|
|
49
|
+
maxFiles: 10,
|
|
50
|
+
onDrop: (acceptedFiles: File[]) => {
|
|
51
|
+
// First, check if the file is already in the list
|
|
52
|
+
const filteredFiles = acceptedFiles.filter((file) => !files.some((prevFile) => prevFile.name === file.name))
|
|
53
|
+
setFiles((prevFiles) => [
|
|
54
|
+
...prevFiles,
|
|
55
|
+
...filteredFiles.map((file: File) =>
|
|
56
|
+
Object.assign(file, {
|
|
57
|
+
preview: URL.createObjectURL(file),
|
|
58
|
+
}),
|
|
59
|
+
),
|
|
60
|
+
])
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
onDropRejected: (rejectedFiles: FileRejection[], e: DropEvent) => {
|
|
64
|
+
toast({
|
|
65
|
+
variant: 'destructive',
|
|
66
|
+
title: t('deleteGalleryPhoto'),
|
|
67
|
+
description: rejectedFiles[0]?.errors[0]?.message
|
|
68
|
+
? rejectedFiles[0].errors[0].message
|
|
69
|
+
: t('error'),
|
|
70
|
+
})
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
onError: (error: any) => {
|
|
74
|
+
toast({
|
|
75
|
+
variant: 'destructive',
|
|
76
|
+
title: t('deleteGalleryPhoto'),
|
|
77
|
+
description: error.displayName,
|
|
78
|
+
})
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
useImperativeHandle(ref, () => ({
|
|
83
|
+
getFiles: () => files,
|
|
84
|
+
removeFiles: () => {
|
|
85
|
+
files.forEach((file: any) => URL.revokeObjectURL(file.preview))
|
|
86
|
+
setFiles([])
|
|
87
|
+
},
|
|
88
|
+
}))
|
|
89
|
+
|
|
90
|
+
const thumbs = files.map((file: DropzoneFile, index: number) => (
|
|
91
|
+
<div style={thumb} key={`${file.name}-${index}`} className='relative'>
|
|
92
|
+
{/* Delete Button */}
|
|
93
|
+
<button
|
|
94
|
+
type='button'
|
|
95
|
+
className='absolute -end-1 -top-1 rounded-full bg-red-500 p-1'
|
|
96
|
+
onClick={() => {
|
|
97
|
+
URL.revokeObjectURL(file.preview)
|
|
98
|
+
setFiles((prevFiles) =>
|
|
99
|
+
prevFiles.filter((prevFile) => {
|
|
100
|
+
if (prevFile.name !== file.name) {
|
|
101
|
+
// Create a new preview url for the file
|
|
102
|
+
prevFile.preview = URL.createObjectURL(prevFile)
|
|
103
|
+
return prevFile
|
|
104
|
+
}
|
|
105
|
+
}),
|
|
106
|
+
)
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
<MinusIcon className='text-white' />
|
|
110
|
+
</button>
|
|
111
|
+
<div style={thumbInner}>
|
|
112
|
+
<img
|
|
113
|
+
alt={file.name}
|
|
114
|
+
src={file.preview}
|
|
115
|
+
style={img}
|
|
116
|
+
// Revoke data uri after image is loaded, will prevent memory leaks
|
|
117
|
+
// (see: https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL)
|
|
118
|
+
// Notice: However, I can't use it because I'm re-rendering the thumbs when removing a file from the list
|
|
119
|
+
/*
|
|
120
|
+
onLoad={() => {
|
|
121
|
+
URL.revokeObjectURL(file.preview)
|
|
122
|
+
}}
|
|
123
|
+
*/
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
))
|
|
128
|
+
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
// Make sure to revoke the data uris to avoid memory leaks, will run on unmount
|
|
131
|
+
return () => files.forEach((file: any) => URL.revokeObjectURL(file.preview))
|
|
132
|
+
}, [])
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<ContainerBox title={t('uploadPhotosToGallery')}>
|
|
136
|
+
<section className='container mt-4 rounded-2xl border-4 border-dashed border-gray-400 bg-accent py-8'>
|
|
137
|
+
<div {...getRootProps({ className: 'dropzone' })}>
|
|
138
|
+
<input {...getInputProps()} />
|
|
139
|
+
<div className='flex flex-col items-center justify-center'>
|
|
140
|
+
<div className='p-4 text-xl font-semibold text-card-foreground'>
|
|
141
|
+
{t('dropzoneText')}
|
|
142
|
+
</div>
|
|
143
|
+
<div className='text-muted-foreground'>
|
|
144
|
+
{t('accepts')}: {Object.values(acceptedFileTypes).flat().join(', ')}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
{thumbs?.length > 0 && <aside className='mt-4 flex flex-row flex-wrap gap-2'>{thumbs}</aside>}
|
|
149
|
+
</section>
|
|
150
|
+
</ContainerBox>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export default forwardRef(Dropzone)
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { Alert, AlertDescription } from './ui/alert'
|
|
2
|
-
import { ExclamationTriangleIcon } from '@radix-ui/react-icons'
|
|
3
|
-
|
|
4
|
-
export default function ErrorComponent({ message }: { message: string }) {
|
|
5
|
-
return (
|
|
6
|
-
<div className='px-4'>
|
|
7
|
-
<Alert variant='info' className='my-8'>
|
|
8
|
-
<AlertDescription className='text-md font-bold'>
|
|
9
|
-
<div className='flex items-center gap-1.5'>
|
|
10
|
-
<ExclamationTriangleIcon className='size-5' /> {message}
|
|
11
|
-
</div>
|
|
12
|
-
</AlertDescription>
|
|
13
|
-
</Alert>
|
|
14
|
-
</div>
|
|
15
|
-
)
|
|
16
|
-
|
|
1
|
+
import { Alert, AlertDescription } from './ui/alert'
|
|
2
|
+
import { ExclamationTriangleIcon } from '@radix-ui/react-icons'
|
|
3
|
+
|
|
4
|
+
export default function ErrorComponent({ message }: { message: string }) {
|
|
5
|
+
return (
|
|
6
|
+
<div className='px-4'>
|
|
7
|
+
<Alert variant='info' className='my-8'>
|
|
8
|
+
<AlertDescription className='text-md font-bold'>
|
|
9
|
+
<div className='flex items-center gap-1.5'>
|
|
10
|
+
<ExclamationTriangleIcon className='size-5' /> {message}
|
|
11
|
+
</div>
|
|
12
|
+
</AlertDescription>
|
|
13
|
+
</Alert>
|
|
14
|
+
</div>
|
|
15
|
+
)
|
|
16
|
+
|
|
17
17
|
}
|