create-nextjs-cms 0.9.5 → 0.9.7

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 (26) hide show
  1. package/dist/helpers/check-directory.d.ts +1 -0
  2. package/dist/helpers/check-directory.d.ts.map +1 -1
  3. package/dist/helpers/check-directory.js +98 -23
  4. package/dist/index.js +13 -8
  5. package/dist/lib/create-project.js +1 -1
  6. package/package.json +3 -3
  7. package/templates/default/app/(auth)/auth-language-provider.tsx +34 -34
  8. package/templates/default/components/AnalyticsPage.tsx +22 -6
  9. package/templates/default/components/CategorySectionSelectInput.tsx +2 -2
  10. package/templates/default/components/form/Form.tsx +12 -2
  11. package/templates/default/components/form/FormInputs.tsx +25 -16
  12. package/templates/default/components/form/inputs/DateFormInput.tsx +110 -53
  13. package/templates/default/components/form/inputs/DateRangeFormInput.tsx +175 -0
  14. package/templates/default/components/form/inputs/TagsFormInput.tsx +6 -5
  15. package/templates/default/next-env.d.ts +1 -1
  16. package/templates/default/package.json +3 -3
  17. package/templates/default/proxy.ts +2 -2
  18. package/templates/default/app/(rootLayout)/dashboard-new/page.tsx +0 -7
  19. package/templates/default/components/DashboardNewPage.tsx +0 -253
  20. package/templates/default/components/DashboardPage.tsx +0 -188
  21. package/templates/default/components/EmailCard.tsx +0 -138
  22. package/templates/default/components/EmailPasswordForm.tsx +0 -85
  23. package/templates/default/components/EmailQuotaForm.tsx +0 -73
  24. package/templates/default/components/EmailsPage.tsx +0 -49
  25. package/templates/default/components/NewEmailForm.tsx +0 -132
  26. package/templates/default/components/form/DateRangeFormInput.tsx +0 -57
@@ -6,11 +6,10 @@ import { Calendar } from '@/components/ui/calendar'
6
6
  import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
7
7
  import { CalendarIcon, ChevronDownIcon } from '@radix-ui/react-icons'
8
8
  import { useI18n } from 'nextjs-cms/translations/client'
9
- import { DateFieldClientConfig } from 'nextjs-cms/core/fields'
10
- import { useController, useFormContext } from 'react-hook-form'
11
- import { Input } from '@/components/ui/input'
12
- import { Label } from '@/components/ui/label'
13
- import { Clock2Icon } from 'lucide-react'
9
+ import type { DateFieldClientConfig } from 'nextjs-cms/core/fields'
10
+ import { useController, useFormContext } from 'react-hook-form'
11
+ import { Input } from '@/components/ui/input'
12
+ import { Clock2Icon } from 'lucide-react'
14
13
 
15
14
  const seasonEmoji: Record<string, string> = {
16
15
  winter: '⛄️',
@@ -27,19 +26,64 @@ const getSeason = (month: Date): string => {
27
26
  else return 'autumn'
28
27
  }
29
28
 
30
- const formatCaption = (month: Date, options?: any) => {
31
- const season = getSeason(month)
32
- return seasonEmoji[season] + ' ' + dayjs(month).format('MMMM')
33
- }
34
-
35
- export default function DateFormInput({ input }: { input: DateFieldClientConfig }) {
36
- const t = useI18n()
37
- const { control } = useFormContext()
38
- const [open, setOpen] = React.useState(false)
39
- const {
40
- field,
41
- fieldState: { invalid, isTouched, isDirty, error },
42
- } = useController({
29
+ const formatCaption = (month: Date) => {
30
+ const season = getSeason(month)
31
+ return seasonEmoji[season] + ' ' + dayjs(month).format('MMMM')
32
+ }
33
+
34
+ function resolveBound(value: string | 'now' | undefined): Date | undefined {
35
+ if (!value) return undefined
36
+ if (value === 'now') return new Date()
37
+
38
+ const parsedDate = dayjs(value)
39
+ return parsedDate.isValid() ? parsedDate.toDate() : undefined
40
+ }
41
+
42
+ function toCalendarDay(date: Date | undefined): Date | undefined {
43
+ if (!date) return undefined
44
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate())
45
+ }
46
+
47
+ function isSameCalendarDay(left: Date | undefined, right: Date | undefined): boolean {
48
+ if (!left || !right) return false
49
+ return (
50
+ left.getFullYear() === right.getFullYear() &&
51
+ left.getMonth() === right.getMonth() &&
52
+ left.getDate() === right.getDate()
53
+ )
54
+ }
55
+
56
+ function clampDateToBounds(date: Date, minDate: Date | undefined, maxDate: Date | undefined): Date {
57
+ if (minDate && date.getTime() < minDate.getTime()) {
58
+ return new Date(minDate)
59
+ }
60
+
61
+ if (maxDate && date.getTime() > maxDate.getTime()) {
62
+ return new Date(maxDate)
63
+ }
64
+
65
+ return date
66
+ }
67
+
68
+ function getTimeBoundForSelectedDate(selectedDate: Date | undefined, bound: Date | undefined): string | undefined {
69
+ if (!isSameCalendarDay(selectedDate, bound) || !bound) return undefined
70
+ return dayjs(bound).format('HH:mm:ss')
71
+ }
72
+
73
+ export default function DateFormInput({ input }: { input: DateFieldClientConfig }) {
74
+ const t = useI18n()
75
+ const { control } = useFormContext()
76
+ const [open, setOpen] = React.useState(false)
77
+ const minDate = resolveBound(input.minDate)
78
+ const maxDate = resolveBound(input.maxDate)
79
+ const disabledDays = [
80
+ ...(minDate ? [{ before: toCalendarDay(minDate) as Date }] : []),
81
+ ...(maxDate ? [{ after: toCalendarDay(maxDate) as Date }] : []),
82
+ ]
83
+ const {
84
+ field,
85
+ fieldState: { error },
86
+ } = useController({
43
87
  name: input.name,
44
88
  control,
45
89
  defaultValue: input.value
@@ -88,27 +132,37 @@ export default function DateFormInput({ input }: { input: DateFieldClientConfig
88
132
  </Button>
89
133
  </PopoverTrigger>
90
134
  <PopoverContent className='w-auto overflow-hidden p-0' align='start'>
91
- <Calendar
92
- endMonth={new Date(2099, 11)}
93
- mode='single'
94
- captionLayout='dropdown'
95
- selected={date}
96
- onSelect={(newDate) => {
97
- /**
98
- * We should preserve the time from the previous date if it is set
99
- */
100
- if (input.format !== 'date') {
101
- const [hours, minutes, seconds] = dayjs(date)
102
- .format('HH:mm:ss')
103
- .split(':')
104
- .map(Number)
105
- newDate?.setHours(hours ?? 0, minutes ?? 0, seconds ?? 0, 0)
106
- }
107
- setDate(newDate)
108
- field.onChange(newDate)
109
- }}
110
- autoFocus
111
- className='rounded-lg border'
135
+ <Calendar
136
+ startMonth={toCalendarDay(minDate)}
137
+ endMonth={toCalendarDay(maxDate) ?? new Date(2099, 11)}
138
+ mode='single'
139
+ captionLayout='dropdown'
140
+ selected={date}
141
+ disabled={disabledDays.length > 0 ? disabledDays : undefined}
142
+ onSelect={(newDate) => {
143
+ if (!newDate) {
144
+ setDate(undefined)
145
+ field.onChange(undefined)
146
+ return
147
+ }
148
+
149
+ /**
150
+ * We should preserve the time from the previous date if it is set
151
+ */
152
+ if (input.format !== 'date') {
153
+ const [hours, minutes, seconds] = dayjs(date)
154
+ .format('HH:mm:ss')
155
+ .split(':')
156
+ .map(Number)
157
+ newDate.setHours(hours ?? 0, minutes ?? 0, seconds ?? 0, 0)
158
+ }
159
+
160
+ const boundedDate = clampDateToBounds(newDate, minDate, maxDate)
161
+ setDate(boundedDate)
162
+ field.onChange(boundedDate)
163
+ }}
164
+ autoFocus
165
+ className='rounded-lg border'
112
166
  formatters={{ formatCaption }}
113
167
  />
114
168
  </PopoverContent>
@@ -120,20 +174,23 @@ export default function DateFormInput({ input }: { input: DateFieldClientConfig
120
174
  <Clock2Icon className='text-muted-foreground pointer-events-none absolute left-2.5 size-4 select-none' />
121
175
  <Input
122
176
  type='time'
123
- step='1'
124
- defaultValue={'--:--:--'}
125
- value={date ? dayjs(date).format('HH:mm:ss') : '--:--:--'}
126
- onChange={(e) => {
127
- const time = e.target.value
128
- if (time && date) {
129
- const [hours, minutes, seconds] = time.split(':').map(Number)
130
- const newDate = new Date(date)
131
- newDate.setHours(hours ?? 0, minutes ?? 0, seconds ?? 0, 0)
132
- setDate(newDate)
133
- field.onChange(newDate)
134
- }
135
- }}
136
- className='appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none'
177
+ step='1'
178
+ defaultValue={'--:--:--'}
179
+ value={date ? dayjs(date).format('HH:mm:ss') : '--:--:--'}
180
+ min={getTimeBoundForSelectedDate(date, minDate)}
181
+ max={getTimeBoundForSelectedDate(date, maxDate)}
182
+ onChange={(e) => {
183
+ const time = e.target.value
184
+ if (time && date) {
185
+ const [hours, minutes, seconds] = time.split(':').map(Number)
186
+ const newDate = new Date(date)
187
+ newDate.setHours(hours ?? 0, minutes ?? 0, seconds ?? 0, 0)
188
+ const boundedDate = clampDateToBounds(newDate, minDate, maxDate)
189
+ setDate(boundedDate)
190
+ field.onChange(boundedDate)
191
+ }
192
+ }}
193
+ className='appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none'
137
194
  />
138
195
  </div>
139
196
  ) : null}
@@ -0,0 +1,175 @@
1
+ 'use client'
2
+
3
+ import React from 'react'
4
+ import dayjs from 'dayjs'
5
+ import type { DateRange } from 'react-day-picker'
6
+ import { useController, useFormContext } from 'react-hook-form'
7
+ import { CalendarIcon, ChevronDownIcon } from '@radix-ui/react-icons'
8
+ import { Button } from '@/components/ui/button'
9
+ import { Calendar } from '@/components/ui/calendar'
10
+ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
11
+ import FormInputElement from '@/components/form/FormInputElement'
12
+ import type { DateRangeFieldClientConfig } from 'nextjs-cms/core/fields'
13
+
14
+ const seasonEmoji: Record<string, string> = {
15
+ winter: '⛄️',
16
+ spring: '🌸',
17
+ summer: '🌻',
18
+ autumn: '🍂',
19
+ }
20
+
21
+ function getSeason(month: Date): string {
22
+ const m = month.getMonth()
23
+ if (m < 3) return 'winter'
24
+ if (m < 6) return 'spring'
25
+ if (m < 9) return 'summer'
26
+ return 'autumn'
27
+ }
28
+
29
+ function formatCaption(month: Date) {
30
+ return seasonEmoji[getSeason(month)] + ' ' + dayjs(month).format('MMMM')
31
+ }
32
+
33
+ function resolveBound(value: string | 'now' | undefined): Date | undefined {
34
+ if (!value) return undefined
35
+ if (value === 'now') return new Date()
36
+
37
+ const parsedDate = dayjs(value)
38
+ return parsedDate.isValid() ? parsedDate.toDate() : undefined
39
+ }
40
+
41
+ function toCalendarDay(date: Date | undefined): Date | undefined {
42
+ if (!date) return undefined
43
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate())
44
+ }
45
+
46
+ function toDate(value: any): Date | undefined {
47
+ if (!value) return undefined
48
+ const d = dayjs(value)
49
+ return d.isValid() ? d.toDate() : undefined
50
+ }
51
+
52
+ function formatValue(date: Date | undefined, format: 'date' | 'datetime'): string | undefined {
53
+ if (!date) return undefined
54
+ return format === 'datetime'
55
+ ? dayjs(date).format('YYYY-MM-DD HH:mm:ss')
56
+ : dayjs(date).format('YYYY-MM-DD')
57
+ }
58
+
59
+ export default function DateRangeFormInput({ input }: { input: DateRangeFieldClientConfig }) {
60
+ const { control } = useFormContext()
61
+ const [open, setOpen] = React.useState(false)
62
+ const minDate = resolveBound(input.minDate)
63
+ const maxDate = resolveBound(input.maxDate)
64
+ const disabledDays = [
65
+ ...(minDate ? [{ before: toCalendarDay(minDate) as Date }] : []),
66
+ ...(maxDate ? [{ after: toCalendarDay(maxDate) as Date }] : []),
67
+ ]
68
+
69
+ const {
70
+ field: startField,
71
+ fieldState: { error: startError },
72
+ } = useController({
73
+ name: input.startName,
74
+ control,
75
+ defaultValue: toDate(input.startValue),
76
+ })
77
+
78
+ const {
79
+ field: endField,
80
+ fieldState: { error: endError },
81
+ } = useController({
82
+ name: input.endName,
83
+ control,
84
+ defaultValue: toDate(input.endValue),
85
+ })
86
+
87
+ const [range, setRange] = React.useState<DateRange | undefined>({
88
+ from: toDate(input.startValue),
89
+ to: toDate(input.endValue),
90
+ })
91
+
92
+ function handleSelect(selected: DateRange | undefined) {
93
+ setRange(selected)
94
+ startField.onChange(selected?.from ?? undefined)
95
+ endField.onChange(selected?.to ?? undefined)
96
+ }
97
+
98
+ const error = startError ?? endError
99
+
100
+ return (
101
+ <FormInputElement
102
+ validationError={error}
103
+ value={input.startValue ?? input.endValue}
104
+ readonly={input.readonly}
105
+ label={input.label}
106
+ required={input.required}
107
+ >
108
+ {/* Hidden inputs so FormData picks up both values on submit */}
109
+ <input
110
+ type='hidden'
111
+ name={startField.name}
112
+ value={formatValue(range?.from, input.format) ?? ''}
113
+ ref={startField.ref}
114
+ disabled={startField.disabled}
115
+ />
116
+ <input
117
+ type='hidden'
118
+ name={endField.name}
119
+ value={formatValue(range?.to, input.format) ?? ''}
120
+ ref={endField.ref}
121
+ disabled={endField.disabled}
122
+ />
123
+
124
+ <div className='flex flex-row items-center gap-2'>
125
+ <Popover open={open} onOpenChange={setOpen}>
126
+ <PopoverTrigger asChild>
127
+ <Button variant='outline' className='justify-between font-normal'>
128
+ <div className='flex items-center gap-2'>
129
+ <CalendarIcon />
130
+ {range?.from ? (
131
+ range.to ? (
132
+ <>
133
+ {dayjs(range.from).format('MMM D, YYYY')}
134
+ {' – '}
135
+ {dayjs(range.to).format('MMM D, YYYY')}
136
+ </>
137
+ ) : (
138
+ dayjs(range.from).format('MMM D, YYYY')
139
+ )
140
+ ) : (
141
+ <span>Pick a date range</span>
142
+ )}
143
+ </div>
144
+ <ChevronDownIcon />
145
+ </Button>
146
+ </PopoverTrigger>
147
+ <PopoverContent className='w-auto overflow-hidden p-0' align='start'>
148
+ <Calendar
149
+ mode='range'
150
+ selected={range}
151
+ onSelect={handleSelect}
152
+ numberOfMonths={2}
153
+ defaultMonth={range?.from ?? toCalendarDay(minDate)}
154
+ startMonth={toCalendarDay(minDate)}
155
+ endMonth={toCalendarDay(maxDate)}
156
+ disabled={disabledDays.length > 0 ? disabledDays : undefined}
157
+ autoFocus
158
+ formatters={{ formatCaption }}
159
+ className='rounded-lg border'
160
+ />
161
+ </PopoverContent>
162
+ </Popover>
163
+
164
+ <Button
165
+ type='button'
166
+ variant='default'
167
+ className='px-1.5'
168
+ onClick={() => handleSelect(undefined)}
169
+ >
170
+ Unset
171
+ </Button>
172
+ </div>
173
+ </FormInputElement>
174
+ )
175
+ }
@@ -17,10 +17,10 @@ export default function TagsFormInput({
17
17
  input: TagsFieldClientConfig
18
18
  sectionName: string
19
19
  }) {
20
- const t = useI18n()
21
- const searchParams = useSearchParams()
22
- const localeParam = searchParams.get('locale')
23
- const locale = input.localized ? (localeParam?.trim() ? localeParam : undefined) : undefined
20
+ const t = useI18n()
21
+ const searchParams = useSearchParams()
22
+ const localeParam = searchParams.get('locale')
23
+ const locale = input.localized ? (localeParam?.trim() ? localeParam : undefined) : undefined
24
24
  const { control } = useFormContext()
25
25
  const {
26
26
  field,
@@ -115,7 +115,8 @@ export default function TagsFormInput({
115
115
  }
116
116
  if (event.key === 'Enter' && highlightedIndex >= 0) {
117
117
  event.preventDefault()
118
- addTag(filteredSuggestions[highlightedIndex])
118
+ const suggestion = filteredSuggestions[highlightedIndex]
119
+ if (suggestion !== undefined) addTag(suggestion)
119
120
  return
120
121
  }
121
122
  if (event.key === 'Escape') {
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- import "./.next/dev/types/routes.d.ts";
3
+ import "./.next/types/routes.d.ts";
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -1,10 +1,10 @@
1
1
  {
2
- "name": "cms",
2
+ "name": "nextjs-cms-app",
3
3
  "version": "0.0.1",
4
4
  "private": true,
5
5
  "scripts": {
6
6
  "dev": "next dev",
7
- "build": "next build",
7
+ "build": "cross-env NEXTJS_CMS_DEBUG_OVERRIDE=false next build",
8
8
  "start": "next start",
9
9
  "lint": "next lint",
10
10
  "format": "prettier --check . --ignore-path ../../.gitignore",
@@ -65,7 +65,7 @@
65
65
  "nanoid": "^5.1.2",
66
66
  "next": "16.1.1",
67
67
  "next-themes": "^0.4.6",
68
- "nextjs-cms": "0.9.5",
68
+ "nextjs-cms": "0.9.7",
69
69
  "plaiceholder": "^3.0.0",
70
70
  "prettier-plugin-tailwindcss": "^0.7.2",
71
71
  "qrcode": "^1.5.4",
@@ -1,5 +1,5 @@
1
1
  import { NextRequest } from 'next/server'
2
- import { validateCSRFToken } from 'nextjs-cms/auth/actions'
2
+ import { validateCSRFToken } from 'nextjs-cms/auth/csrf'
3
3
 
4
4
  export async function proxy(request: NextRequest) {
5
5
  /**
@@ -28,5 +28,5 @@ export const config = {
28
28
  * - favicon.ico (favicon file)
29
29
  */
30
30
  '/((?!_next/static|_next/image|favicon.ico).*)',
31
- ]
31
+ ],
32
32
  }
@@ -1,7 +0,0 @@
1
- import DashboardNewPage from '@/components/DashboardNewPage'
2
-
3
- export const dynamic = 'force-dynamic'
4
-
5
- export default async function Page() {
6
- return <DashboardNewPage />
7
- }