create-nextjs-cms 0.9.28 → 0.9.30

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 (142) 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 +2 -2
  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)/admins/page.tsx +10 -10
  16. package/templates/default/app/(rootLayout)/browse/[section]/[page]/page.tsx +22 -22
  17. package/templates/default/app/(rootLayout)/categorized/[section]/page.tsx +15 -15
  18. package/templates/default/app/(rootLayout)/dashboard/page.tsx +70 -70
  19. package/templates/default/app/(rootLayout)/edit/[section]/[itemId]/page.tsx +20 -20
  20. package/templates/default/app/(rootLayout)/layout.tsx +81 -81
  21. package/templates/default/app/(rootLayout)/loading.tsx +10 -10
  22. package/templates/default/app/(rootLayout)/log/page.tsx +7 -7
  23. package/templates/default/app/(rootLayout)/new/[section]/page.tsx +15 -15
  24. package/templates/default/app/(rootLayout)/section/[section]/page.tsx +19 -19
  25. package/templates/default/app/(rootLayout)/settings/page.tsx +13 -13
  26. package/templates/default/app/api/auth/csrf/route.ts +25 -25
  27. package/templates/default/app/api/auth/refresh/route.ts +10 -10
  28. package/templates/default/app/api/auth/route.ts +49 -49
  29. package/templates/default/app/api/auth/session/route.ts +20 -20
  30. package/templates/default/app/api/document/route.ts +165 -165
  31. package/templates/default/app/api/editor/photo/route.ts +49 -49
  32. package/templates/default/app/api/photo/route.ts +27 -27
  33. package/templates/default/app/api/submit/section/item/[slug]/route.ts +95 -95
  34. package/templates/default/app/api/submit/section/item/route.ts +56 -56
  35. package/templates/default/app/api/submit/section/simple/route.ts +86 -86
  36. package/templates/default/app/api/video/route.ts +174 -174
  37. package/templates/default/app/globals.css +236 -236
  38. package/templates/default/cms.config.ts +56 -56
  39. package/templates/default/components/admin/admin-card.tsx +165 -165
  40. package/templates/default/components/admin/admin-edit-page.tsx +124 -124
  41. package/templates/default/components/admin/admin-privilege-card.tsx +184 -184
  42. package/templates/default/components/admin/new-admin-form.tsx +172 -172
  43. package/templates/default/components/container-box.tsx +24 -24
  44. package/templates/default/components/dnd-kit/draggable.tsx +21 -21
  45. package/templates/default/components/dnd-kit/droppable.tsx +20 -20
  46. package/templates/default/components/dnd-kit/sortable-item.tsx +18 -18
  47. package/templates/default/components/feedback/error-component.tsx +16 -16
  48. package/templates/default/components/feedback/info-card.tsx +93 -93
  49. package/templates/default/components/feedback/loading-spinners.tsx +67 -67
  50. package/templates/default/components/feedback/modal.tsx +166 -166
  51. package/templates/default/components/feedback/progress-bar.tsx +48 -48
  52. package/templates/default/components/feedback/tooltip-component.tsx +27 -27
  53. package/templates/default/components/form/form-input-element.tsx +70 -70
  54. package/templates/default/components/form/helpers/_section-hot-reload.js +1 -1
  55. package/templates/default/components/form/helpers/util.ts +17 -17
  56. package/templates/default/components/form/inputs/checkbox-form-input.tsx +46 -46
  57. package/templates/default/components/form/inputs/color-form-input.tsx +44 -44
  58. package/templates/default/components/form/inputs/date-form-input.tsx +93 -93
  59. package/templates/default/components/form/inputs/map-form-input.tsx +141 -141
  60. package/templates/default/components/form/inputs/multiple-select-form-input.tsx +85 -85
  61. package/templates/default/components/form/inputs/number-form-input.tsx +43 -43
  62. package/templates/default/components/form/inputs/password-form-input.tsx +47 -47
  63. package/templates/default/components/form/inputs/photo-form-input.tsx +279 -279
  64. package/templates/default/components/form/inputs/rich-text-form-input.tsx +148 -148
  65. package/templates/default/components/form/inputs/select-form-input.tsx +159 -159
  66. package/templates/default/components/form/inputs/slug-form-input.tsx +131 -131
  67. package/templates/default/components/form/inputs/tags-form-input.tsx +255 -255
  68. package/templates/default/components/form/inputs/text-form-input.tsx +61 -61
  69. package/templates/default/components/form/inputs/textarea-form-input.tsx +61 -61
  70. package/templates/default/components/layout/default-nav-items.tsx +3 -3
  71. package/templates/default/components/layout/layout.tsx +84 -84
  72. package/templates/default/components/layout/navbar.tsx +258 -258
  73. package/templates/default/components/layout/sidebar-dropdown-item.tsx +83 -83
  74. package/templates/default/components/layout/sidebar-item.tsx +24 -24
  75. package/templates/default/components/layout/sidebar.tsx +229 -229
  76. package/templates/default/components/layout/theme-provider.tsx +8 -8
  77. package/templates/default/components/layout/theme-toggle.tsx +39 -39
  78. package/templates/default/components/locale/locale-switcher.tsx +98 -98
  79. package/templates/default/components/media/dropzone.tsx +154 -154
  80. package/templates/default/components/media/protected-document.tsx +44 -44
  81. package/templates/default/components/media/protected-image.tsx +143 -143
  82. package/templates/default/components/media/protected-video.tsx +76 -76
  83. package/templates/default/components/multi-select.tsx +1150 -1150
  84. package/templates/default/components/pages/admins-page.tsx +43 -43
  85. package/templates/default/components/pages/browse-page.tsx +106 -106
  86. package/templates/default/components/pages/categorized-section-page.tsx +31 -31
  87. package/templates/default/components/pages/dashboard-page-alt.tsx +45 -45
  88. package/templates/default/components/pages/item-edit-page.tsx +267 -267
  89. package/templates/default/components/pages/log-page.tsx +107 -107
  90. package/templates/default/components/pages/new-page.tsx +183 -183
  91. package/templates/default/components/pages/section-page.tsx +203 -203
  92. package/templates/default/components/pages/settings-page.tsx +232 -232
  93. package/templates/default/components/pagination/pagination-buttons.tsx +147 -147
  94. package/templates/default/components/pagination/pagination.tsx +36 -36
  95. package/templates/default/components/sections/category-delete-confirm-page.tsx +130 -130
  96. package/templates/default/components/sections/category-section-select-input.tsx +139 -139
  97. package/templates/default/components/sections/conditional-fields.tsx +49 -49
  98. package/templates/default/components/sections/section-icon.tsx +8 -8
  99. package/templates/default/components/sections/section-item-card.tsx +143 -143
  100. package/templates/default/components/sections/section-item-status-badge.tsx +17 -17
  101. package/templates/default/components/sections/select-input-buttons.tsx +125 -125
  102. package/templates/default/components/select-box.tsx +98 -98
  103. package/templates/default/components/ui/accordion.tsx +53 -53
  104. package/templates/default/components/ui/alert-dialog.tsx +113 -113
  105. package/templates/default/components/ui/alert.tsx +47 -47
  106. package/templates/default/components/ui/badge.tsx +38 -38
  107. package/templates/default/components/ui/card.tsx +43 -43
  108. package/templates/default/components/ui/command.tsx +137 -137
  109. package/templates/default/components/ui/custom-alert-dialog.tsx +113 -113
  110. package/templates/default/components/ui/custom-dialog.tsx +123 -123
  111. package/templates/default/components/ui/dialog.tsx +123 -123
  112. package/templates/default/components/ui/direction.tsx +22 -22
  113. package/templates/default/components/ui/dropdown-menu.tsx +182 -182
  114. package/templates/default/components/ui/input-group.tsx +54 -54
  115. package/templates/default/components/ui/input.tsx +22 -22
  116. package/templates/default/components/ui/label.tsx +19 -19
  117. package/templates/default/components/ui/popover.tsx +42 -42
  118. package/templates/default/components/ui/progress.tsx +31 -31
  119. package/templates/default/components/ui/scroll-area.tsx +42 -42
  120. package/templates/default/components/ui/select.tsx +165 -165
  121. package/templates/default/components/ui/separator.tsx +28 -28
  122. package/templates/default/components/ui/sheet.tsx +103 -103
  123. package/templates/default/components/ui/spinner.tsx +16 -16
  124. package/templates/default/components/ui/switch.tsx +29 -29
  125. package/templates/default/components/ui/table.tsx +83 -83
  126. package/templates/default/components/ui/tabs.tsx +55 -55
  127. package/templates/default/components/ui/toast.tsx +113 -113
  128. package/templates/default/components/ui/toaster.tsx +35 -35
  129. package/templates/default/components/ui/tooltip.tsx +30 -30
  130. package/templates/default/components/ui/use-toast.ts +187 -187
  131. package/templates/default/drizzle.config.ts +4 -4
  132. package/templates/default/dynamic-schemas/schema.ts +75 -225
  133. package/templates/default/env/env.ts +46 -46
  134. package/templates/default/envConfig.ts +4 -4
  135. package/templates/default/lib/postinstall.js +14 -14
  136. package/templates/default/lib/utils.ts +6 -6
  137. package/templates/default/next-env.d.ts +6 -6
  138. package/templates/default/next.config.ts +24 -24
  139. package/templates/default/package.json +1 -1
  140. package/templates/default/postcss.config.mjs +6 -6
  141. package/templates/default/proxy.ts +32 -32
  142. 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