create-nextjs-cms 0.9.30 → 0.9.32

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 (145) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +71 -71
  3. package/dist/helpers/utils.js +16 -16
  4. package/dist/lib/section-creators.js +166 -166
  5. package/package.json +1 -1
  6. package/templates/default/.eslintrc.json +5 -5
  7. package/templates/default/.prettierignore +7 -7
  8. package/templates/default/.prettierrc.json +27 -27
  9. package/templates/default/CHANGELOG.md +140 -140
  10. package/templates/default/_gitignore +57 -57
  11. package/templates/default/app/(auth)/auth/login/LoginPage.tsx +192 -192
  12. package/templates/default/app/(auth)/auth/login/page.tsx +11 -11
  13. package/templates/default/app/(auth)/auth-language-provider.tsx +34 -34
  14. package/templates/default/app/(auth)/layout.tsx +81 -81
  15. package/templates/default/app/(rootLayout)/(plugins)/[...slug]/error.tsx +64 -0
  16. package/templates/default/app/(rootLayout)/(plugins)/[...slug]/page.tsx +59 -47
  17. package/templates/default/app/(rootLayout)/(plugins)/[...slug]/plugin-server-registry.ts +14 -16
  18. package/templates/default/app/(rootLayout)/admins/page.tsx +10 -10
  19. package/templates/default/app/(rootLayout)/browse/[section]/[page]/page.tsx +22 -22
  20. package/templates/default/app/(rootLayout)/categorized/[section]/page.tsx +15 -15
  21. package/templates/default/app/(rootLayout)/dashboard/page.tsx +68 -70
  22. package/templates/default/app/(rootLayout)/edit/[section]/[itemId]/page.tsx +20 -20
  23. package/templates/default/app/(rootLayout)/layout.tsx +81 -81
  24. package/templates/default/app/(rootLayout)/loading.tsx +10 -10
  25. package/templates/default/app/(rootLayout)/log/page.tsx +5 -7
  26. package/templates/default/app/(rootLayout)/new/[section]/page.tsx +15 -15
  27. package/templates/default/app/(rootLayout)/section/[section]/page.tsx +19 -19
  28. package/templates/default/app/(rootLayout)/settings/page.tsx +11 -13
  29. package/templates/default/app/api/auth/csrf/route.ts +25 -25
  30. package/templates/default/app/api/auth/refresh/route.ts +10 -10
  31. package/templates/default/app/api/auth/route.ts +49 -49
  32. package/templates/default/app/api/auth/session/route.ts +20 -20
  33. package/templates/default/app/api/document/route.ts +165 -165
  34. package/templates/default/app/api/editor/photo/route.ts +49 -49
  35. package/templates/default/app/api/photo/route.ts +27 -27
  36. package/templates/default/app/api/submit/section/item/[slug]/route.ts +95 -95
  37. package/templates/default/app/api/submit/section/item/route.ts +56 -56
  38. package/templates/default/app/api/submit/section/simple/route.ts +86 -86
  39. package/templates/default/app/api/video/route.ts +174 -174
  40. package/templates/default/app/globals.css +236 -236
  41. package/templates/default/cms.config.ts +56 -56
  42. package/templates/default/components/admin/admin-card.tsx +165 -165
  43. package/templates/default/components/admin/admin-edit-page.tsx +124 -124
  44. package/templates/default/components/admin/admin-privilege-card.tsx +184 -184
  45. package/templates/default/components/admin/new-admin-form.tsx +172 -172
  46. package/templates/default/components/container-box.tsx +24 -24
  47. package/templates/default/components/dnd-kit/draggable.tsx +21 -21
  48. package/templates/default/components/dnd-kit/droppable.tsx +20 -20
  49. package/templates/default/components/dnd-kit/sortable-item.tsx +18 -18
  50. package/templates/default/components/feedback/error-component.tsx +16 -16
  51. package/templates/default/components/feedback/info-card.tsx +93 -93
  52. package/templates/default/components/feedback/loading-spinners.tsx +67 -67
  53. package/templates/default/components/feedback/modal.tsx +166 -166
  54. package/templates/default/components/feedback/progress-bar.tsx +48 -48
  55. package/templates/default/components/feedback/tooltip-component.tsx +27 -27
  56. package/templates/default/components/form/form-input-element.tsx +70 -70
  57. package/templates/default/components/form/helpers/_section-hot-reload.js +1 -1
  58. package/templates/default/components/form/helpers/util.ts +17 -17
  59. package/templates/default/components/form/inputs/checkbox-form-input.tsx +46 -46
  60. package/templates/default/components/form/inputs/color-form-input.tsx +44 -44
  61. package/templates/default/components/form/inputs/date-form-input.tsx +93 -93
  62. package/templates/default/components/form/inputs/map-form-input.tsx +141 -141
  63. package/templates/default/components/form/inputs/multiple-select-form-input.tsx +85 -85
  64. package/templates/default/components/form/inputs/number-form-input.tsx +43 -43
  65. package/templates/default/components/form/inputs/password-form-input.tsx +47 -47
  66. package/templates/default/components/form/inputs/photo-form-input.tsx +279 -279
  67. package/templates/default/components/form/inputs/rich-text-form-input.tsx +148 -148
  68. package/templates/default/components/form/inputs/select-form-input.tsx +159 -159
  69. package/templates/default/components/form/inputs/slug-form-input.tsx +131 -131
  70. package/templates/default/components/form/inputs/tags-form-input.tsx +255 -255
  71. package/templates/default/components/form/inputs/text-form-input.tsx +61 -61
  72. package/templates/default/components/form/inputs/textarea-form-input.tsx +61 -61
  73. package/templates/default/components/layout/default-nav-items.tsx +3 -3
  74. package/templates/default/components/layout/layout.tsx +84 -84
  75. package/templates/default/components/layout/navbar.tsx +258 -258
  76. package/templates/default/components/layout/sidebar-dropdown-item.tsx +83 -83
  77. package/templates/default/components/layout/sidebar-item.tsx +24 -24
  78. package/templates/default/components/layout/sidebar.tsx +229 -229
  79. package/templates/default/components/layout/theme-provider.tsx +8 -8
  80. package/templates/default/components/layout/theme-toggle.tsx +39 -39
  81. package/templates/default/components/locale/locale-switcher.tsx +98 -98
  82. package/templates/default/components/media/dropzone.tsx +154 -154
  83. package/templates/default/components/media/protected-document.tsx +44 -44
  84. package/templates/default/components/media/protected-image.tsx +143 -143
  85. package/templates/default/components/media/protected-video.tsx +76 -76
  86. package/templates/default/components/multi-select.tsx +1150 -1150
  87. package/templates/default/components/pages/admins-page.tsx +43 -43
  88. package/templates/default/components/pages/browse-page.tsx +106 -106
  89. package/templates/default/components/pages/categorized-section-page.tsx +31 -31
  90. package/templates/default/components/pages/dashboard-page-alt.tsx +45 -45
  91. package/templates/default/components/pages/item-edit-page.tsx +267 -267
  92. package/templates/default/components/pages/log-page.tsx +107 -107
  93. package/templates/default/components/pages/new-page.tsx +183 -183
  94. package/templates/default/components/pages/section-page.tsx +203 -203
  95. package/templates/default/components/pages/settings-page.tsx +232 -232
  96. package/templates/default/components/pagination/pagination-buttons.tsx +147 -147
  97. package/templates/default/components/pagination/pagination.tsx +36 -36
  98. package/templates/default/components/sections/category-delete-confirm-page.tsx +130 -130
  99. package/templates/default/components/sections/category-section-select-input.tsx +139 -139
  100. package/templates/default/components/sections/conditional-fields.tsx +49 -49
  101. package/templates/default/components/sections/section-icon.tsx +8 -8
  102. package/templates/default/components/sections/section-item-card.tsx +143 -143
  103. package/templates/default/components/sections/section-item-status-badge.tsx +17 -17
  104. package/templates/default/components/sections/select-input-buttons.tsx +125 -125
  105. package/templates/default/components/select-box.tsx +98 -98
  106. package/templates/default/components/ui/accordion.tsx +53 -53
  107. package/templates/default/components/ui/alert-dialog.tsx +113 -113
  108. package/templates/default/components/ui/alert.tsx +47 -47
  109. package/templates/default/components/ui/badge.tsx +38 -38
  110. package/templates/default/components/ui/card.tsx +43 -43
  111. package/templates/default/components/ui/command.tsx +137 -137
  112. package/templates/default/components/ui/custom-alert-dialog.tsx +113 -113
  113. package/templates/default/components/ui/custom-dialog.tsx +123 -123
  114. package/templates/default/components/ui/dialog.tsx +123 -123
  115. package/templates/default/components/ui/direction.tsx +22 -22
  116. package/templates/default/components/ui/dropdown-menu.tsx +182 -182
  117. package/templates/default/components/ui/input-group.tsx +54 -54
  118. package/templates/default/components/ui/input.tsx +22 -22
  119. package/templates/default/components/ui/label.tsx +19 -19
  120. package/templates/default/components/ui/popover.tsx +42 -42
  121. package/templates/default/components/ui/progress.tsx +31 -31
  122. package/templates/default/components/ui/scroll-area.tsx +42 -42
  123. package/templates/default/components/ui/select.tsx +165 -165
  124. package/templates/default/components/ui/separator.tsx +28 -28
  125. package/templates/default/components/ui/sheet.tsx +103 -103
  126. package/templates/default/components/ui/spinner.tsx +16 -16
  127. package/templates/default/components/ui/switch.tsx +29 -29
  128. package/templates/default/components/ui/table.tsx +83 -83
  129. package/templates/default/components/ui/tabs.tsx +55 -55
  130. package/templates/default/components/ui/toast.tsx +113 -113
  131. package/templates/default/components/ui/toaster.tsx +35 -35
  132. package/templates/default/components/ui/tooltip.tsx +30 -30
  133. package/templates/default/components/ui/use-toast.ts +187 -187
  134. package/templates/default/drizzle.config.ts +4 -4
  135. package/templates/default/dynamic-schemas/schema.ts +225 -75
  136. package/templates/default/env/env.ts +46 -46
  137. package/templates/default/envConfig.ts +4 -4
  138. package/templates/default/lib/postinstall.js +14 -14
  139. package/templates/default/lib/utils.ts +6 -6
  140. package/templates/default/next-env.d.ts +6 -6
  141. package/templates/default/next.config.ts +24 -24
  142. package/templates/default/package.json +1 -1
  143. package/templates/default/postcss.config.mjs +6 -6
  144. package/templates/default/proxy.ts +32 -32
  145. package/templates/default/tsconfig.json +48 -48
@@ -1,143 +1,143 @@
1
- import Image from 'next/image'
2
- import React, { useEffect } from 'react'
3
- import { useAxiosPrivate } from 'nextjs-cms/api/client'
4
- import { AxiosInstance } from 'axios'
5
- import { base64ToBlob } from 'nextjs-cms/utils'
6
-
7
- const defaultDataPlaceholder: string =
8
- 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mPkY2Q8AgABCwDWK6njxAAAAABJRU5ErkJggg=='
9
-
10
- async function getProtectedPhoto({
11
- src,
12
- axiosPrivate,
13
- }: {
14
- src: string
15
- axiosPrivate: AxiosInstance
16
- }): Promise<string | null> {
17
- try {
18
- const res = await axiosPrivate.get(src)
19
- return res.data
20
- } catch (error) {
21
- return null
22
- }
23
- }
24
-
25
- const ProtectedImage = ({
26
- section,
27
- photo,
28
- isThumb = false,
29
- alt,
30
- width,
31
- height,
32
- fill = false,
33
- className = 'rounded-3xl object-cover',
34
- displayAs = 'blob',
35
- }: {
36
- section: string
37
- photo: string
38
- isThumb?: boolean
39
- alt: string
40
- width: number
41
- height: number
42
- fill?: boolean
43
- className?: string
44
- displayAs?: 'blob' | 'base64'
45
- }) => {
46
- const axiosPrivate = useAxiosPrivate()
47
- const [srcUrl, setSrcUrl] = React.useState<string | null>(null)
48
-
49
- // Use trpc?
50
- /*
51
- const { data, isLoading } = trpc.files.getPhoto.useQuery({
52
- name: photo,
53
- folder: section,
54
- isThumb: isThumb,
55
- })
56
-
57
- useEffect(() => {
58
- if (!data) return
59
- switch (displayAs) {
60
- case 'blob':
61
- setSrcUrl(
62
- URL.createObjectURL(
63
- base64ToBlob({
64
- base64: data,
65
- contentType: 'image/webp',
66
- stripHeader: true,
67
- }),
68
- ),
69
- )
70
- break
71
- case 'base64':
72
- setSrcUrl(data)
73
- break
74
- }
75
-
76
- return () => {
77
- setSrcUrl(null)
78
- if (displayAs === 'blob' && srcUrl) {
79
- URL.revokeObjectURL(srcUrl)
80
- }
81
- }
82
- }, [data])
83
- */
84
- useEffect(() => {
85
- if (!photo || !section) return
86
- getProtectedPhoto({
87
- src: `/photo?name=${photo}&folder=${section}&isThumb=${isThumb}`,
88
- axiosPrivate: axiosPrivate,
89
- }).then((res) => {
90
- if (!res) return
91
- switch (displayAs) {
92
- case 'blob':
93
- setSrcUrl(
94
- URL.createObjectURL(
95
- base64ToBlob({
96
- base64: res,
97
- contentType: 'image/webp',
98
- stripHeader: true,
99
- }),
100
- ),
101
- )
102
- break
103
- case 'base64':
104
- setSrcUrl(res)
105
- break
106
- }
107
- })
108
-
109
- return () => {
110
- setSrcUrl(null)
111
- if (displayAs === 'blob' && srcUrl) {
112
- URL.revokeObjectURL(srcUrl)
113
- }
114
- }
115
- }, [])
116
-
117
- return (
118
- <Image
119
- alt={alt}
120
- className={className}
121
- src={srcUrl ? srcUrl : defaultDataPlaceholder}
122
- width={fill ? undefined : width}
123
- height={fill ? undefined : height}
124
- style={
125
- !fill
126
- ? {
127
- width: `${width}px`,
128
- height: 'auto',
129
- }
130
- : undefined
131
- }
132
- fill={fill ? true : undefined}
133
- onLoad={(e) => {
134
- // Revoke the blob url after the image is loaded
135
- if (displayAs === 'blob' && srcUrl) {
136
- URL.revokeObjectURL(srcUrl)
137
- }
138
- }}
139
- />
140
- )
141
- }
142
-
143
- export default ProtectedImage
1
+ import Image from 'next/image'
2
+ import React, { useEffect } from 'react'
3
+ import { useAxiosPrivate } from 'nextjs-cms/api/client'
4
+ import { AxiosInstance } from 'axios'
5
+ import { base64ToBlob } from 'nextjs-cms/utils'
6
+
7
+ const defaultDataPlaceholder: string =
8
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mPkY2Q8AgABCwDWK6njxAAAAABJRU5ErkJggg=='
9
+
10
+ async function getProtectedPhoto({
11
+ src,
12
+ axiosPrivate,
13
+ }: {
14
+ src: string
15
+ axiosPrivate: AxiosInstance
16
+ }): Promise<string | null> {
17
+ try {
18
+ const res = await axiosPrivate.get(src)
19
+ return res.data
20
+ } catch (error) {
21
+ return null
22
+ }
23
+ }
24
+
25
+ const ProtectedImage = ({
26
+ section,
27
+ photo,
28
+ isThumb = false,
29
+ alt,
30
+ width,
31
+ height,
32
+ fill = false,
33
+ className = 'rounded-3xl object-cover',
34
+ displayAs = 'blob',
35
+ }: {
36
+ section: string
37
+ photo: string
38
+ isThumb?: boolean
39
+ alt: string
40
+ width: number
41
+ height: number
42
+ fill?: boolean
43
+ className?: string
44
+ displayAs?: 'blob' | 'base64'
45
+ }) => {
46
+ const axiosPrivate = useAxiosPrivate()
47
+ const [srcUrl, setSrcUrl] = React.useState<string | null>(null)
48
+
49
+ // Use trpc?
50
+ /*
51
+ const { data, isLoading } = trpc.files.getPhoto.useQuery({
52
+ name: photo,
53
+ folder: section,
54
+ isThumb: isThumb,
55
+ })
56
+
57
+ useEffect(() => {
58
+ if (!data) return
59
+ switch (displayAs) {
60
+ case 'blob':
61
+ setSrcUrl(
62
+ URL.createObjectURL(
63
+ base64ToBlob({
64
+ base64: data,
65
+ contentType: 'image/webp',
66
+ stripHeader: true,
67
+ }),
68
+ ),
69
+ )
70
+ break
71
+ case 'base64':
72
+ setSrcUrl(data)
73
+ break
74
+ }
75
+
76
+ return () => {
77
+ setSrcUrl(null)
78
+ if (displayAs === 'blob' && srcUrl) {
79
+ URL.revokeObjectURL(srcUrl)
80
+ }
81
+ }
82
+ }, [data])
83
+ */
84
+ useEffect(() => {
85
+ if (!photo || !section) return
86
+ getProtectedPhoto({
87
+ src: `/photo?name=${photo}&folder=${section}&isThumb=${isThumb}`,
88
+ axiosPrivate: axiosPrivate,
89
+ }).then((res) => {
90
+ if (!res) return
91
+ switch (displayAs) {
92
+ case 'blob':
93
+ setSrcUrl(
94
+ URL.createObjectURL(
95
+ base64ToBlob({
96
+ base64: res,
97
+ contentType: 'image/webp',
98
+ stripHeader: true,
99
+ }),
100
+ ),
101
+ )
102
+ break
103
+ case 'base64':
104
+ setSrcUrl(res)
105
+ break
106
+ }
107
+ })
108
+
109
+ return () => {
110
+ setSrcUrl(null)
111
+ if (displayAs === 'blob' && srcUrl) {
112
+ URL.revokeObjectURL(srcUrl)
113
+ }
114
+ }
115
+ }, [])
116
+
117
+ return (
118
+ <Image
119
+ alt={alt}
120
+ className={className}
121
+ src={srcUrl ? srcUrl : defaultDataPlaceholder}
122
+ width={fill ? undefined : width}
123
+ height={fill ? undefined : height}
124
+ style={
125
+ !fill
126
+ ? {
127
+ width: `${width}px`,
128
+ height: 'auto',
129
+ }
130
+ : undefined
131
+ }
132
+ fill={fill ? true : undefined}
133
+ onLoad={(e) => {
134
+ // Revoke the blob url after the image is loaded
135
+ if (displayAs === 'blob' && srcUrl) {
136
+ URL.revokeObjectURL(srcUrl)
137
+ }
138
+ }}
139
+ />
140
+ )
141
+ }
142
+
143
+ export default ProtectedImage
@@ -1,76 +1,76 @@
1
- import React, { useEffect } from 'react'
2
- import { useRefreshToken } from 'nextjs-cms/auth/hooks'
3
- import { useSession } from 'nextjs-cms/auth/react'
4
-
5
- const ProtectedVideo = ({
6
- section,
7
- fieldName,
8
- file,
9
- width,
10
- height,
11
- className = 'rounded-3xl object-cover',
12
- }: {
13
- section: string
14
- fieldName: string
15
- file: string
16
- width: number
17
- height: number
18
- className?: string
19
- }) => {
20
- const refresh = useRefreshToken()
21
- const ref = React.useRef<HTMLVideoElement>(null)
22
- const [currentTime, setCurrentTime] = React.useState(0)
23
- const [isSeeking, setIsSeeking] = React.useState(false)
24
-
25
- const session = useSession()
26
- useEffect(() => {
27
- if (ref.current && currentTime) {
28
- ref.current.currentTime = currentTime
29
- ref.current.play().then(() => {})
30
- }
31
- }, [session])
32
-
33
- return (
34
- <div className={className}>
35
- {/**
36
- * The video will stream as long as the access token is valid
37
- * If the access token expires, the video will pause
38
- * A refresh token request will be sent to `/api/auth/refresh`
39
- * If the refresh token is valid, the video will resume playing
40
- * If the refresh token is invalid, the user will be logged out
41
- */}
42
- <video
43
- ref={ref}
44
- src={`/api/video?sectionName=${section}&fieldName=${fieldName}&name=${file}`}
45
- className='max-w-full'
46
- width={width}
47
- controls={true}
48
- height={height}
49
- onErrorCapture={async (e) => {
50
- if (isSeeking) return
51
- setIsSeeking(true)
52
- // console.log('seeked')
53
- /**
54
- * Pause the video
55
- */
56
- e.currentTarget.pause()
57
- /**
58
- * Get the current time of the video and save it in the state
59
- */
60
- setCurrentTime(e.currentTarget.currentTime)
61
- /**
62
- * Refresh the access token
63
- * This will trigger the `useEffect` hook
64
- * and set the current time of the video
65
- * to the time it was paused at
66
- * and play the video
67
- */
68
- await refresh()
69
- setIsSeeking(false)
70
- }}
71
- />
72
- </div>
73
- )
74
- }
75
-
76
- export default ProtectedVideo
1
+ import React, { useEffect } from 'react'
2
+ import { useRefreshToken } from 'nextjs-cms/auth/hooks'
3
+ import { useSession } from 'nextjs-cms/auth/react'
4
+
5
+ const ProtectedVideo = ({
6
+ section,
7
+ fieldName,
8
+ file,
9
+ width,
10
+ height,
11
+ className = 'rounded-3xl object-cover',
12
+ }: {
13
+ section: string
14
+ fieldName: string
15
+ file: string
16
+ width: number
17
+ height: number
18
+ className?: string
19
+ }) => {
20
+ const refresh = useRefreshToken()
21
+ const ref = React.useRef<HTMLVideoElement>(null)
22
+ const [currentTime, setCurrentTime] = React.useState(0)
23
+ const [isSeeking, setIsSeeking] = React.useState(false)
24
+
25
+ const session = useSession()
26
+ useEffect(() => {
27
+ if (ref.current && currentTime) {
28
+ ref.current.currentTime = currentTime
29
+ ref.current.play().then(() => {})
30
+ }
31
+ }, [session])
32
+
33
+ return (
34
+ <div className={className}>
35
+ {/**
36
+ * The video will stream as long as the access token is valid
37
+ * If the access token expires, the video will pause
38
+ * A refresh token request will be sent to `/api/auth/refresh`
39
+ * If the refresh token is valid, the video will resume playing
40
+ * If the refresh token is invalid, the user will be logged out
41
+ */}
42
+ <video
43
+ ref={ref}
44
+ src={`/api/video?sectionName=${section}&fieldName=${fieldName}&name=${file}`}
45
+ className='max-w-full'
46
+ width={width}
47
+ controls={true}
48
+ height={height}
49
+ onErrorCapture={async (e) => {
50
+ if (isSeeking) return
51
+ setIsSeeking(true)
52
+ // console.log('seeked')
53
+ /**
54
+ * Pause the video
55
+ */
56
+ e.currentTarget.pause()
57
+ /**
58
+ * Get the current time of the video and save it in the state
59
+ */
60
+ setCurrentTime(e.currentTarget.currentTime)
61
+ /**
62
+ * Refresh the access token
63
+ * This will trigger the `useEffect` hook
64
+ * and set the current time of the video
65
+ * to the time it was paused at
66
+ * and play the video
67
+ */
68
+ await refresh()
69
+ setIsSeeking(false)
70
+ }}
71
+ />
72
+ </div>
73
+ )
74
+ }
75
+
76
+ export default ProtectedVideo