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.
Files changed (187) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +395 -0
  6. package/dist/lib/utils.d.ts +11 -0
  7. package/dist/lib/utils.d.ts.map +1 -0
  8. package/dist/lib/utils.js +48 -0
  9. package/package.json +44 -0
  10. package/templates/default/.env +24 -0
  11. package/templates/default/.env.development +8 -0
  12. package/templates/default/.eslintrc.json +5 -0
  13. package/templates/default/.prettierignore +7 -0
  14. package/templates/default/.prettierrc.json +19 -0
  15. package/templates/default/CHANGELOG.md +77 -0
  16. package/templates/default/README.md +45 -0
  17. package/templates/default/app/(auth)/auth/login/LoginPage.tsx +175 -0
  18. package/templates/default/app/(auth)/auth/login/page.tsx +12 -0
  19. package/templates/default/app/(rootLayout)/admins/page.tsx +5 -0
  20. package/templates/default/app/(rootLayout)/advanced/page.tsx +5 -0
  21. package/templates/default/app/(rootLayout)/analytics/page.tsx +7 -0
  22. package/templates/default/app/(rootLayout)/browse/[section]/[page]/page.tsx +7 -0
  23. package/templates/default/app/(rootLayout)/categorized/[section]/page.tsx +7 -0
  24. package/templates/default/app/(rootLayout)/dashboard/page.tsx +7 -0
  25. package/templates/default/app/(rootLayout)/edit/[section]/[itemId]/page.tsx +7 -0
  26. package/templates/default/app/(rootLayout)/emails/page.tsx +6 -0
  27. package/templates/default/app/(rootLayout)/layout.tsx +5 -0
  28. package/templates/default/app/(rootLayout)/loading.tsx +10 -0
  29. package/templates/default/app/(rootLayout)/log/page.tsx +7 -0
  30. package/templates/default/app/(rootLayout)/new/[section]/page.tsx +7 -0
  31. package/templates/default/app/(rootLayout)/page.tsx +9 -0
  32. package/templates/default/app/(rootLayout)/section/[section]/page.tsx +7 -0
  33. package/templates/default/app/(rootLayout)/settings/page.tsx +7 -0
  34. package/templates/default/app/_trpc/client.ts +4 -0
  35. package/templates/default/app/api/auth/csrf/route.ts +25 -0
  36. package/templates/default/app/api/auth/refresh/route.ts +10 -0
  37. package/templates/default/app/api/auth/route.ts +23 -0
  38. package/templates/default/app/api/auth/session/route.ts +20 -0
  39. package/templates/default/app/api/editor/photo/route.ts +42 -0
  40. package/templates/default/app/api/photo/route.ts +27 -0
  41. package/templates/default/app/api/placeholder/route.ts +7 -0
  42. package/templates/default/app/api/submit/section/item/[slug]/route.ts +63 -0
  43. package/templates/default/app/api/submit/section/item/route.ts +53 -0
  44. package/templates/default/app/api/submit/section/simple/route.ts +54 -0
  45. package/templates/default/app/api/trpc/[trpc]/route.ts +33 -0
  46. package/templates/default/app/api/video/route.ts +174 -0
  47. package/templates/default/app/dictionaries.ts +14 -0
  48. package/templates/default/app/layout.tsx +28 -0
  49. package/templates/default/app/providers.tsx +151 -0
  50. package/templates/default/cli.ts +4 -0
  51. package/templates/default/components/AdminCard.tsx +163 -0
  52. package/templates/default/components/AdminEditPage.tsx +123 -0
  53. package/templates/default/components/AdminPrivilegeCard.tsx +184 -0
  54. package/templates/default/components/AdminsPage.tsx +43 -0
  55. package/templates/default/components/AdvancedSettingsPage.tsx +167 -0
  56. package/templates/default/components/AnalyticsPage.tsx +127 -0
  57. package/templates/default/components/BarChartBox.tsx +43 -0
  58. package/templates/default/components/BrowsePage.tsx +119 -0
  59. package/templates/default/components/CategorizedSectionPage.tsx +36 -0
  60. package/templates/default/components/CategoryDeleteConfirmPage.tsx +129 -0
  61. package/templates/default/components/CategorySectionSelectInput.tsx +139 -0
  62. package/templates/default/components/ConditionalFields.tsx +49 -0
  63. package/templates/default/components/ContainerBox.tsx +24 -0
  64. package/templates/default/components/DashboardPage.tsx +187 -0
  65. package/templates/default/components/DashboardPageAlt.tsx +43 -0
  66. package/templates/default/components/DefaultNavItems.tsx +3 -0
  67. package/templates/default/components/Dropzone.tsx +153 -0
  68. package/templates/default/components/EmailCard.tsx +137 -0
  69. package/templates/default/components/EmailPasswordForm.tsx +84 -0
  70. package/templates/default/components/EmailQuotaForm.tsx +72 -0
  71. package/templates/default/components/EmailsPage.tsx +48 -0
  72. package/templates/default/components/GalleryPhoto.tsx +93 -0
  73. package/templates/default/components/InfoCard.tsx +94 -0
  74. package/templates/default/components/ItemEditPage.tsx +217 -0
  75. package/templates/default/components/Layout.tsx +70 -0
  76. package/templates/default/components/LoadingSpinners.tsx +67 -0
  77. package/templates/default/components/LogPage.tsx +17 -0
  78. package/templates/default/components/Modal.tsx +99 -0
  79. package/templates/default/components/Navbar.tsx +29 -0
  80. package/templates/default/components/NavbarAlt.tsx +182 -0
  81. package/templates/default/components/NewAdminForm.tsx +172 -0
  82. package/templates/default/components/NewEmailForm.tsx +131 -0
  83. package/templates/default/components/NewPage.tsx +206 -0
  84. package/templates/default/components/NewVariantComponent.tsx +228 -0
  85. package/templates/default/components/PhotoGallery.tsx +35 -0
  86. package/templates/default/components/PieChartBox.tsx +101 -0
  87. package/templates/default/components/ProgressBar.tsx +24 -0
  88. package/templates/default/components/ProtectedDocument.tsx +78 -0
  89. package/templates/default/components/ProtectedImage.tsx +143 -0
  90. package/templates/default/components/ProtectedVideo.tsx +76 -0
  91. package/templates/default/components/SectionItemCard.tsx +143 -0
  92. package/templates/default/components/SectionItemStatusBadge.tsx +16 -0
  93. package/templates/default/components/SectionPage.tsx +124 -0
  94. package/templates/default/components/SelectBox.tsx +99 -0
  95. package/templates/default/components/SelectInputButtons.tsx +124 -0
  96. package/templates/default/components/SettingsPage.tsx +238 -0
  97. package/templates/default/components/Sidebar.tsx +209 -0
  98. package/templates/default/components/SidebarDropdownItem.tsx +74 -0
  99. package/templates/default/components/SidebarItem.tsx +19 -0
  100. package/templates/default/components/TempPage.tsx +12 -0
  101. package/templates/default/components/ThemeProvider.tsx +8 -0
  102. package/templates/default/components/TooltipComponent.tsx +27 -0
  103. package/templates/default/components/VariantCard.tsx +123 -0
  104. package/templates/default/components/VariantEditPage.tsx +229 -0
  105. package/templates/default/components/analytics/BounceRate.tsx +69 -0
  106. package/templates/default/components/analytics/LivePageViews.tsx +54 -0
  107. package/templates/default/components/analytics/LiveUsersCount.tsx +32 -0
  108. package/templates/default/components/analytics/MonthlyPageViews.tsx +41 -0
  109. package/templates/default/components/analytics/TopCountries.tsx +51 -0
  110. package/templates/default/components/analytics/TopDevices.tsx +45 -0
  111. package/templates/default/components/analytics/TopMediums.tsx +57 -0
  112. package/templates/default/components/analytics/TopSources.tsx +44 -0
  113. package/templates/default/components/analytics/TotalPageViews.tsx +40 -0
  114. package/templates/default/components/analytics/TotalSessions.tsx +40 -0
  115. package/templates/default/components/analytics/TotalUniqueUsers.tsx +40 -0
  116. package/templates/default/components/custom/RightHomeRoomVariantCard.tsx +137 -0
  117. package/templates/default/components/dndKit/Draggable.tsx +21 -0
  118. package/templates/default/components/dndKit/Droppable.tsx +20 -0
  119. package/templates/default/components/dndKit/SortableItem.tsx +18 -0
  120. package/templates/default/components/form/DateRangeFormInput.tsx +55 -0
  121. package/templates/default/components/form/Form.tsx +298 -0
  122. package/templates/default/components/form/FormInputElement.tsx +68 -0
  123. package/templates/default/components/form/FormInputs.tsx +108 -0
  124. package/templates/default/components/form/helpers/util.ts +20 -0
  125. package/templates/default/components/form/inputs/CheckboxFormInput.tsx +33 -0
  126. package/templates/default/components/form/inputs/ColorFormInput.tsx +44 -0
  127. package/templates/default/components/form/inputs/DateFormInput.tsx +107 -0
  128. package/templates/default/components/form/inputs/DocumentFormInput.tsx +124 -0
  129. package/templates/default/components/form/inputs/MapFormInput.tsx +139 -0
  130. package/templates/default/components/form/inputs/MultipleSelectFormInput.tsx +150 -0
  131. package/templates/default/components/form/inputs/NumberFormInput.tsx +42 -0
  132. package/templates/default/components/form/inputs/PasswordFormInput.tsx +47 -0
  133. package/templates/default/components/form/inputs/PhotoFormInput.tsx +218 -0
  134. package/templates/default/components/form/inputs/RichTextFormInput.tsx +133 -0
  135. package/templates/default/components/form/inputs/SelectFormInput.tsx +164 -0
  136. package/templates/default/components/form/inputs/TagsFormInput.tsx +63 -0
  137. package/templates/default/components/form/inputs/TextFormInput.tsx +48 -0
  138. package/templates/default/components/form/inputs/TextareaFormInput.tsx +47 -0
  139. package/templates/default/components/form/inputs/VideoFormInput.tsx +117 -0
  140. package/templates/default/components/pagination/Pagination.tsx +36 -0
  141. package/templates/default/components/pagination/PaginationButtons.tsx +145 -0
  142. package/templates/default/components/ui/accordion.tsx +57 -0
  143. package/templates/default/components/ui/alert.tsx +46 -0
  144. package/templates/default/components/ui/badge.tsx +33 -0
  145. package/templates/default/components/ui/button.tsx +57 -0
  146. package/templates/default/components/ui/calendar.tsx +68 -0
  147. package/templates/default/components/ui/card.tsx +76 -0
  148. package/templates/default/components/ui/checkbox.tsx +29 -0
  149. package/templates/default/components/ui/dropdown-menu.tsx +205 -0
  150. package/templates/default/components/ui/input.tsx +25 -0
  151. package/templates/default/components/ui/label.tsx +26 -0
  152. package/templates/default/components/ui/popover.tsx +31 -0
  153. package/templates/default/components/ui/scroll-area.tsx +42 -0
  154. package/templates/default/components/ui/select.tsx +164 -0
  155. package/templates/default/components/ui/sheet.tsx +107 -0
  156. package/templates/default/components/ui/switch.tsx +29 -0
  157. package/templates/default/components/ui/table.tsx +120 -0
  158. package/templates/default/components/ui/tabs.tsx +55 -0
  159. package/templates/default/components/ui/toast.tsx +113 -0
  160. package/templates/default/components/ui/toaster.tsx +35 -0
  161. package/templates/default/components/ui/tooltip.tsx +30 -0
  162. package/templates/default/components/ui/use-toast.ts +188 -0
  163. package/templates/default/components.json +16 -0
  164. package/templates/default/context/ModalProvider.tsx +53 -0
  165. package/templates/default/drizzle.config.ts +4 -0
  166. package/templates/default/dynamic-schemas/schema.ts +373 -0
  167. package/templates/default/env/env.js +130 -0
  168. package/templates/default/envConfig.ts +4 -0
  169. package/templates/default/hooks/useModal.ts +8 -0
  170. package/templates/default/lib/apiHelpers.ts +106 -0
  171. package/templates/default/lz.config.ts +40 -0
  172. package/templates/default/middleware.ts +33 -0
  173. package/templates/default/next.config.ts +46 -0
  174. package/templates/default/package.json +134 -0
  175. package/templates/default/postcss.config.js +6 -0
  176. package/templates/default/postinstall.js +14 -0
  177. package/templates/default/public/blank_avatar.png +0 -0
  178. package/templates/default/public/favicon.ico +0 -0
  179. package/templates/default/public/img/placeholder.svg +1 -0
  180. package/templates/default/public/lazemni_logo.png +0 -0
  181. package/templates/default/public/next.svg +1 -0
  182. package/templates/default/public/vercel.svg +1 -0
  183. package/templates/default/section-tests.ts +92 -0
  184. package/templates/default/styles/globals.css +88 -0
  185. package/templates/default/tailwind.config.js +95 -0
  186. package/templates/default/test.ts +77 -0
  187. package/templates/default/tsconfig.json +44 -0
@@ -0,0 +1,139 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import FormInputElement from '@/components/form/FormInputElement'
3
+ import SelectBox from '@/components/SelectBox'
4
+ import LoadingSpinners from '@/components/LoadingSpinners'
5
+ import { useAutoAnimate } from '@formkit/auto-animate/react'
6
+ import SelectInputButtons from '@/components/SelectInputButtons'
7
+ import getString from 'nextjs-cms/translations'
8
+ import { trpc } from '@/app/_trpc/client'
9
+ import { SelectOption } from 'nextjs-cms/core/fields'
10
+ import { RouterOutputs } from 'nextjs-cms/api'
11
+ import { nanoid } from 'nanoid'
12
+
13
+ type CategorySelect = RouterOutputs['categorySections']['get']['data']
14
+
15
+ export default function CategorySectionSelectInput({
16
+ input,
17
+ refetch,
18
+ isChild = false,
19
+ }: {
20
+ input: CategorySelect
21
+ refetch?: any
22
+ isChild?: boolean
23
+ }) {
24
+ /**
25
+ * Check and add the select option if it does not exist, the select option has a value of undefined
26
+ */
27
+ const exists = input.options?.filter((option) => option.value === undefined)
28
+ if (!exists || exists.length === 0) {
29
+ if (input.options) {
30
+ // @ts-ignore - This is a type-hack to add the placeholder `select` option to the options array
31
+ input.options.unshift({ value: undefined, label: getString('select') })
32
+ }
33
+ }
34
+
35
+ const [parent, enableAnimations] = useAutoAnimate(/* optional config */)
36
+ const [isLoading, setIsLoading] = useState<boolean>(false)
37
+ const [hasMounted, setHasMounted] = useState<boolean>(false)
38
+ const [value, setValue] = useState<string | number | undefined>(/*getValue(input?.value)*/)
39
+ const [child, setChild] = useState<CategorySelect | null>(null)
40
+ const [randomKey, setRandomKey] = useState<string>(nanoid())
41
+
42
+ useEffect(() => {
43
+ if (input) {
44
+ setChild(null)
45
+ if (input.depth <= 1) return
46
+ if (value === undefined) {
47
+ return
48
+ }
49
+ setIsLoading(true)
50
+ childrenMutation.mutate({
51
+ parentId: value,
52
+ sectionName: input.sectionName,
53
+ level: input.level,
54
+ })
55
+ }
56
+ }, [value])
57
+
58
+ if (!input) return null
59
+
60
+ const childrenMutation = trpc.categorySections.getChildren.useMutation({
61
+ onSuccess: (data) => {
62
+ if (data && data.options) {
63
+ const childInput: CategorySelect = {
64
+ parentId: data.parentId,
65
+ sectionName: input.sectionName,
66
+ required: input.required,
67
+ label: input.label,
68
+ name: input.name,
69
+ options: data.options,
70
+ level: data.level,
71
+ depth: input.depth,
72
+ value: hasMounted ? undefined : input.value,
73
+ allowRecursiveDelete: input.allowRecursiveDelete,
74
+ }
75
+ setChild(childInput)
76
+ } else {
77
+ setChild(null)
78
+ }
79
+
80
+ setIsLoading(false)
81
+ setHasMounted(true)
82
+ },
83
+ onError: (error) => {},
84
+ // console.log('Error', error)
85
+ })
86
+
87
+ return (
88
+ <>
89
+ {/* @ts-ignore no need for validations in this case */}
90
+ <FormInputElement label={isChild ? '|->' : input.label} required={input.required}>
91
+ <SelectBox
92
+ // The key is to tell react to re-render the select
93
+ // box whenever we change the randomKey value
94
+ // (we're doing this after deleting an item from the select)
95
+ key={randomKey}
96
+ items={input.options ? input.options : []}
97
+ onChange={(value: SelectOption | undefined) => {
98
+ setValue(value?.value)
99
+ }}
100
+ classname='w-full shadow-sm'
101
+ />
102
+
103
+ <SelectInputButtons
104
+ value={value}
105
+ level={input.level}
106
+ sectionTitle={input.label}
107
+ section={input.name}
108
+ parentId={input.parentId}
109
+ refetch={() => {
110
+ refetch()
111
+ }}
112
+ reset={() => {
113
+ setValue(undefined)
114
+ setRandomKey(nanoid())
115
+ }}
116
+ allowRecursiveDelete={input.allowRecursiveDelete}
117
+ />
118
+ </FormInputElement>
119
+ <div ref={parent}>
120
+ {isLoading && <LoadingSpinners />}
121
+ {child && (
122
+ <div className='ps-4'>
123
+ <CategorySectionSelectInput
124
+ refetch={() => {
125
+ childrenMutation.mutate({
126
+ parentId: value,
127
+ sectionName: input.sectionName,
128
+ level: input.level,
129
+ })
130
+ }}
131
+ isChild={true}
132
+ input={child}
133
+ />
134
+ </div>
135
+ )}
136
+ </div>
137
+ </>
138
+ )
139
+ }
@@ -0,0 +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
+ }
@@ -0,0 +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-sm 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
+ }
@@ -0,0 +1,187 @@
1
+ 'use client'
2
+
3
+ import React, { useEffect } from 'react'
4
+ import getString from 'nextjs-cms/translations'
5
+ import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'
6
+ import { humanReadableFileSize } from 'nextjs-cms/utils'
7
+ import PieChartBox from '@/components/PieChartBox'
8
+ import LoadingSpinners from '@/components/LoadingSpinners'
9
+ import ContainerBox from '@/components/ContainerBox'
10
+ import { trpc } from '@/app/_trpc/client'
11
+ ChartJS.register(ArcElement, Tooltip, Legend)
12
+
13
+ const DashboardPage = () => {
14
+ const { isLoading, isError, data, error } = trpc.cpanel.getData.useQuery()
15
+
16
+ useEffect(() => {}, [])
17
+
18
+ return (
19
+ <div className='w-full'>
20
+ <div className='bg-gradient-to-r from-sky-200 via-emerald-300 to-blue-600 p-8 font-extrabold text-foreground dark:from-blue-800 dark:via-amber-700 dark:to-rose-900'>
21
+ <h1 className='text-3xl'>{getString('dashboard')}</h1>
22
+ </div>
23
+ <div className='flex flex-col gap-4 p-4'>
24
+ {isLoading && (
25
+ <div>
26
+ <LoadingSpinners />
27
+ </div>
28
+ )}
29
+ <div className='grid grid-cols-1 gap-4 sm-sidebar:grid-cols-1 md-sidebar:grid-cols-2 lg-sidebar:grid-cols-2 2xl-sidebar:grid-cols-4'>
30
+ {data ? (
31
+ <>
32
+ <PieChartBox
33
+ chartData={[
34
+ {
35
+ name: getString('total_disk_space'),
36
+ value: data.diskSpaceLimit - data.diskSpaceUsage,
37
+ fill: '#00C49F',
38
+ },
39
+ {
40
+ name: getString('used_disk_space'),
41
+ value: data.diskSpaceUsage,
42
+ fill: '#E1315B',
43
+ },
44
+ ]}
45
+ chartBoxTitles={{
46
+ mainTitle: getString('disk_space'),
47
+ totalUnitSubtitle: {
48
+ key: getString('total_disk_space'),
49
+ value: humanReadableFileSize(data.diskSpaceLimit),
50
+ },
51
+ usedUnitSubtitle: {
52
+ key: getString('used_disk_space'),
53
+ value: humanReadableFileSize(data.diskSpaceUsage),
54
+ },
55
+ }}
56
+ />
57
+ <PieChartBox
58
+ chartData={[
59
+ {
60
+ name: getString('total_bandwidth'),
61
+ value: data.bandwidthLimit - data.bandwidthUsage,
62
+ fill: '#1c89f1',
63
+ },
64
+ {
65
+ name: getString('used_bandwidth'),
66
+ value: data.bandwidthUsage,
67
+ fill: '#ff6688',
68
+ },
69
+ ]}
70
+ chartBoxTitles={{
71
+ mainTitle: getString('this_moth_bandwidth'),
72
+ totalUnitSubtitle: {
73
+ key: getString('total_bandwidth'),
74
+ value: humanReadableFileSize(data.bandwidthLimit),
75
+ },
76
+ usedUnitSubtitle: {
77
+ key: getString('used_bandwidth'),
78
+ value: humanReadableFileSize(data.bandwidthUsage),
79
+ },
80
+ }}
81
+ />
82
+ <PieChartBox
83
+ chartData={[
84
+ {
85
+ name: getString('total_emails'),
86
+ value: data.emailsLimit - data.emailsUsage,
87
+ fill: '#c4ba56',
88
+ },
89
+ {
90
+ name: getString('used_emails'),
91
+ value: data.emailsUsage,
92
+ fill: '#1404c4',
93
+ },
94
+ ]}
95
+ chartBoxTitles={{
96
+ mainTitle: getString('email_accounts'),
97
+ totalUnitSubtitle: {
98
+ key: getString('total_emails'),
99
+ value: data.emailsLimit.toString(),
100
+ },
101
+ usedUnitSubtitle: {
102
+ key: getString('used_emails'),
103
+ value: data.emailsUsage.toString(),
104
+ },
105
+ }}
106
+ />
107
+ <PieChartBox
108
+ chartData={[
109
+ {
110
+ name: getString('totalDatabases'),
111
+ value: data.dbsLimit - data.dbsCount,
112
+ fill: '#56c4b4',
113
+ },
114
+ {
115
+ name: getString('usedDatabases'),
116
+ value: data.dbsCount,
117
+ fill: '#625bb2',
118
+ },
119
+ ]}
120
+ chartBoxTitles={{
121
+ mainTitle: 'MySQL®/MariaDB Databases',
122
+ totalUnitSubtitle: {
123
+ key: getString('totalDatabases'),
124
+ value: data.dbsLimit.toString(),
125
+ },
126
+ usedUnitSubtitle: {
127
+ key: getString('usedDatabases'),
128
+ value: data.dbsCount.toString(),
129
+ },
130
+ }}
131
+ />
132
+ </>
133
+ ) : null}
134
+ </div>
135
+ <div className='grid grid-cols-1 gap-4 md:grid-cols-2'>
136
+ <ContainerBox title={getString('accountInformation')}>
137
+ <div>PHP Version: {data?.phpVersion}</div>
138
+ <div>root: {data?.documentRoot}</div>
139
+ </ContainerBox>
140
+
141
+ <ContainerBox title='MySQL/MariaDB'>
142
+ <div>
143
+ {data?.dbInfo ? (
144
+ <>
145
+ <div>Version: {data.dbInfo.version}</div>
146
+ <div>Remote: {data.dbInfo.is_remote}</div>
147
+ </>
148
+ ) : (
149
+ <div>MySQL/MariaDB is not installed</div>
150
+ )}
151
+ </div>
152
+ <div>
153
+ {data?.dbsList?.map((db: any, index: number) => (
154
+ <div key={`${db.name}_${db.database}_${index}`}>
155
+ <div>database: {db.database}</div>
156
+ <div>Disk Usage: {humanReadableFileSize(db.disk_usage)}</div>
157
+ <div>Users: {db.users?.length}</div>
158
+ </div>
159
+ ))}
160
+ </div>
161
+ </ContainerBox>
162
+ </div>
163
+ {/*<div>
164
+ <ContainerBox title={getString('passengerApplications')}>
165
+ <div className='flex gap-4'>
166
+ {data?.passengerAppList?.map((app: any) => (
167
+ <ContainerBox title={app.name} key={app.name}>
168
+ <div key={app.name + app.path}>
169
+ <div>Name: {app.name}</div>
170
+ <div>Domain: {app.domain}</div>
171
+ <div>Path: {app.path}</div>
172
+ <div>Deployment: {app.deploymentMode}</div>
173
+ <div>Nodejs: {app.nodejs}</div>
174
+ <div>Base URL: {app.url}</div>
175
+ <div>Enabled: {app.enabled}</div>
176
+ </div>
177
+ </ContainerBox>
178
+ ))}
179
+ </div>
180
+ </ContainerBox>
181
+ </div>*/}
182
+ </div>
183
+ </div>
184
+ )
185
+ }
186
+
187
+ export default DashboardPage
@@ -0,0 +1,43 @@
1
+ import { Metadata } from 'next'
2
+ import Image from 'next/image'
3
+
4
+ export const metadata: Metadata = {
5
+ title: 'Dashboard',
6
+ description: 'Example dashboard app built using the components.',
7
+ }
8
+
9
+ export default function DashboardPage() {
10
+ return (
11
+ <>
12
+ <div className='md:hidden'>
13
+ <Image
14
+ src='/examples/dashboard-light.png'
15
+ width={1280}
16
+ height={866}
17
+ alt='Dashboard'
18
+ className='block dark:hidden'
19
+ />
20
+ <Image
21
+ src='/examples/dashboard-dark.png'
22
+ width={1280}
23
+ height={866}
24
+ alt='Dashboard'
25
+ className='hidden dark:block'
26
+ />
27
+ </div>
28
+ <div className='hidden flex-col md:flex'>
29
+ <div className='border-b'>
30
+ <div className='flex h-16 items-center px-4'>
31
+ <div className='ml-auto flex items-center space-x-4'></div>
32
+ </div>
33
+ </div>
34
+ <div className='flex-1 space-y-4 p-8 pt-6'>
35
+ <div className='flex items-center justify-between space-y-2'>
36
+ <h2 className='text-3xl font-bold tracking-tight'>Dashboard</h2>
37
+ <div className='flex items-center space-x-2'></div>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </>
42
+ )
43
+ }
@@ -0,0 +1,3 @@
1
+ import { NavItem } from 'nextjs-cms/core/types'
2
+
3
+ export const DefaultNavItems: NavItem[] = []
@@ -0,0 +1,153 @@
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 getString from 'nextjs-cms/translations'
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 acceptedFileTypes = {
40
+ 'image/png': ['.png'],
41
+ 'image/jpeg': ['.jpg', '.jpeg'],
42
+ 'image/webp': ['.webp'],
43
+ }
44
+ const [files, setFiles] = useState<DropzoneFile[]>([])
45
+ const { toast } = useToast()
46
+ const { getRootProps, getInputProps } = useDropzone({
47
+ accept: acceptedFileTypes,
48
+ maxFiles: 10,
49
+ onDrop: (acceptedFiles: File[]) => {
50
+ // First, check if the file is already in the list
51
+ const filteredFiles = acceptedFiles.filter((file) => !files.some((prevFile) => prevFile.name === file.name))
52
+ setFiles((prevFiles) => [
53
+ ...prevFiles,
54
+ ...filteredFiles.map((file: File) =>
55
+ Object.assign(file, {
56
+ preview: URL.createObjectURL(file),
57
+ }),
58
+ ),
59
+ ])
60
+ },
61
+
62
+ onDropRejected: (rejectedFiles: FileRejection[], e: DropEvent) => {
63
+ toast({
64
+ variant: 'destructive',
65
+ title: getString('delete_gallery_photo'),
66
+ description: rejectedFiles[0]?.errors[0]?.message
67
+ ? rejectedFiles[0].errors[0].message
68
+ : getString('error'),
69
+ })
70
+ },
71
+
72
+ onError: (error: any) => {
73
+ toast({
74
+ variant: 'destructive',
75
+ title: getString('delete_gallery_photo'),
76
+ description: error.displayName,
77
+ })
78
+ },
79
+ })
80
+
81
+ useImperativeHandle(ref, () => ({
82
+ getFiles: () => files,
83
+ removeFiles: () => {
84
+ files.forEach((file: any) => URL.revokeObjectURL(file.preview))
85
+ setFiles([])
86
+ },
87
+ }))
88
+
89
+ const thumbs = files.map((file: DropzoneFile, index: number) => (
90
+ <div style={thumb} key={`${file.name}-${index}`} className='relative'>
91
+ {/* Delete Button */}
92
+ <button
93
+ type='button'
94
+ className='absolute -end-1 -top-1 rounded-full bg-red-500 p-1'
95
+ onClick={() => {
96
+ URL.revokeObjectURL(file.preview)
97
+ setFiles((prevFiles) =>
98
+ prevFiles.filter((prevFile) => {
99
+ if (prevFile.name !== file.name) {
100
+ // Create a new preview url for the file
101
+ prevFile.preview = URL.createObjectURL(prevFile)
102
+ return prevFile
103
+ }
104
+ }),
105
+ )
106
+ }}
107
+ >
108
+ <MinusIcon className='text-white' />
109
+ </button>
110
+ <div style={thumbInner}>
111
+ <img
112
+ alt={file.name}
113
+ src={file.preview}
114
+ style={img}
115
+ // Revoke data uri after image is loaded, will prevent memory leaks
116
+ // (see: https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL)
117
+ // Notice: However, I can't use it because I'm re-rendering the thumbs when removing a file from the list
118
+ /*
119
+ onLoad={() => {
120
+ URL.revokeObjectURL(file.preview)
121
+ }}
122
+ */
123
+ />
124
+ </div>
125
+ </div>
126
+ ))
127
+
128
+ useEffect(() => {
129
+ // Make sure to revoke the data uris to avoid memory leaks, will run on unmount
130
+ return () => files.forEach((file: any) => URL.revokeObjectURL(file.preview))
131
+ }, [])
132
+
133
+ return (
134
+ <ContainerBox title={getString('uploadPhotosToGallery')}>
135
+ <section className='container mt-4 rounded-2xl border-4 border-dashed border-gray-400 bg-accent py-8'>
136
+ <div {...getRootProps({ className: 'dropzone' })}>
137
+ <input {...getInputProps()} />
138
+ <div className='flex flex-col items-center justify-center'>
139
+ <div className='p-4 text-xl font-semibold text-card-foreground'>
140
+ {getString('dropzoneText')}
141
+ </div>
142
+ <div className='text-muted-foreground'>
143
+ Accepts: {Object.values(acceptedFileTypes).flat().join(', ')}
144
+ </div>
145
+ </div>
146
+ </div>
147
+ {thumbs?.length > 0 && <aside className='mt-4 flex flex-row flex-wrap gap-2'>{thumbs}</aside>}
148
+ </section>
149
+ </ContainerBox>
150
+ )
151
+ }
152
+
153
+ export default forwardRef(Dropzone)