create-nextjs-cms 0.5.91 → 0.5.93
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/package.json +1 -1
- package/templates/default/app/(auth)/auth/login/LoginPage.tsx +13 -12
- package/templates/default/app/(auth)/layout.tsx +56 -0
- package/templates/default/app/(rootLayout)/layout.tsx +58 -8
- package/templates/default/app/globals.css +1 -1
- package/templates/default/components/AdminCard.tsx +16 -15
- package/templates/default/components/AdminEditPage.tsx +8 -7
- package/templates/default/components/AdminPrivilegeCard.tsx +12 -11
- package/templates/default/components/AdminsPage.tsx +5 -4
- package/templates/default/components/AnalyticsPage.tsx +8 -7
- package/templates/default/components/BrowsePage.tsx +7 -6
- package/templates/default/components/CategoryDeleteConfirmPage.tsx +12 -11
- package/templates/default/components/CategorySectionSelectInput.tsx +3 -2
- package/templates/default/components/DashboardNewPage.tsx +40 -39
- package/templates/default/components/DashboardPage.tsx +34 -33
- package/templates/default/components/DashboardPageAlt.tsx +3 -1
- package/templates/default/components/Dropzone.tsx +8 -7
- package/templates/default/components/EmailCard.tsx +12 -11
- package/templates/default/components/EmailPasswordForm.tsx +7 -6
- package/templates/default/components/EmailQuotaForm.tsx +7 -6
- package/templates/default/components/EmailsPage.tsx +6 -5
- package/templates/default/components/GalleryPhoto.tsx +6 -5
- package/templates/default/components/ItemEditPage.tsx +5 -4
- package/templates/default/components/Layout.tsx +1 -1
- package/templates/default/components/LoadingSpinners.tsx +1 -1
- package/templates/default/components/LogPage.tsx +24 -9
- package/templates/default/components/Navbar.tsx +14 -13
- package/templates/default/components/NewAdminForm.tsx +12 -11
- package/templates/default/components/NewEmailForm.tsx +11 -10
- package/templates/default/components/NewPage.tsx +6 -5
- package/templates/default/components/NewVariantComponent.tsx +6 -5
- package/templates/default/components/PhotoGallery.tsx +4 -3
- package/templates/default/components/SectionItemCard.tsx +8 -8
- package/templates/default/components/SectionItemStatusBadge.tsx +3 -2
- package/templates/default/components/SectionPage.tsx +6 -5
- package/templates/default/components/SelectInputButtons.tsx +8 -7
- package/templates/default/components/SettingsPage.tsx +18 -17
- package/templates/default/components/Sidebar.tsx +23 -15
- package/templates/default/components/SidebarDropdownItem.tsx +11 -5
- package/templates/default/components/SidebarItem.tsx +1 -0
- package/templates/default/components/VariantCard.tsx +8 -8
- package/templates/default/components/VariantEditPage.tsx +5 -4
- package/templates/default/components/analytics/BounceRate.tsx +6 -5
- package/templates/default/components/analytics/LivePageViews.tsx +8 -7
- package/templates/default/components/analytics/LiveUsersCount.tsx +3 -2
- package/templates/default/components/analytics/MonthlyPageViews.tsx +3 -2
- package/templates/default/components/analytics/TopCountries.tsx +4 -3
- package/templates/default/components/analytics/TopDevices.tsx +4 -3
- package/templates/default/components/analytics/TopMediums.tsx +4 -3
- package/templates/default/components/analytics/TopSources.tsx +4 -3
- package/templates/default/components/analytics/TotalPageViews.tsx +3 -2
- package/templates/default/components/analytics/TotalSessions.tsx +3 -2
- package/templates/default/components/analytics/TotalUniqueUsers.tsx +3 -2
- package/templates/default/components/custom/RightHomeRoomVariantCard.tsx +9 -8
- package/templates/default/components/form/Form.tsx +5 -4
- package/templates/default/components/form/FormInputElement.tsx +3 -1
- package/templates/default/components/form/helpers/_section-hot-reload.js +1 -1
- package/templates/default/components/form/inputs/DateFormInput.tsx +3 -2
- package/templates/default/components/form/inputs/DocumentFormInput.tsx +16 -15
- package/templates/default/components/form/inputs/MapFormInput.tsx +5 -4
- package/templates/default/components/form/inputs/PhotoFormInput.tsx +16 -15
- package/templates/default/components/form/inputs/SelectFormInput.tsx +3 -2
- package/templates/default/components/form/inputs/TagsFormInput.tsx +3 -2
- package/templates/default/components/form/inputs/VideoFormInput.tsx +7 -6
- package/templates/default/components/pagination/PaginationButtons.tsx +8 -6
- package/templates/default/components/theme-toggle.tsx +1 -1
- package/templates/default/components/ui/spinner.tsx +16 -0
- package/templates/default/package.json +2 -2
- package/templates/default/app/layout.tsx +0 -40
- package/templates/default/components/spinner.tsx +0 -11
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useEffect, useRef, useState } from 'react'
|
|
4
4
|
import { useRouter, useSearchParams } from 'next/navigation'
|
|
5
|
-
import
|
|
5
|
+
import { useI18n } from 'nextjs-cms/translations/client'
|
|
6
6
|
import InfoCard from '@/components/InfoCard'
|
|
7
7
|
import Link from 'next/link'
|
|
8
8
|
import { Switch } from '@/components/ui/switch'
|
|
@@ -20,6 +20,7 @@ type fromErrors = {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export default function LoginPage() {
|
|
23
|
+
const t = useI18n()
|
|
23
24
|
const session = useSession()
|
|
24
25
|
const searchParams = useSearchParams()
|
|
25
26
|
const router = useRouter()
|
|
@@ -31,7 +32,7 @@ export default function LoginPage() {
|
|
|
31
32
|
event.preventDefault()
|
|
32
33
|
if (loginButtonRef.current) {
|
|
33
34
|
loginButtonRef.current.disabled = true
|
|
34
|
-
loginButtonRef.current.innerHTML =
|
|
35
|
+
loginButtonRef.current.innerHTML = t('loading')
|
|
35
36
|
}
|
|
36
37
|
try {
|
|
37
38
|
await login({
|
|
@@ -41,12 +42,12 @@ export default function LoginPage() {
|
|
|
41
42
|
} catch (error: any) {
|
|
42
43
|
if (loginButtonRef.current) {
|
|
43
44
|
loginButtonRef.current.disabled = false
|
|
44
|
-
loginButtonRef.current.innerHTML =
|
|
45
|
+
loginButtonRef.current.innerHTML = t('login')
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
if (!error.message) {
|
|
48
49
|
setFormErrors({
|
|
49
|
-
general:
|
|
50
|
+
general: t('serverError'),
|
|
50
51
|
})
|
|
51
52
|
} else {
|
|
52
53
|
setFormErrors({ general: error.message })
|
|
@@ -92,7 +93,7 @@ export default function LoginPage() {
|
|
|
92
93
|
nextjs-cms
|
|
93
94
|
</span>
|
|
94
95
|
<h2 className='mt-10 text-center text-2xl leading-9 font-bold tracking-tight'>
|
|
95
|
-
{
|
|
96
|
+
{t('loginToYourAccount')}
|
|
96
97
|
</h2>
|
|
97
98
|
</div>
|
|
98
99
|
|
|
@@ -100,14 +101,14 @@ export default function LoginPage() {
|
|
|
100
101
|
<form ref={formRef} className='space-y-6'>
|
|
101
102
|
<div>
|
|
102
103
|
<label htmlFor='username' className='block text-sm leading-6 font-medium'>
|
|
103
|
-
{
|
|
104
|
+
{t('username')}
|
|
104
105
|
</label>
|
|
105
106
|
<div>
|
|
106
107
|
<input
|
|
107
108
|
id='username'
|
|
108
109
|
name='username'
|
|
109
110
|
type='text'
|
|
110
|
-
placeholder={
|
|
111
|
+
placeholder={t('username')}
|
|
111
112
|
required
|
|
112
113
|
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'
|
|
113
114
|
/>
|
|
@@ -116,14 +117,14 @@ export default function LoginPage() {
|
|
|
116
117
|
|
|
117
118
|
<div>
|
|
118
119
|
<label htmlFor='password' className='block text-sm leading-6 font-medium'>
|
|
119
|
-
{
|
|
120
|
+
{t('password')}
|
|
120
121
|
</label>
|
|
121
122
|
<div>
|
|
122
123
|
<input
|
|
123
124
|
id='password'
|
|
124
125
|
name='password'
|
|
125
126
|
type='password'
|
|
126
|
-
placeholder={
|
|
127
|
+
placeholder={t('password')}
|
|
127
128
|
required
|
|
128
129
|
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'
|
|
129
130
|
/>
|
|
@@ -131,7 +132,7 @@ export default function LoginPage() {
|
|
|
131
132
|
<div className='mt-2 text-end'>
|
|
132
133
|
<div className='text-sm'>
|
|
133
134
|
<Link href='#' className='font-semibold text-indigo-600 hover:text-indigo-500'>
|
|
134
|
-
{
|
|
135
|
+
{t('forgotPassword')}
|
|
135
136
|
</Link>
|
|
136
137
|
</div>
|
|
137
138
|
</div>
|
|
@@ -141,7 +142,7 @@ export default function LoginPage() {
|
|
|
141
142
|
<div className='flex gap-2'>
|
|
142
143
|
<div className='flex items-center space-x-2'>
|
|
143
144
|
<Switch id='remember_me_check' name='rm' />
|
|
144
|
-
<Label htmlFor='remember_me_check'>{
|
|
145
|
+
<Label htmlFor='remember_me_check'>{t('rememberMe')}</Label>
|
|
145
146
|
</div>
|
|
146
147
|
</div>
|
|
147
148
|
</div>
|
|
@@ -155,7 +156,7 @@ export default function LoginPage() {
|
|
|
155
156
|
type='submit'
|
|
156
157
|
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'
|
|
157
158
|
>
|
|
158
|
-
{
|
|
159
|
+
{t('login')}
|
|
159
160
|
</Button>
|
|
160
161
|
</div>
|
|
161
162
|
</form>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Inter, Cairo } from 'next/font/google'
|
|
2
|
+
import '../globals.css'
|
|
3
|
+
import type { Metadata } from 'next'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
import { ThemeProvider } from '@/components/ThemeProvider'
|
|
6
|
+
import Providers from '@/app/providers'
|
|
7
|
+
import auth from 'nextjs-cms/auth'
|
|
8
|
+
import { getCMSConfig } from 'nextjs-cms/core'
|
|
9
|
+
import { I18nProviderClient } from 'nextjs-cms/translations/client'
|
|
10
|
+
|
|
11
|
+
const inter = Inter({
|
|
12
|
+
subsets: ['latin'],
|
|
13
|
+
variable: '--font-sans',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const cairo = Cairo({
|
|
17
|
+
subsets: ['arabic', 'latin'],
|
|
18
|
+
variable: '--font-cairo',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
22
|
+
const cmsConfig = await getCMSConfig()
|
|
23
|
+
return {
|
|
24
|
+
title: cmsConfig.ui.title,
|
|
25
|
+
description: 'nextjs-cms',
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
30
|
+
const session = await auth()
|
|
31
|
+
const cmsConfig = await getCMSConfig()
|
|
32
|
+
const locale = session?.user?.locale ?? 'en'
|
|
33
|
+
const isRTL = locale === 'ar'
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<html lang={locale} dir={isRTL ? 'rtl' : 'ltr'} suppressHydrationWarning>
|
|
37
|
+
<body
|
|
38
|
+
className={cn(
|
|
39
|
+
'bg-background min-h-screen font-sans antialiased',
|
|
40
|
+
isRTL ? cairo.variable : inter.variable,
|
|
41
|
+
)}
|
|
42
|
+
>
|
|
43
|
+
<I18nProviderClient locale={locale}>
|
|
44
|
+
<ThemeProvider
|
|
45
|
+
attribute='class'
|
|
46
|
+
defaultTheme={cmsConfig.ui.defaultTheme}
|
|
47
|
+
enableSystem
|
|
48
|
+
disableTransitionOnChange
|
|
49
|
+
>
|
|
50
|
+
<Providers session={session ?? undefined}>{children}</Providers>
|
|
51
|
+
</ThemeProvider>
|
|
52
|
+
</I18nProviderClient>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -1,16 +1,66 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { Inter, Cairo } from 'next/font/google'
|
|
2
|
+
import '../globals.css'
|
|
3
|
+
import type { Metadata } from 'next'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
import { ThemeProvider } from '@/components/ThemeProvider'
|
|
6
|
+
import Providers from '@/app/providers'
|
|
7
|
+
import auth from 'nextjs-cms/auth'
|
|
3
8
|
import { getCMSConfig } from 'nextjs-cms/core'
|
|
9
|
+
import { I18nProviderClient } from 'nextjs-cms/translations/client'
|
|
10
|
+
import { api, HydrateClient } from 'nextjs-cms/api/trpc/server'
|
|
11
|
+
import Layout from '@/components/Layout'
|
|
12
|
+
|
|
13
|
+
const inter = Inter({
|
|
14
|
+
subsets: ['latin'],
|
|
15
|
+
variable: '--font-sans',
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const cairo = Cairo({
|
|
19
|
+
subsets: ['arabic', 'latin'],
|
|
20
|
+
variable: '--font-cairo',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
24
|
+
const cmsConfig = await getCMSConfig()
|
|
25
|
+
return {
|
|
26
|
+
title: cmsConfig.ui.title,
|
|
27
|
+
description: 'nextjs-cms',
|
|
28
|
+
}
|
|
29
|
+
}
|
|
4
30
|
|
|
5
31
|
export default async function CMSLayout({ children }: { children: React.ReactNode }) {
|
|
6
|
-
await
|
|
32
|
+
const session = await auth()
|
|
7
33
|
const cmsConfig = await getCMSConfig()
|
|
34
|
+
const locale = session?.user?.locale ?? 'en'
|
|
35
|
+
const isRTL = locale === 'ar'
|
|
36
|
+
await api.navigation.getSidebar.prefetch()
|
|
8
37
|
|
|
9
38
|
return (
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
39
|
+
<html lang={locale} dir={isRTL ? 'rtl' : 'ltr'} suppressHydrationWarning>
|
|
40
|
+
<body
|
|
41
|
+
className={cn(
|
|
42
|
+
'bg-background min-h-screen font-sans antialiased',
|
|
43
|
+
isRTL ? cairo.variable : inter.variable,
|
|
44
|
+
)}
|
|
45
|
+
>
|
|
46
|
+
<I18nProviderClient locale={locale}>
|
|
47
|
+
<ThemeProvider
|
|
48
|
+
attribute='class'
|
|
49
|
+
defaultTheme={cmsConfig.ui.defaultTheme}
|
|
50
|
+
enableSystem
|
|
51
|
+
disableTransitionOnChange
|
|
52
|
+
>
|
|
53
|
+
<Providers session={session ?? undefined}>
|
|
54
|
+
<HydrateClient>
|
|
55
|
+
<Layout logoUrlPath={cmsConfig.ui.logo} logoText={cmsConfig.ui.logoText}>
|
|
56
|
+
{children}
|
|
57
|
+
</Layout>
|
|
58
|
+
</HydrateClient>
|
|
59
|
+
</Providers>
|
|
60
|
+
</ThemeProvider>
|
|
61
|
+
</I18nProviderClient>
|
|
62
|
+
</body>
|
|
63
|
+
</html>
|
|
64
|
+
|
|
15
65
|
)
|
|
16
66
|
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
--breakpoint-2xl-sidebar: 1920px;
|
|
23
23
|
|
|
24
24
|
--font-sans:
|
|
25
|
-
var(--font-sans), ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
|
|
25
|
+
var(--font-cairo, var(--font-sans)), ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
|
|
26
26
|
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
|
27
27
|
|
|
28
28
|
--color-border: hsl(var(--border));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useI18n } from 'nextjs-cms/translations/client'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import useModal from '@/hooks/useModal'
|
|
4
4
|
import ProtectedImage from '@/components/ProtectedImage'
|
|
@@ -19,6 +19,7 @@ export default function AdminCard({
|
|
|
19
19
|
admin: RouterOutputs['admins']['list']['admins'][0]
|
|
20
20
|
action: any
|
|
21
21
|
}) {
|
|
22
|
+
const t = useI18n()
|
|
22
23
|
const { setModal, modal, modalResponse, setModalResponse } = useModal()
|
|
23
24
|
const { toast } = useToast()
|
|
24
25
|
|
|
@@ -29,14 +30,14 @@ export default function AdminCard({
|
|
|
29
30
|
setModalResponse(null)
|
|
30
31
|
toast({
|
|
31
32
|
variant: 'success',
|
|
32
|
-
title:
|
|
33
|
-
description:
|
|
33
|
+
title: t('deleteAdmin'),
|
|
34
|
+
description: t('adminDeletedSuccessfully'),
|
|
34
35
|
})
|
|
35
36
|
},
|
|
36
37
|
onError: (error) => {
|
|
37
38
|
toast({
|
|
38
39
|
variant: 'destructive',
|
|
39
|
-
title:
|
|
40
|
+
title: t('deleteAdmin'),
|
|
40
41
|
description: error.message,
|
|
41
42
|
})
|
|
42
43
|
},
|
|
@@ -46,8 +47,8 @@ export default function AdminCard({
|
|
|
46
47
|
if ([1, '1'].includes(admin.id)) {
|
|
47
48
|
toast({
|
|
48
49
|
variant: 'destructive',
|
|
49
|
-
title:
|
|
50
|
-
description:
|
|
50
|
+
title: t('deleteAdmin'),
|
|
51
|
+
description: t('masterAdminCannotBeDeleted'),
|
|
51
52
|
})
|
|
52
53
|
return
|
|
53
54
|
}
|
|
@@ -107,9 +108,9 @@ export default function AdminCard({
|
|
|
107
108
|
variant='default'
|
|
108
109
|
onClick={() => {
|
|
109
110
|
setModal({
|
|
110
|
-
title:
|
|
111
|
+
title: t('editAdmin'),
|
|
111
112
|
body: [1, '1'].includes(admin.id) ? (
|
|
112
|
-
<div className='p-4'>{
|
|
113
|
+
<div className='p-4'>{t('masterAdminCannotBeModified')}</div>
|
|
113
114
|
) : (
|
|
114
115
|
<div className='p-4'>{<AdminEditPage id={admin.id} action={() => action()} />}</div>
|
|
115
116
|
),
|
|
@@ -120,25 +121,25 @@ export default function AdminCard({
|
|
|
120
121
|
})
|
|
121
122
|
}}
|
|
122
123
|
>
|
|
123
|
-
{
|
|
124
|
+
{t('edit')}
|
|
124
125
|
</Button>
|
|
125
126
|
<Button
|
|
126
127
|
variant='destructive'
|
|
127
128
|
onClick={() => {
|
|
128
129
|
setModal({
|
|
129
|
-
title:
|
|
130
|
+
title: t('deleteAdmin'),
|
|
130
131
|
body: [1, '1'].includes(admin.id) ? (
|
|
131
|
-
<div className='p-4'>{
|
|
132
|
+
<div className='p-4'>{t('masterAdminCannotBeDeleted')}</div>
|
|
132
133
|
) : (
|
|
133
134
|
<div className='p-4'>
|
|
134
135
|
<div className='flex flex-col gap-4'>
|
|
135
|
-
<div>{
|
|
136
|
+
<div>{t('deleteAdminText')}</div>
|
|
136
137
|
<div className='flex gap-2'>
|
|
137
138
|
<button
|
|
138
139
|
className='rounded bg-green-600 px-2 py-1 text-white'
|
|
139
140
|
onClick={handleDeleteAdmin}
|
|
140
141
|
>
|
|
141
|
-
|
|
142
|
+
{t('yes')}
|
|
142
143
|
</button>
|
|
143
144
|
<button
|
|
144
145
|
className='rounded bg-red-800 px-2 py-1 text-white'
|
|
@@ -146,7 +147,7 @@ export default function AdminCard({
|
|
|
146
147
|
setModal(null)
|
|
147
148
|
}}
|
|
148
149
|
>
|
|
149
|
-
|
|
150
|
+
{t('no')}
|
|
150
151
|
</button>
|
|
151
152
|
</div>
|
|
152
153
|
</div>
|
|
@@ -158,7 +159,7 @@ export default function AdminCard({
|
|
|
158
159
|
})
|
|
159
160
|
}}
|
|
160
161
|
>
|
|
161
|
-
{
|
|
162
|
+
{t('delete')}
|
|
162
163
|
</Button>
|
|
163
164
|
</CardFooter>
|
|
164
165
|
</Card>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useI18n } from 'nextjs-cms/translations/client'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import useModal from '@/hooks/useModal'
|
|
4
4
|
import { trpc } from '@/app/_trpc/client'
|
|
@@ -7,6 +7,7 @@ import LoadingSpinners from './LoadingSpinners'
|
|
|
7
7
|
import { Button } from '@/components/ui/button'
|
|
8
8
|
|
|
9
9
|
export default function AdminEditPage({ id, action }: { id: string; action: any }) {
|
|
10
|
+
const t = useI18n()
|
|
10
11
|
const { setModal } = useModal()
|
|
11
12
|
const [allChecked, setAllChecked] = React.useState<boolean | 'edited' | null>(null)
|
|
12
13
|
const utils = trpc.useUtils()
|
|
@@ -14,7 +15,7 @@ export default function AdminEditPage({ id, action }: { id: string; action: any
|
|
|
14
15
|
const submitMutation = trpc.admins.update.useMutation({
|
|
15
16
|
onError: (error) => {
|
|
16
17
|
setModal({
|
|
17
|
-
title:
|
|
18
|
+
title: t('editAdmin'),
|
|
18
19
|
body: (
|
|
19
20
|
<div className='rounded border-2 border-dashed border-red-500 bg-red-100 p-2 text-red-800'>
|
|
20
21
|
{error.message}
|
|
@@ -30,7 +31,7 @@ export default function AdminEditPage({ id, action }: { id: string; action: any
|
|
|
30
31
|
action()
|
|
31
32
|
utils.admins.get.invalidate(id)
|
|
32
33
|
setModal({
|
|
33
|
-
title:
|
|
34
|
+
title: t('editAdmin'),
|
|
34
35
|
body: (
|
|
35
36
|
<div className='rounded border-2 border-dashed border-green-500 bg-green-100 p-2 text-green-800'>
|
|
36
37
|
{data.status}
|
|
@@ -68,13 +69,13 @@ export default function AdminEditPage({ id, action }: { id: string; action: any
|
|
|
68
69
|
return (
|
|
69
70
|
<div>
|
|
70
71
|
<h2>
|
|
71
|
-
{
|
|
72
|
+
{t('editAdminText')}: {data?.admin.user}
|
|
72
73
|
</h2>
|
|
73
74
|
<div>
|
|
74
75
|
{isLoading ? <LoadingSpinners /> : null}
|
|
75
76
|
{data && data.allRoles && data.allRoles.length > 0 ? (
|
|
76
77
|
<form onSubmit={handleSubmit}>
|
|
77
|
-
<div className='mt-5 w-full font-semibold'>{
|
|
78
|
+
<div className='mt-5 w-full font-semibold'>{t('adminPrivileges')}</div>
|
|
78
79
|
<div className='flex'>
|
|
79
80
|
<Button
|
|
80
81
|
className='border border-foreground'
|
|
@@ -90,7 +91,7 @@ export default function AdminEditPage({ id, action }: { id: string; action: any
|
|
|
90
91
|
}
|
|
91
92
|
}}
|
|
92
93
|
>
|
|
93
|
-
{allChecked === true ?
|
|
94
|
+
{allChecked === true ? t('removeAll') : t('checkAll')}
|
|
94
95
|
</Button>
|
|
95
96
|
</div>
|
|
96
97
|
<div className='mt-2 grid w-full grid-cols-1 gap-2 md-sidebar:grid-cols-2 lg-sidebar:grid-cols-3 xl-sidebar:grid-cols-4 2xl-sidebar:grid-cols-5'>
|
|
@@ -112,7 +113,7 @@ export default function AdminEditPage({ id, action }: { id: string; action: any
|
|
|
112
113
|
</div>
|
|
113
114
|
<div className='mt-4'>
|
|
114
115
|
<button type='submit' className='rounded bg-blue-700 px-2 py-1 font-bold text-white'>
|
|
115
|
-
{
|
|
116
|
+
{t('save')}
|
|
116
117
|
</button>
|
|
117
118
|
</div>
|
|
118
119
|
</form>
|
|
@@ -2,7 +2,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
|
2
2
|
import { Checkbox } from '@/components/ui/checkbox'
|
|
3
3
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
|
4
4
|
import React, { useEffect } from 'react'
|
|
5
|
-
import
|
|
5
|
+
import { useI18n } from 'nextjs-cms/translations/client'
|
|
6
6
|
import { Badge } from '@/components/ui/badge'
|
|
7
7
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
|
8
8
|
import type { RouterOutputs } from 'nextjs-cms/api'
|
|
@@ -24,6 +24,7 @@ export default function AdminRoleCard({
|
|
|
24
24
|
publisher: boolean
|
|
25
25
|
}
|
|
26
26
|
}) {
|
|
27
|
+
const t = useI18n()
|
|
27
28
|
const [checked, setChecked] = React.useState<boolean | 'indeterminate'>(defaultValue.operations === 'cud')
|
|
28
29
|
const [publisherChecked, setPublisherChecked] = React.useState<boolean>(defaultValue.publisher)
|
|
29
30
|
const [value, setValue] = React.useState<string>(defaultValue.operations)
|
|
@@ -85,7 +86,7 @@ export default function AdminRoleCard({
|
|
|
85
86
|
htmlFor={privilege.title + '_new'}
|
|
86
87
|
className='ms-2 block text-sm font-medium text-foreground'
|
|
87
88
|
>
|
|
88
|
-
|
|
89
|
+
{t('fullAccess')}
|
|
89
90
|
</label>
|
|
90
91
|
</div>
|
|
91
92
|
|
|
@@ -107,7 +108,7 @@ export default function AdminRoleCard({
|
|
|
107
108
|
htmlFor={`${privilege.title}_publisher`}
|
|
108
109
|
className={`ms-2 block text-sm font-medium text-foreground`}
|
|
109
110
|
>
|
|
110
|
-
<span className={`${value === 'none' ? 'opacity-40' : ''}`}>
|
|
111
|
+
<span className={`${value === 'none' ? 'opacity-40' : ''}`}>{t('publisher')}</span>{' '}
|
|
111
112
|
<TooltipProvider delayDuration={0}>
|
|
112
113
|
<Tooltip>
|
|
113
114
|
<TooltipTrigger asChild>
|
|
@@ -116,7 +117,7 @@ export default function AdminRoleCard({
|
|
|
116
117
|
</span>
|
|
117
118
|
</TooltipTrigger>
|
|
118
119
|
<TooltipContent>
|
|
119
|
-
<p>
|
|
120
|
+
<p>{t('publisherTooltip')}</p>
|
|
120
121
|
</TooltipContent>
|
|
121
122
|
</Tooltip>
|
|
122
123
|
</TooltipProvider>
|
|
@@ -125,7 +126,7 @@ export default function AdminRoleCard({
|
|
|
125
126
|
) : null}
|
|
126
127
|
|
|
127
128
|
<div className='mt-2 flex flex-col border-t p-2'>
|
|
128
|
-
<div className='text-sm'>
|
|
129
|
+
<div className='text-sm'>{t('customAccess')}</div>
|
|
129
130
|
<Select
|
|
130
131
|
defaultValue={'none'}
|
|
131
132
|
value={value}
|
|
@@ -148,20 +149,20 @@ export default function AdminRoleCard({
|
|
|
148
149
|
}}
|
|
149
150
|
>
|
|
150
151
|
<SelectTrigger className='w-[180px]'>
|
|
151
|
-
<SelectValue placeholder={
|
|
152
|
+
<SelectValue placeholder={t('select')} />
|
|
152
153
|
</SelectTrigger>
|
|
153
154
|
<SelectContent>
|
|
154
155
|
<SelectItem value='none'>
|
|
155
|
-
<Badge variant='outline'>
|
|
156
|
+
<Badge variant='outline'>{t('noAccess')}</Badge>
|
|
156
157
|
</SelectItem>
|
|
157
158
|
<SelectItem value='c'>
|
|
158
|
-
<Badge variant='secondary'>C</Badge> <span className='italic'>
|
|
159
|
+
<Badge variant='secondary'>C</Badge> <span className='italic'>{t('create')}</span>
|
|
159
160
|
</SelectItem>
|
|
160
161
|
<SelectItem value='u'>
|
|
161
|
-
<Badge variant='secondary'>U</Badge> <span className='italic'>
|
|
162
|
+
<Badge variant='secondary'>U</Badge> <span className='italic'>{t('update')}</span>
|
|
162
163
|
</SelectItem>
|
|
163
164
|
<SelectItem value='d'>
|
|
164
|
-
<Badge variant='secondary'>D</Badge> <span className='italic'>
|
|
165
|
+
<Badge variant='secondary'>D</Badge> <span className='italic'>{t('delete')}</span>
|
|
165
166
|
</SelectItem>
|
|
166
167
|
<SelectItem value='cu'>
|
|
167
168
|
<Badge variant='secondary'>C + U</Badge>
|
|
@@ -173,7 +174,7 @@ export default function AdminRoleCard({
|
|
|
173
174
|
<Badge variant='secondary'>U + D</Badge>
|
|
174
175
|
</SelectItem>
|
|
175
176
|
<SelectItem value='cud'>
|
|
176
|
-
<Badge variant='success'>C + U + D</Badge> (
|
|
177
|
+
<Badge variant='success'>C + U + D</Badge> {t('allPermissions')}
|
|
177
178
|
</SelectItem>
|
|
178
179
|
</SelectContent>
|
|
179
180
|
</Select>
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import AdminCard from '@/components/AdminCard'
|
|
4
|
-
import
|
|
4
|
+
import { useI18n } from 'nextjs-cms/translations/client'
|
|
5
5
|
import NewAdminForm from '@/components/NewAdminForm'
|
|
6
6
|
import ContainerBox from '@/components/ContainerBox'
|
|
7
7
|
import { trpc } from '@/app/_trpc/client'
|
|
8
8
|
import ErrorComponent from './ErrorComponent'
|
|
9
9
|
|
|
10
10
|
const AdminsPage = () => {
|
|
11
|
+
const t = useI18n()
|
|
11
12
|
const [data, {refetch}] = trpc.admins.list.useSuspenseQuery()
|
|
12
13
|
|
|
13
14
|
if (data.error) {
|
|
@@ -17,16 +18,16 @@ const AdminsPage = () => {
|
|
|
17
18
|
return (
|
|
18
19
|
<div className='w-full'>
|
|
19
20
|
<div className='bg-linear-to-r from-sky-200 via-emerald-300 to-blue-600 p-8 font-extrabold text-foreground dark:from-blue-800 dark:via-amber-700 dark:to-rose-900'>
|
|
20
|
-
<h1 className='text-3xl'>{
|
|
21
|
+
<h1 className='text-3xl'>{t('admins')}</h1>
|
|
21
22
|
</div>
|
|
22
23
|
|
|
23
24
|
<div className='flex flex-col gap-2 p-4'>
|
|
24
|
-
<ContainerBox title={
|
|
25
|
+
<ContainerBox title={t('createNewAdmin')}>
|
|
25
26
|
{data && data.privileges && <NewAdminForm action={() => refetch()} privileges={data.privileges} />}
|
|
26
27
|
</ContainerBox>
|
|
27
28
|
|
|
28
29
|
{data && data.admins && data.admins.length > 0 && (
|
|
29
|
-
<ContainerBox title={
|
|
30
|
+
<ContainerBox title={t('adminsList')}>
|
|
30
31
|
<div className='mt-2 grid w-full grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3'>
|
|
31
32
|
{data.admins.map((admin) => (
|
|
32
33
|
<AdminCard key={admin.id} action={() => refetch()} admin={admin} />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
3
3
|
import React, { useEffect, useRef } from 'react'
|
|
4
|
-
import
|
|
4
|
+
import { useI18n } from 'nextjs-cms/translations/client'
|
|
5
5
|
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'
|
|
6
6
|
import { DatePickerWithRange } from '@/components/form/DateRangeFormInput'
|
|
7
7
|
import { MonthlyPageViews } from '@/components/analytics/MonthlyPageViews'
|
|
@@ -19,6 +19,7 @@ import { trpc } from '@/app/_trpc/client'
|
|
|
19
19
|
ChartJS.register(ArcElement, Tooltip, Legend)
|
|
20
20
|
|
|
21
21
|
const AnalyticsPage = () => {
|
|
22
|
+
const t = useI18n()
|
|
22
23
|
const controller = new AbortController()
|
|
23
24
|
const datePickerRangeRef = useRef<HTMLDivElement>(null)
|
|
24
25
|
const [fromDate, setFromDate] = React.useState<Date | string>('30daysAgo')
|
|
@@ -35,7 +36,7 @@ const AnalyticsPage = () => {
|
|
|
35
36
|
return (
|
|
36
37
|
<div className='w-full'>
|
|
37
38
|
<div className='bg-linear-to-r from-sky-200 via-emerald-300 to-blue-600 p-8 font-extrabold text-foreground dark:from-blue-800 dark:via-amber-700 dark:to-rose-900'>
|
|
38
|
-
<h1 className='text-3xl'>{
|
|
39
|
+
<h1 className='text-3xl'>{t('analytics')}</h1>
|
|
39
40
|
</div>
|
|
40
41
|
<div className='p-4'>
|
|
41
42
|
<div className='flex gap-2'>
|
|
@@ -82,11 +83,11 @@ const AnalyticsPage = () => {
|
|
|
82
83
|
}}
|
|
83
84
|
>
|
|
84
85
|
<TabsList>
|
|
85
|
-
<TabsTrigger value='today'>
|
|
86
|
-
<TabsTrigger value='lastWeek'>
|
|
87
|
-
<TabsTrigger value='lastMonth'>
|
|
88
|
-
<TabsTrigger value='lastYear'>
|
|
89
|
-
<TabsTrigger value='custom'>
|
|
86
|
+
<TabsTrigger value='today'>{t('today')}</TabsTrigger>
|
|
87
|
+
<TabsTrigger value='lastWeek'>{t('last7Days')}</TabsTrigger>
|
|
88
|
+
<TabsTrigger value='lastMonth'>{t('last30Days')}</TabsTrigger>
|
|
89
|
+
<TabsTrigger value='lastYear'>{t('last365Days')}</TabsTrigger>
|
|
90
|
+
<TabsTrigger value='custom'>{t('custom')}</TabsTrigger>
|
|
90
91
|
</TabsList>
|
|
91
92
|
</Tabs>
|
|
92
93
|
</Tabs>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useRef } from 'react'
|
|
4
|
-
import
|
|
4
|
+
import { useI18n } from 'nextjs-cms/translations/client'
|
|
5
5
|
import SectionItemCard from '@/components/SectionItemCard'
|
|
6
6
|
import ContainerBox from '@/components/ContainerBox'
|
|
7
7
|
import Pagination from '@/components/pagination/Pagination'
|
|
@@ -15,6 +15,7 @@ import { capitalizeWords } from 'nextjs-cms/utils'
|
|
|
15
15
|
import ErrorComponent from '@/components/ErrorComponent'
|
|
16
16
|
|
|
17
17
|
export default function BrowsePage({ section, page }: { section: string; page: string }) {
|
|
18
|
+
const t = useI18n()
|
|
18
19
|
const pageInt = parseInt(page) ?? 1
|
|
19
20
|
const params = useSearchParams()
|
|
20
21
|
const router = useRouter()
|
|
@@ -50,24 +51,24 @@ export default function BrowsePage({ section, page }: { section: string; page: s
|
|
|
50
51
|
}}
|
|
51
52
|
>
|
|
52
53
|
<CardHeader>
|
|
53
|
-
<CardTitle>
|
|
54
|
+
<CardTitle>{t('search')} {data?.section?.title}</CardTitle>
|
|
54
55
|
{/*<CardDescription>Search by title</CardDescription>*/}
|
|
55
56
|
</CardHeader>
|
|
56
57
|
<CardContent>
|
|
57
58
|
<Input
|
|
58
59
|
className='border-accent-foreground bg-input'
|
|
59
60
|
ref={searchInputRef}
|
|
60
|
-
placeholder={`${capitalizeWords(
|
|
61
|
+
placeholder={`${capitalizeWords(t('search'))}...`}
|
|
61
62
|
defaultValue={q || ''}
|
|
62
63
|
/>
|
|
63
64
|
</CardContent>
|
|
64
65
|
<CardFooter className='border-t px-6 py-4'>
|
|
65
|
-
<Button>{capitalizeWords(
|
|
66
|
+
<Button>{capitalizeWords(t('search'))}</Button>
|
|
66
67
|
</CardFooter>
|
|
67
68
|
</form>
|
|
68
69
|
</Card>
|
|
69
70
|
) : null}
|
|
70
|
-
<ContainerBox title={
|
|
71
|
+
<ContainerBox title={t('browse')}>
|
|
71
72
|
|
|
72
73
|
|
|
73
74
|
{ data.items && data.items.length > 0 ? (
|
|
@@ -83,7 +84,7 @@ export default function BrowsePage({ section, page }: { section: string; page: s
|
|
|
83
84
|
</div>
|
|
84
85
|
) : (
|
|
85
86
|
<Alert variant='default' className='bg-primary text-primary-foreground my-8'>
|
|
86
|
-
<AlertDescription className='font-bold'>{
|
|
87
|
+
<AlertDescription className='font-bold'>{t('noItems')}</AlertDescription>
|
|
87
88
|
</Alert>
|
|
88
89
|
)}
|
|
89
90
|
|