create-nextjs-cms 0.7.0 → 0.7.2
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/LICENSE +21 -21
- package/README.md +71 -71
- package/dist/helpers/utils.js +16 -16
- package/dist/lib/section-creators.js +166 -166
- package/package.json +3 -3
- package/templates/default/.eslintrc.json +5 -5
- package/templates/default/.prettierignore +7 -7
- package/templates/default/.prettierrc.json +27 -27
- package/templates/default/CHANGELOG.md +140 -140
- package/templates/default/_gitignore +57 -57
- package/templates/default/app/(auth)/auth/login/LoginPage.tsx +192 -192
- package/templates/default/app/(auth)/auth/login/page.tsx +11 -11
- package/templates/default/app/(auth)/auth-locale-provider.tsx +34 -34
- package/templates/default/app/(auth)/layout.tsx +81 -81
- package/templates/default/app/(rootLayout)/(plugins)/[...slug]/page.tsx +40 -40
- package/templates/default/app/(rootLayout)/(plugins)/[...slug]/plugin-server-registry.ts +22 -22
- package/templates/default/app/(rootLayout)/admins/page.tsx +10 -10
- package/templates/default/app/(rootLayout)/browse/[section]/[page]/page.tsx +22 -22
- package/templates/default/app/(rootLayout)/categorized/[section]/page.tsx +15 -15
- package/templates/default/app/(rootLayout)/dashboard/page.tsx +63 -63
- package/templates/default/app/(rootLayout)/dashboard-new/page.tsx +7 -7
- package/templates/default/app/(rootLayout)/edit/[section]/[itemId]/page.tsx +17 -17
- package/templates/default/app/(rootLayout)/layout.tsx +81 -81
- package/templates/default/app/(rootLayout)/loading.tsx +10 -10
- package/templates/default/app/(rootLayout)/log/page.tsx +7 -7
- package/templates/default/app/(rootLayout)/new/[section]/page.tsx +15 -15
- package/templates/default/app/(rootLayout)/section/[section]/page.tsx +16 -16
- package/templates/default/app/(rootLayout)/settings/page.tsx +13 -13
- package/templates/default/app/_trpc/client.ts +3 -3
- package/templates/default/app/api/auth/csrf/route.ts +25 -25
- package/templates/default/app/api/auth/refresh/route.ts +10 -10
- package/templates/default/app/api/auth/session/route.ts +20 -20
- package/templates/default/app/api/editor/photo/route.ts +49 -49
- package/templates/default/app/api/photo/route.ts +27 -27
- package/templates/default/app/api/submit/section/item/[slug]/route.ts +66 -66
- package/templates/default/app/api/submit/section/item/route.ts +56 -56
- package/templates/default/app/api/submit/section/simple/route.ts +57 -57
- package/templates/default/app/api/trpc/[trpc]/route.ts +33 -33
- package/templates/default/app/api/video/route.ts +174 -174
- package/templates/default/app/globals.css +219 -219
- package/templates/default/app/providers.tsx +152 -152
- package/templates/default/cms.config.ts +49 -52
- package/templates/default/components/AdminCard.tsx +166 -166
- package/templates/default/components/AdminEditPage.tsx +124 -124
- package/templates/default/components/AdminPrivilegeCard.tsx +185 -185
- package/templates/default/components/AdminsPage.tsx +43 -43
- package/templates/default/components/AnalyticsPage.tsx +128 -128
- package/templates/default/components/BarChartBox.tsx +42 -42
- package/templates/default/components/BrowsePage.tsx +106 -106
- package/templates/default/components/CategorizedSectionPage.tsx +31 -31
- package/templates/default/components/CategoryDeleteConfirmPage.tsx +130 -130
- package/templates/default/components/CategorySectionSelectInput.tsx +140 -140
- package/templates/default/components/ConditionalFields.tsx +49 -49
- package/templates/default/components/ContainerBox.tsx +24 -24
- package/templates/default/components/DashboardNewPage.tsx +253 -253
- package/templates/default/components/DashboardPage.tsx +188 -188
- package/templates/default/components/DashboardPageAlt.tsx +45 -45
- package/templates/default/components/DefaultNavItems.tsx +3 -3
- package/templates/default/components/Dropzone.tsx +154 -154
- package/templates/default/components/EmailCard.tsx +138 -138
- package/templates/default/components/EmailPasswordForm.tsx +85 -85
- package/templates/default/components/EmailQuotaForm.tsx +73 -73
- package/templates/default/components/EmailsPage.tsx +49 -49
- package/templates/default/components/ErrorComponent.tsx +16 -16
- package/templates/default/components/GalleryPhoto.tsx +93 -93
- package/templates/default/components/InfoCard.tsx +93 -93
- package/templates/default/components/ItemEditPage.tsx +214 -214
- package/templates/default/components/Layout.tsx +84 -84
- package/templates/default/components/LoadingSpinners.tsx +67 -67
- package/templates/default/components/LogPage.tsx +107 -107
- package/templates/default/components/Modal.tsx +166 -166
- package/templates/default/components/Navbar.tsx +258 -258
- package/templates/default/components/NewAdminForm.tsx +173 -173
- package/templates/default/components/NewEmailForm.tsx +132 -132
- package/templates/default/components/NewPage.tsx +205 -205
- package/templates/default/components/NewVariantComponent.tsx +229 -229
- package/templates/default/components/PhotoGallery.tsx +35 -35
- package/templates/default/components/PieChartBox.tsx +101 -101
- package/templates/default/components/ProgressBar.tsx +48 -48
- package/templates/default/components/ProtectedDocument.tsx +78 -78
- package/templates/default/components/ProtectedImage.tsx +143 -143
- package/templates/default/components/ProtectedVideo.tsx +76 -76
- package/templates/default/components/SectionItemCard.tsx +144 -144
- package/templates/default/components/SectionItemStatusBadge.tsx +17 -17
- package/templates/default/components/SectionPage.tsx +125 -125
- package/templates/default/components/SelectBox.tsx +98 -98
- package/templates/default/components/SelectInputButtons.tsx +125 -125
- package/templates/default/components/SettingsPage.tsx +232 -232
- package/templates/default/components/Sidebar.tsx +201 -201
- package/templates/default/components/SidebarDropdownItem.tsx +80 -80
- package/templates/default/components/SidebarItem.tsx +20 -20
- package/templates/default/components/ThemeProvider.tsx +8 -8
- package/templates/default/components/TooltipComponent.tsx +27 -27
- package/templates/default/components/VariantCard.tsx +124 -124
- package/templates/default/components/VariantEditPage.tsx +230 -230
- package/templates/default/components/analytics/BounceRate.tsx +70 -70
- package/templates/default/components/analytics/LivePageViews.tsx +55 -55
- package/templates/default/components/analytics/LiveUsersCount.tsx +33 -33
- package/templates/default/components/analytics/MonthlyPageViews.tsx +42 -42
- package/templates/default/components/analytics/TopCountries.tsx +52 -52
- package/templates/default/components/analytics/TopDevices.tsx +46 -46
- package/templates/default/components/analytics/TopMediums.tsx +58 -58
- package/templates/default/components/analytics/TopSources.tsx +45 -45
- package/templates/default/components/analytics/TotalPageViews.tsx +41 -41
- package/templates/default/components/analytics/TotalSessions.tsx +41 -41
- package/templates/default/components/analytics/TotalUniqueUsers.tsx +41 -41
- package/templates/default/components/custom/RightHomeRoomVariantCard.tsx +138 -138
- package/templates/default/components/dndKit/Draggable.tsx +21 -21
- package/templates/default/components/dndKit/Droppable.tsx +20 -20
- package/templates/default/components/dndKit/SortableItem.tsx +18 -18
- package/templates/default/components/form/DateRangeFormInput.tsx +57 -57
- package/templates/default/components/form/Form.tsx +317 -317
- package/templates/default/components/form/FormInputElement.tsx +70 -70
- package/templates/default/components/form/FormInputs.tsx +112 -112
- package/templates/default/components/form/helpers/_section-hot-reload.js +1 -1
- package/templates/default/components/form/helpers/util.ts +17 -17
- package/templates/default/components/form/inputs/CheckboxFormInput.tsx +33 -33
- package/templates/default/components/form/inputs/ColorFormInput.tsx +44 -44
- package/templates/default/components/form/inputs/DateFormInput.tsx +156 -156
- package/templates/default/components/form/inputs/DocumentFormInput.tsx +222 -222
- package/templates/default/components/form/inputs/MapFormInput.tsx +140 -140
- package/templates/default/components/form/inputs/MultipleSelectFormInput.tsx +83 -83
- package/templates/default/components/form/inputs/NumberFormInput.tsx +42 -42
- package/templates/default/components/form/inputs/PasswordFormInput.tsx +47 -47
- package/templates/default/components/form/inputs/PhotoFormInput.tsx +219 -219
- package/templates/default/components/form/inputs/RichTextFormInput.tsx +135 -135
- package/templates/default/components/form/inputs/SelectFormInput.tsx +175 -175
- package/templates/default/components/form/inputs/SlugFormInput.tsx +129 -129
- package/templates/default/components/form/inputs/TagsFormInput.tsx +154 -154
- package/templates/default/components/form/inputs/TextFormInput.tsx +48 -48
- package/templates/default/components/form/inputs/TextareaFormInput.tsx +47 -47
- package/templates/default/components/form/inputs/VideoFormInput.tsx +118 -118
- package/templates/default/components/locale-dropdown.tsx +74 -74
- package/templates/default/components/locale-picker.tsx +85 -85
- package/templates/default/components/login-locale-dropdown.tsx +46 -46
- package/templates/default/components/multi-select.tsx +1144 -1144
- package/templates/default/components/pagination/Pagination.tsx +36 -36
- package/templates/default/components/pagination/PaginationButtons.tsx +147 -147
- package/templates/default/components/theme-toggle.tsx +37 -37
- package/templates/default/components/ui/accordion.tsx +53 -53
- package/templates/default/components/ui/alert-dialog.tsx +157 -157
- package/templates/default/components/ui/alert.tsx +46 -46
- package/templates/default/components/ui/badge.tsx +38 -38
- package/templates/default/components/ui/button.tsx +62 -62
- package/templates/default/components/ui/calendar.tsx +166 -166
- package/templates/default/components/ui/card.tsx +43 -43
- package/templates/default/components/ui/checkbox.tsx +29 -29
- package/templates/default/components/ui/command.tsx +137 -137
- package/templates/default/components/ui/custom-alert-dialog.tsx +113 -113
- package/templates/default/components/ui/custom-dialog.tsx +123 -123
- package/templates/default/components/ui/dialog.tsx +123 -123
- package/templates/default/components/ui/dropdown-menu.tsx +182 -182
- package/templates/default/components/ui/input-group.tsx +54 -54
- package/templates/default/components/ui/input.tsx +22 -22
- package/templates/default/components/ui/label.tsx +19 -19
- package/templates/default/components/ui/popover.tsx +42 -42
- package/templates/default/components/ui/progress.tsx +31 -31
- package/templates/default/components/ui/scroll-area.tsx +42 -42
- package/templates/default/components/ui/select.tsx +165 -165
- package/templates/default/components/ui/separator.tsx +28 -28
- package/templates/default/components/ui/sheet.tsx +103 -103
- package/templates/default/components/ui/switch.tsx +29 -29
- package/templates/default/components/ui/table.tsx +83 -83
- package/templates/default/components/ui/tabs.tsx +55 -55
- package/templates/default/components/ui/toast.tsx +113 -113
- package/templates/default/components/ui/toaster.tsx +35 -35
- package/templates/default/components/ui/tooltip.tsx +30 -30
- package/templates/default/components/ui/use-toast.ts +188 -188
- package/templates/default/components.json +21 -21
- package/templates/default/context/ModalProvider.tsx +53 -53
- package/templates/default/drizzle.config.ts +4 -4
- package/templates/default/dynamic-schemas/schema.ts +10 -0
- package/templates/default/env/env.js +130 -130
- package/templates/default/envConfig.ts +4 -4
- package/templates/default/hooks/useModal.ts +8 -8
- package/templates/default/lib/apiHelpers.ts +92 -92
- package/templates/default/lib/postinstall.js +14 -14
- package/templates/default/lib/utils.ts +6 -6
- package/templates/default/next-env.d.ts +6 -6
- package/templates/default/next.config.ts +23 -23
- package/templates/default/package.json +2 -4
- package/templates/default/postcss.config.mjs +6 -6
- package/templates/default/proxy.ts +32 -32
- package/templates/default/tsconfig.json +48 -48
|
@@ -1,192 +1,192 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import React, { useEffect, useRef, useState } from 'react'
|
|
4
|
-
import { useRouter, useSearchParams } from 'next/navigation'
|
|
5
|
-
import { useI18n } from 'nextjs-cms/translations/client'
|
|
6
|
-
import InfoCard from '@/components/InfoCard'
|
|
7
|
-
import Link from 'next/link'
|
|
8
|
-
import { Switch } from '@/components/ui/switch'
|
|
9
|
-
import { Label } from '@/components/ui/label'
|
|
10
|
-
import { Button } from '@/components/ui/button'
|
|
11
|
-
import { MoonIcon, SunIcon } from '@radix-ui/react-icons'
|
|
12
|
-
import { useTheme } from 'next-themes'
|
|
13
|
-
import { login, useSession } from 'nextjs-cms/auth/react'
|
|
14
|
-
import { wasLocaleChangedOnLogin } from 'nextjs-cms/translations'
|
|
15
|
-
import LoginLocaleDropdown from '@/components/login-locale-dropdown'
|
|
16
|
-
import { useAuthLocale } from '../../auth-locale-provider'
|
|
17
|
-
|
|
18
|
-
type fromErrors = {
|
|
19
|
-
username?: string | null
|
|
20
|
-
password?: string | null
|
|
21
|
-
general?: string | null
|
|
22
|
-
[key: string]: any
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default function LoginPage() {
|
|
26
|
-
const { supportedLanguages, fallbackLanguage, initialLocale } = useAuthLocale()
|
|
27
|
-
const t = useI18n()
|
|
28
|
-
const session = useSession()
|
|
29
|
-
const searchParams = useSearchParams()
|
|
30
|
-
const router = useRouter()
|
|
31
|
-
const loginButtonRef = useRef<HTMLButtonElement>(null)
|
|
32
|
-
const formRef = useRef<HTMLFormElement>(null)
|
|
33
|
-
const { theme, setTheme } = useTheme()
|
|
34
|
-
|
|
35
|
-
const handleSubmit = async (event: React.FormEvent<HTMLButtonElement>) => {
|
|
36
|
-
event.preventDefault()
|
|
37
|
-
if (loginButtonRef.current) {
|
|
38
|
-
loginButtonRef.current.disabled = true
|
|
39
|
-
loginButtonRef.current.innerHTML = t('loading')
|
|
40
|
-
}
|
|
41
|
-
try {
|
|
42
|
-
// Only send locale if user explicitly changed it on login page
|
|
43
|
-
// Otherwise, server will use admin's stored DB preference
|
|
44
|
-
const localeChanged = wasLocaleChangedOnLogin()
|
|
45
|
-
await login({
|
|
46
|
-
username: formRef.current?.username.value,
|
|
47
|
-
password: formRef.current?.password.value,
|
|
48
|
-
locale: localeChanged ? initialLocale : undefined,
|
|
49
|
-
})
|
|
50
|
-
} catch (error: any) {
|
|
51
|
-
if (loginButtonRef.current) {
|
|
52
|
-
loginButtonRef.current.disabled = false
|
|
53
|
-
loginButtonRef.current.innerHTML = t('login')
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (!error.message) {
|
|
57
|
-
setFormErrors({
|
|
58
|
-
general: t('serverError'),
|
|
59
|
-
})
|
|
60
|
-
} else {
|
|
61
|
-
setFormErrors({ general: error.message })
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const [formErrors, setFormErrors] = useState<fromErrors>({ username: null, password: null, general: null })
|
|
67
|
-
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
if (session?.status === 'authenticated') {
|
|
70
|
-
const from = searchParams.get('callbackUrl')
|
|
71
|
-
/**
|
|
72
|
-
* Prevent redirect to the same page to prevent infinite loop
|
|
73
|
-
*/
|
|
74
|
-
if (from && !from.includes('auth/login')) {
|
|
75
|
-
router.push(from)
|
|
76
|
-
} else {
|
|
77
|
-
router.push('/')
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}, [session])
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<>
|
|
84
|
-
<div className='bg-background text-foreground fixed top-0 left-0 h-full w-full flex-1 flex-col justify-center overflow-auto px-6 py-12 lg:px-8'>
|
|
85
|
-
<div className='flex w-full items-center justify-end gap-2 absolute end-10 top-10'>
|
|
86
|
-
<LoginLocaleDropdown
|
|
87
|
-
supportedLanguages={supportedLanguages}
|
|
88
|
-
fallbackLanguage={fallbackLanguage}
|
|
89
|
-
initialLocale={initialLocale}
|
|
90
|
-
/>
|
|
91
|
-
<Button
|
|
92
|
-
variant='default'
|
|
93
|
-
onClick={() => {
|
|
94
|
-
setTheme(theme === 'dark' ? 'light' : 'dark')
|
|
95
|
-
}}
|
|
96
|
-
type='button'
|
|
97
|
-
className='rounded-full p-3 focus:outline-hidden'
|
|
98
|
-
>
|
|
99
|
-
<span className='sr-only'>Theme</span>
|
|
100
|
-
<SunIcon className='hidden transition-all dark:block' aria-hidden='true' />
|
|
101
|
-
<MoonIcon className='transition-all dark:hidden' aria-hidden='true' />
|
|
102
|
-
</Button>
|
|
103
|
-
</div>
|
|
104
|
-
<div className='text-center sm:mx-auto sm:w-full sm:max-w-sm'>
|
|
105
|
-
<span className='hover:text-foreground bg-gray-800 px-2 font-bold tracking-tighter text-amber-200 ring-1 ring-sky-500 transition-all duration-150 hover:bg-transparent hover:font-normal hover:tracking-[1em] hover:ring-0'>
|
|
106
|
-
nextjs-cms
|
|
107
|
-
</span>
|
|
108
|
-
<h2 className='mt-10 text-center text-2xl leading-9 font-bold tracking-tight'>
|
|
109
|
-
{t('loginToYourAccount')}
|
|
110
|
-
</h2>
|
|
111
|
-
</div>
|
|
112
|
-
|
|
113
|
-
<div className='mx-auto mt-10 w-full max-w-md rounded-lg bg-linear-to-bl from-sky-50 to-green-50 p-12 shadow-lg ring-2 ring-sky-400 ring-inset dark:from-neutral-950 dark:to-neutral-800'>
|
|
114
|
-
<form ref={formRef} className='space-y-6'>
|
|
115
|
-
<div>
|
|
116
|
-
<label htmlFor='username' className='block text-sm leading-6 font-medium'>
|
|
117
|
-
{t('username')}
|
|
118
|
-
</label>
|
|
119
|
-
<div>
|
|
120
|
-
<input
|
|
121
|
-
id='username'
|
|
122
|
-
name='username'
|
|
123
|
-
type='text'
|
|
124
|
-
placeholder={t('username')}
|
|
125
|
-
required
|
|
126
|
-
className='bg-background text-foreground block w-full rounded-md border-0 p-3 shadow-xs ring-1 ring-gray-300 outline-0 ring-inset placeholder:text-gray-400 focus:ring-2 focus:ring-indigo-600 focus:ring-inset sm:text-sm sm:leading-6'
|
|
127
|
-
/>
|
|
128
|
-
</div>
|
|
129
|
-
</div>
|
|
130
|
-
|
|
131
|
-
<div>
|
|
132
|
-
<label htmlFor='password' className='block text-sm leading-6 font-medium'>
|
|
133
|
-
{t('password')}
|
|
134
|
-
</label>
|
|
135
|
-
<div>
|
|
136
|
-
<input
|
|
137
|
-
id='password'
|
|
138
|
-
name='password'
|
|
139
|
-
type='password'
|
|
140
|
-
placeholder={t('password')}
|
|
141
|
-
required
|
|
142
|
-
className='bg-background text-foreground block w-full rounded-md border-0 p-3 shadow-xs ring-1 ring-sky-300 outline-0 ring-inset placeholder:text-gray-400 focus:ring-2 focus:ring-indigo-600 focus:ring-inset sm:text-sm sm:leading-6'
|
|
143
|
-
/>
|
|
144
|
-
</div>
|
|
145
|
-
<div className='mt-2 text-end'>
|
|
146
|
-
<div className='text-sm'>
|
|
147
|
-
<Link href='#' className='font-semibold text-indigo-600 hover:text-indigo-500'>
|
|
148
|
-
{t('forgotPassword')}
|
|
149
|
-
</Link>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
<div>
|
|
154
|
-
<div className='flex items-center justify-between'>
|
|
155
|
-
<div className='flex gap-2'>
|
|
156
|
-
<div className='flex items-center space-x-2'>
|
|
157
|
-
<Switch id='remember_me_check' name='rm' />
|
|
158
|
-
<Label htmlFor='remember_me_check'>{t('rememberMe')}</Label>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
<div className='mt-2'></div>
|
|
163
|
-
</div>
|
|
164
|
-
|
|
165
|
-
<div>
|
|
166
|
-
<Button
|
|
167
|
-
ref={loginButtonRef}
|
|
168
|
-
onClick={handleSubmit}
|
|
169
|
-
type='submit'
|
|
170
|
-
className='flex w-full justify-center rounded-md bg-linear-to-r from-green-400 to-blue-500 font-extrabold hover:to-sky-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 focus-visible:outline-solid'
|
|
171
|
-
>
|
|
172
|
-
{t('login')}
|
|
173
|
-
</Button>
|
|
174
|
-
</div>
|
|
175
|
-
</form>
|
|
176
|
-
{Object.keys(formErrors).map((key, index) => {
|
|
177
|
-
if (formErrors[key]) {
|
|
178
|
-
return (
|
|
179
|
-
<div className='pt-3' key={index}>
|
|
180
|
-
<InfoCard
|
|
181
|
-
key={index}
|
|
182
|
-
result={{ key: 'danger', status: false, title: formErrors[key] }}
|
|
183
|
-
/>
|
|
184
|
-
</div>
|
|
185
|
-
)
|
|
186
|
-
}
|
|
187
|
-
})}
|
|
188
|
-
</div>
|
|
189
|
-
</div>
|
|
190
|
-
</>
|
|
191
|
-
)
|
|
192
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useRef, useState } from 'react'
|
|
4
|
+
import { useRouter, useSearchParams } from 'next/navigation'
|
|
5
|
+
import { useI18n } from 'nextjs-cms/translations/client'
|
|
6
|
+
import InfoCard from '@/components/InfoCard'
|
|
7
|
+
import Link from 'next/link'
|
|
8
|
+
import { Switch } from '@/components/ui/switch'
|
|
9
|
+
import { Label } from '@/components/ui/label'
|
|
10
|
+
import { Button } from '@/components/ui/button'
|
|
11
|
+
import { MoonIcon, SunIcon } from '@radix-ui/react-icons'
|
|
12
|
+
import { useTheme } from 'next-themes'
|
|
13
|
+
import { login, useSession } from 'nextjs-cms/auth/react'
|
|
14
|
+
import { wasLocaleChangedOnLogin } from 'nextjs-cms/translations'
|
|
15
|
+
import LoginLocaleDropdown from '@/components/login-locale-dropdown'
|
|
16
|
+
import { useAuthLocale } from '../../auth-locale-provider'
|
|
17
|
+
|
|
18
|
+
type fromErrors = {
|
|
19
|
+
username?: string | null
|
|
20
|
+
password?: string | null
|
|
21
|
+
general?: string | null
|
|
22
|
+
[key: string]: any
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function LoginPage() {
|
|
26
|
+
const { supportedLanguages, fallbackLanguage, initialLocale } = useAuthLocale()
|
|
27
|
+
const t = useI18n()
|
|
28
|
+
const session = useSession()
|
|
29
|
+
const searchParams = useSearchParams()
|
|
30
|
+
const router = useRouter()
|
|
31
|
+
const loginButtonRef = useRef<HTMLButtonElement>(null)
|
|
32
|
+
const formRef = useRef<HTMLFormElement>(null)
|
|
33
|
+
const { theme, setTheme } = useTheme()
|
|
34
|
+
|
|
35
|
+
const handleSubmit = async (event: React.FormEvent<HTMLButtonElement>) => {
|
|
36
|
+
event.preventDefault()
|
|
37
|
+
if (loginButtonRef.current) {
|
|
38
|
+
loginButtonRef.current.disabled = true
|
|
39
|
+
loginButtonRef.current.innerHTML = t('loading')
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
// Only send locale if user explicitly changed it on login page
|
|
43
|
+
// Otherwise, server will use admin's stored DB preference
|
|
44
|
+
const localeChanged = wasLocaleChangedOnLogin()
|
|
45
|
+
await login({
|
|
46
|
+
username: formRef.current?.username.value,
|
|
47
|
+
password: formRef.current?.password.value,
|
|
48
|
+
locale: localeChanged ? initialLocale : undefined,
|
|
49
|
+
})
|
|
50
|
+
} catch (error: any) {
|
|
51
|
+
if (loginButtonRef.current) {
|
|
52
|
+
loginButtonRef.current.disabled = false
|
|
53
|
+
loginButtonRef.current.innerHTML = t('login')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!error.message) {
|
|
57
|
+
setFormErrors({
|
|
58
|
+
general: t('serverError'),
|
|
59
|
+
})
|
|
60
|
+
} else {
|
|
61
|
+
setFormErrors({ general: error.message })
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const [formErrors, setFormErrors] = useState<fromErrors>({ username: null, password: null, general: null })
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (session?.status === 'authenticated') {
|
|
70
|
+
const from = searchParams.get('callbackUrl')
|
|
71
|
+
/**
|
|
72
|
+
* Prevent redirect to the same page to prevent infinite loop
|
|
73
|
+
*/
|
|
74
|
+
if (from && !from.includes('auth/login')) {
|
|
75
|
+
router.push(from)
|
|
76
|
+
} else {
|
|
77
|
+
router.push('/')
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}, [session])
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<>
|
|
84
|
+
<div className='bg-background text-foreground fixed top-0 left-0 h-full w-full flex-1 flex-col justify-center overflow-auto px-6 py-12 lg:px-8'>
|
|
85
|
+
<div className='flex w-full items-center justify-end gap-2 absolute end-10 top-10'>
|
|
86
|
+
<LoginLocaleDropdown
|
|
87
|
+
supportedLanguages={supportedLanguages}
|
|
88
|
+
fallbackLanguage={fallbackLanguage}
|
|
89
|
+
initialLocale={initialLocale}
|
|
90
|
+
/>
|
|
91
|
+
<Button
|
|
92
|
+
variant='default'
|
|
93
|
+
onClick={() => {
|
|
94
|
+
setTheme(theme === 'dark' ? 'light' : 'dark')
|
|
95
|
+
}}
|
|
96
|
+
type='button'
|
|
97
|
+
className='rounded-full p-3 focus:outline-hidden'
|
|
98
|
+
>
|
|
99
|
+
<span className='sr-only'>Theme</span>
|
|
100
|
+
<SunIcon className='hidden transition-all dark:block' aria-hidden='true' />
|
|
101
|
+
<MoonIcon className='transition-all dark:hidden' aria-hidden='true' />
|
|
102
|
+
</Button>
|
|
103
|
+
</div>
|
|
104
|
+
<div className='text-center sm:mx-auto sm:w-full sm:max-w-sm'>
|
|
105
|
+
<span className='hover:text-foreground bg-gray-800 px-2 font-bold tracking-tighter text-amber-200 ring-1 ring-sky-500 transition-all duration-150 hover:bg-transparent hover:font-normal hover:tracking-[1em] hover:ring-0'>
|
|
106
|
+
nextjs-cms
|
|
107
|
+
</span>
|
|
108
|
+
<h2 className='mt-10 text-center text-2xl leading-9 font-bold tracking-tight'>
|
|
109
|
+
{t('loginToYourAccount')}
|
|
110
|
+
</h2>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div className='mx-auto mt-10 w-full max-w-md rounded-lg bg-linear-to-bl from-sky-50 to-green-50 p-12 shadow-lg ring-2 ring-sky-400 ring-inset dark:from-neutral-950 dark:to-neutral-800'>
|
|
114
|
+
<form ref={formRef} className='space-y-6'>
|
|
115
|
+
<div>
|
|
116
|
+
<label htmlFor='username' className='block text-sm leading-6 font-medium'>
|
|
117
|
+
{t('username')}
|
|
118
|
+
</label>
|
|
119
|
+
<div>
|
|
120
|
+
<input
|
|
121
|
+
id='username'
|
|
122
|
+
name='username'
|
|
123
|
+
type='text'
|
|
124
|
+
placeholder={t('username')}
|
|
125
|
+
required
|
|
126
|
+
className='bg-background text-foreground block w-full rounded-md border-0 p-3 shadow-xs ring-1 ring-gray-300 outline-0 ring-inset placeholder:text-gray-400 focus:ring-2 focus:ring-indigo-600 focus:ring-inset sm:text-sm sm:leading-6'
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div>
|
|
132
|
+
<label htmlFor='password' className='block text-sm leading-6 font-medium'>
|
|
133
|
+
{t('password')}
|
|
134
|
+
</label>
|
|
135
|
+
<div>
|
|
136
|
+
<input
|
|
137
|
+
id='password'
|
|
138
|
+
name='password'
|
|
139
|
+
type='password'
|
|
140
|
+
placeholder={t('password')}
|
|
141
|
+
required
|
|
142
|
+
className='bg-background text-foreground block w-full rounded-md border-0 p-3 shadow-xs ring-1 ring-sky-300 outline-0 ring-inset placeholder:text-gray-400 focus:ring-2 focus:ring-indigo-600 focus:ring-inset sm:text-sm sm:leading-6'
|
|
143
|
+
/>
|
|
144
|
+
</div>
|
|
145
|
+
<div className='mt-2 text-end'>
|
|
146
|
+
<div className='text-sm'>
|
|
147
|
+
<Link href='#' className='font-semibold text-indigo-600 hover:text-indigo-500'>
|
|
148
|
+
{t('forgotPassword')}
|
|
149
|
+
</Link>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
<div>
|
|
154
|
+
<div className='flex items-center justify-between'>
|
|
155
|
+
<div className='flex gap-2'>
|
|
156
|
+
<div className='flex items-center space-x-2'>
|
|
157
|
+
<Switch id='remember_me_check' name='rm' />
|
|
158
|
+
<Label htmlFor='remember_me_check'>{t('rememberMe')}</Label>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
<div className='mt-2'></div>
|
|
163
|
+
</div>
|
|
164
|
+
|
|
165
|
+
<div>
|
|
166
|
+
<Button
|
|
167
|
+
ref={loginButtonRef}
|
|
168
|
+
onClick={handleSubmit}
|
|
169
|
+
type='submit'
|
|
170
|
+
className='flex w-full justify-center rounded-md bg-linear-to-r from-green-400 to-blue-500 font-extrabold hover:to-sky-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 focus-visible:outline-solid'
|
|
171
|
+
>
|
|
172
|
+
{t('login')}
|
|
173
|
+
</Button>
|
|
174
|
+
</div>
|
|
175
|
+
</form>
|
|
176
|
+
{Object.keys(formErrors).map((key, index) => {
|
|
177
|
+
if (formErrors[key]) {
|
|
178
|
+
return (
|
|
179
|
+
<div className='pt-3' key={index}>
|
|
180
|
+
<InfoCard
|
|
181
|
+
key={index}
|
|
182
|
+
result={{ key: 'danger', status: false, title: formErrors[key] }}
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
})}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import LoginPage from './LoginPage'
|
|
2
|
-
import auth from 'nextjs-cms/auth'
|
|
3
|
-
import { redirect } from 'next/navigation'
|
|
4
|
-
|
|
5
|
-
export default async function Page() {
|
|
6
|
-
const session = await auth()
|
|
7
|
-
if (session) {
|
|
8
|
-
redirect('/')
|
|
9
|
-
}
|
|
10
|
-
return <LoginPage />
|
|
11
|
-
}
|
|
1
|
+
import LoginPage from './LoginPage'
|
|
2
|
+
import auth from 'nextjs-cms/auth'
|
|
3
|
+
import { redirect } from 'next/navigation'
|
|
4
|
+
|
|
5
|
+
export default async function Page() {
|
|
6
|
+
const session = await auth()
|
|
7
|
+
if (session) {
|
|
8
|
+
redirect('/')
|
|
9
|
+
}
|
|
10
|
+
return <LoginPage />
|
|
11
|
+
}
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { createContext, useContext, type ReactNode } from 'react'
|
|
4
|
-
|
|
5
|
-
export interface AuthLocaleValue {
|
|
6
|
-
supportedLanguages: readonly string[]
|
|
7
|
-
fallbackLanguage: string
|
|
8
|
-
initialLocale: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const AuthLocaleContext = createContext<AuthLocaleValue | null>(null)
|
|
12
|
-
|
|
13
|
-
export function AuthLocaleProvider({
|
|
14
|
-
supportedLanguages,
|
|
15
|
-
fallbackLanguage,
|
|
16
|
-
initialLocale,
|
|
17
|
-
children,
|
|
18
|
-
}: AuthLocaleValue & { children: ReactNode }) {
|
|
19
|
-
return (
|
|
20
|
-
<AuthLocaleContext.Provider
|
|
21
|
-
value={{ supportedLanguages, fallbackLanguage, initialLocale }}
|
|
22
|
-
>
|
|
23
|
-
{children}
|
|
24
|
-
</AuthLocaleContext.Provider>
|
|
25
|
-
)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function useAuthLocale(): AuthLocaleValue {
|
|
29
|
-
const ctx = useContext(AuthLocaleContext)
|
|
30
|
-
if (!ctx) {
|
|
31
|
-
throw new Error('useAuthLocale must be used within AuthLocaleProvider')
|
|
32
|
-
}
|
|
33
|
-
return ctx
|
|
34
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, type ReactNode } from 'react'
|
|
4
|
+
|
|
5
|
+
export interface AuthLocaleValue {
|
|
6
|
+
supportedLanguages: readonly string[]
|
|
7
|
+
fallbackLanguage: string
|
|
8
|
+
initialLocale: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const AuthLocaleContext = createContext<AuthLocaleValue | null>(null)
|
|
12
|
+
|
|
13
|
+
export function AuthLocaleProvider({
|
|
14
|
+
supportedLanguages,
|
|
15
|
+
fallbackLanguage,
|
|
16
|
+
initialLocale,
|
|
17
|
+
children,
|
|
18
|
+
}: AuthLocaleValue & { children: ReactNode }) {
|
|
19
|
+
return (
|
|
20
|
+
<AuthLocaleContext.Provider
|
|
21
|
+
value={{ supportedLanguages, fallbackLanguage, initialLocale }}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
</AuthLocaleContext.Provider>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useAuthLocale(): AuthLocaleValue {
|
|
29
|
+
const ctx = useContext(AuthLocaleContext)
|
|
30
|
+
if (!ctx) {
|
|
31
|
+
throw new Error('useAuthLocale must be used within AuthLocaleProvider')
|
|
32
|
+
}
|
|
33
|
+
return ctx
|
|
34
|
+
}
|
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
import { Inter, Cairo } from 'next/font/google'
|
|
2
|
-
import { cookies } from 'next/headers'
|
|
3
|
-
import '../globals.css'
|
|
4
|
-
import type { Metadata } from 'next'
|
|
5
|
-
import { cn } from '@/lib/utils'
|
|
6
|
-
import { ThemeProvider } from '@/components/ThemeProvider'
|
|
7
|
-
import Providers from '@/app/providers'
|
|
8
|
-
import auth from 'nextjs-cms/auth'
|
|
9
|
-
import { getCMSConfig } from 'nextjs-cms/core'
|
|
10
|
-
import { I18nProviderClient } from 'nextjs-cms/translations/client'
|
|
11
|
-
import { resolveLocale, RTL_LOCALES, LOCALE_COOKIE_NAME } from 'nextjs-cms/translations'
|
|
12
|
-
import { getClientDictionaries } from 'nextjs-cms/translations/server'
|
|
13
|
-
import { redirect } from 'next/navigation'
|
|
14
|
-
import { AuthLocaleProvider } from './auth-locale-provider'
|
|
15
|
-
import { DirectionProvider } from "@/components/ui/direction"
|
|
16
|
-
|
|
17
|
-
const inter = Inter({
|
|
18
|
-
subsets: ['latin'],
|
|
19
|
-
variable: '--font-sans',
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
const cairo = Cairo({
|
|
23
|
-
subsets: ['arabic', 'latin'],
|
|
24
|
-
variable: '--font-cairo',
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
export async function generateMetadata(): Promise<Metadata> {
|
|
28
|
-
const cmsConfig = await getCMSConfig()
|
|
29
|
-
return {
|
|
30
|
-
title: cmsConfig.ui.title,
|
|
31
|
-
description: 'nextjs-cms',
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
36
|
-
const [session, cmsConfig] = await Promise.all([
|
|
37
|
-
auth(),
|
|
38
|
-
getCMSConfig(),
|
|
39
|
-
])
|
|
40
|
-
|
|
41
|
-
if (session) {
|
|
42
|
-
redirect('/')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const { supportedLanguages, fallbackLanguage } = cmsConfig.i18n
|
|
46
|
-
const cookieStore = await cookies()
|
|
47
|
-
const cookieLocale = cookieStore.get(LOCALE_COOKIE_NAME)?.value
|
|
48
|
-
const locale = resolveLocale(cookieLocale, supportedLanguages, fallbackLanguage)
|
|
49
|
-
const isRTL = RTL_LOCALES.has(locale)
|
|
50
|
-
const dictionaries = getClientDictionaries(supportedLanguages)
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<html lang={locale} dir={isRTL ? 'rtl' : 'ltr'} suppressHydrationWarning>
|
|
54
|
-
<body
|
|
55
|
-
className={cn(
|
|
56
|
-
'bg-background min-h-screen font-sans antialiased',
|
|
57
|
-
isRTL ? cairo.variable : inter.variable,
|
|
58
|
-
)}
|
|
59
|
-
>
|
|
60
|
-
<DirectionProvider dir={isRTL ? 'rtl' : 'ltr'} direction={isRTL ? 'rtl' : 'ltr'}>
|
|
61
|
-
<I18nProviderClient locale={locale} dictionaries={dictionaries}>
|
|
62
|
-
<ThemeProvider
|
|
63
|
-
attribute='class'
|
|
64
|
-
defaultTheme={cmsConfig.ui.defaultTheme}
|
|
65
|
-
enableSystem
|
|
66
|
-
disableTransitionOnChange
|
|
67
|
-
>
|
|
68
|
-
<AuthLocaleProvider
|
|
69
|
-
supportedLanguages={supportedLanguages}
|
|
70
|
-
fallbackLanguage={fallbackLanguage}
|
|
71
|
-
initialLocale={locale}
|
|
72
|
-
>
|
|
73
|
-
<Providers session={undefined}>{children}</Providers>
|
|
74
|
-
</AuthLocaleProvider>
|
|
75
|
-
</ThemeProvider>
|
|
76
|
-
</I18nProviderClient>
|
|
77
|
-
</DirectionProvider>
|
|
78
|
-
</body>
|
|
79
|
-
</html>
|
|
80
|
-
)
|
|
81
|
-
}
|
|
1
|
+
import { Inter, Cairo } from 'next/font/google'
|
|
2
|
+
import { cookies } from 'next/headers'
|
|
3
|
+
import '../globals.css'
|
|
4
|
+
import type { Metadata } from 'next'
|
|
5
|
+
import { cn } from '@/lib/utils'
|
|
6
|
+
import { ThemeProvider } from '@/components/ThemeProvider'
|
|
7
|
+
import Providers from '@/app/providers'
|
|
8
|
+
import auth from 'nextjs-cms/auth'
|
|
9
|
+
import { getCMSConfig } from 'nextjs-cms/core'
|
|
10
|
+
import { I18nProviderClient } from 'nextjs-cms/translations/client'
|
|
11
|
+
import { resolveLocale, RTL_LOCALES, LOCALE_COOKIE_NAME } from 'nextjs-cms/translations'
|
|
12
|
+
import { getClientDictionaries } from 'nextjs-cms/translations/server'
|
|
13
|
+
import { redirect } from 'next/navigation'
|
|
14
|
+
import { AuthLocaleProvider } from './auth-locale-provider'
|
|
15
|
+
import { DirectionProvider } from "@/components/ui/direction"
|
|
16
|
+
|
|
17
|
+
const inter = Inter({
|
|
18
|
+
subsets: ['latin'],
|
|
19
|
+
variable: '--font-sans',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const cairo = Cairo({
|
|
23
|
+
subsets: ['arabic', 'latin'],
|
|
24
|
+
variable: '--font-cairo',
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
28
|
+
const cmsConfig = await getCMSConfig()
|
|
29
|
+
return {
|
|
30
|
+
title: cmsConfig.ui.title,
|
|
31
|
+
description: 'nextjs-cms',
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
36
|
+
const [session, cmsConfig] = await Promise.all([
|
|
37
|
+
auth(),
|
|
38
|
+
getCMSConfig(),
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
if (session) {
|
|
42
|
+
redirect('/')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { supportedLanguages, fallbackLanguage } = cmsConfig.i18n
|
|
46
|
+
const cookieStore = await cookies()
|
|
47
|
+
const cookieLocale = cookieStore.get(LOCALE_COOKIE_NAME)?.value
|
|
48
|
+
const locale = resolveLocale(cookieLocale, supportedLanguages, fallbackLanguage)
|
|
49
|
+
const isRTL = RTL_LOCALES.has(locale)
|
|
50
|
+
const dictionaries = getClientDictionaries(supportedLanguages)
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<html lang={locale} dir={isRTL ? 'rtl' : 'ltr'} suppressHydrationWarning>
|
|
54
|
+
<body
|
|
55
|
+
className={cn(
|
|
56
|
+
'bg-background min-h-screen font-sans antialiased',
|
|
57
|
+
isRTL ? cairo.variable : inter.variable,
|
|
58
|
+
)}
|
|
59
|
+
>
|
|
60
|
+
<DirectionProvider dir={isRTL ? 'rtl' : 'ltr'} direction={isRTL ? 'rtl' : 'ltr'}>
|
|
61
|
+
<I18nProviderClient locale={locale} dictionaries={dictionaries}>
|
|
62
|
+
<ThemeProvider
|
|
63
|
+
attribute='class'
|
|
64
|
+
defaultTheme={cmsConfig.ui.defaultTheme}
|
|
65
|
+
enableSystem
|
|
66
|
+
disableTransitionOnChange
|
|
67
|
+
>
|
|
68
|
+
<AuthLocaleProvider
|
|
69
|
+
supportedLanguages={supportedLanguages}
|
|
70
|
+
fallbackLanguage={fallbackLanguage}
|
|
71
|
+
initialLocale={locale}
|
|
72
|
+
>
|
|
73
|
+
<Providers session={undefined}>{children}</Providers>
|
|
74
|
+
</AuthLocaleProvider>
|
|
75
|
+
</ThemeProvider>
|
|
76
|
+
</I18nProviderClient>
|
|
77
|
+
</DirectionProvider>
|
|
78
|
+
</body>
|
|
79
|
+
</html>
|
|
80
|
+
)
|
|
81
|
+
}
|