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.
- package/dist/helpers/check-directory.d.ts +1 -0
- package/dist/helpers/check-directory.d.ts.map +1 -1
- package/dist/helpers/check-directory.js +98 -23
- package/dist/index.js +13 -8
- package/dist/lib/create-project.js +1 -1
- package/package.json +3 -3
- package/templates/default/app/(auth)/auth-language-provider.tsx +34 -34
- package/templates/default/components/AnalyticsPage.tsx +22 -6
- package/templates/default/components/CategorySectionSelectInput.tsx +2 -2
- package/templates/default/components/form/Form.tsx +12 -2
- package/templates/default/components/form/FormInputs.tsx +25 -16
- package/templates/default/components/form/inputs/DateFormInput.tsx +110 -53
- package/templates/default/components/form/inputs/DateRangeFormInput.tsx +175 -0
- package/templates/default/components/form/inputs/TagsFormInput.tsx +6 -5
- package/templates/default/next-env.d.ts +1 -1
- package/templates/default/package.json +3 -3
- package/templates/default/proxy.ts +2 -2
- 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
|
@@ -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 {
|
|
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
|
|
31
|
-
const season = getSeason(month)
|
|
32
|
-
return seasonEmoji[season] + ' ' + dayjs(month).format('MMMM')
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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/
|
|
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.
|
|
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/
|
|
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
|
}
|