create-nextjs-cms 0.5.92 → 0.5.94

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 (67) hide show
  1. package/package.json +3 -3
  2. package/templates/default/app/(auth)/auth/login/LoginPage.tsx +13 -12
  3. package/templates/default/app/(auth)/layout.tsx +56 -0
  4. package/templates/default/app/(rootLayout)/layout.tsx +58 -8
  5. package/templates/default/app/globals.css +1 -1
  6. package/templates/default/components/AdminCard.tsx +16 -15
  7. package/templates/default/components/AdminEditPage.tsx +8 -7
  8. package/templates/default/components/AdminPrivilegeCard.tsx +12 -11
  9. package/templates/default/components/AdminsPage.tsx +5 -4
  10. package/templates/default/components/AnalyticsPage.tsx +8 -7
  11. package/templates/default/components/BrowsePage.tsx +7 -6
  12. package/templates/default/components/CategoryDeleteConfirmPage.tsx +12 -11
  13. package/templates/default/components/CategorySectionSelectInput.tsx +3 -2
  14. package/templates/default/components/DashboardNewPage.tsx +40 -39
  15. package/templates/default/components/DashboardPage.tsx +34 -33
  16. package/templates/default/components/DashboardPageAlt.tsx +3 -1
  17. package/templates/default/components/Dropzone.tsx +8 -7
  18. package/templates/default/components/EmailCard.tsx +12 -11
  19. package/templates/default/components/EmailPasswordForm.tsx +7 -6
  20. package/templates/default/components/EmailQuotaForm.tsx +7 -6
  21. package/templates/default/components/EmailsPage.tsx +6 -5
  22. package/templates/default/components/GalleryPhoto.tsx +6 -5
  23. package/templates/default/components/ItemEditPage.tsx +5 -4
  24. package/templates/default/components/Layout.tsx +1 -1
  25. package/templates/default/components/LoadingSpinners.tsx +1 -1
  26. package/templates/default/components/LogPage.tsx +24 -9
  27. package/templates/default/components/Navbar.tsx +13 -12
  28. package/templates/default/components/NewAdminForm.tsx +12 -11
  29. package/templates/default/components/NewEmailForm.tsx +11 -10
  30. package/templates/default/components/NewPage.tsx +6 -5
  31. package/templates/default/components/NewVariantComponent.tsx +6 -5
  32. package/templates/default/components/PhotoGallery.tsx +4 -3
  33. package/templates/default/components/SectionItemCard.tsx +8 -8
  34. package/templates/default/components/SectionItemStatusBadge.tsx +3 -2
  35. package/templates/default/components/SectionPage.tsx +6 -5
  36. package/templates/default/components/SelectInputButtons.tsx +8 -7
  37. package/templates/default/components/SettingsPage.tsx +18 -17
  38. package/templates/default/components/Sidebar.tsx +23 -15
  39. package/templates/default/components/SidebarDropdownItem.tsx +11 -5
  40. package/templates/default/components/SidebarItem.tsx +1 -0
  41. package/templates/default/components/VariantCard.tsx +8 -8
  42. package/templates/default/components/VariantEditPage.tsx +5 -4
  43. package/templates/default/components/analytics/BounceRate.tsx +6 -5
  44. package/templates/default/components/analytics/LivePageViews.tsx +8 -7
  45. package/templates/default/components/analytics/LiveUsersCount.tsx +3 -2
  46. package/templates/default/components/analytics/MonthlyPageViews.tsx +3 -2
  47. package/templates/default/components/analytics/TopCountries.tsx +4 -3
  48. package/templates/default/components/analytics/TopDevices.tsx +4 -3
  49. package/templates/default/components/analytics/TopMediums.tsx +4 -3
  50. package/templates/default/components/analytics/TopSources.tsx +4 -3
  51. package/templates/default/components/analytics/TotalPageViews.tsx +3 -2
  52. package/templates/default/components/analytics/TotalSessions.tsx +3 -2
  53. package/templates/default/components/analytics/TotalUniqueUsers.tsx +3 -2
  54. package/templates/default/components/custom/RightHomeRoomVariantCard.tsx +9 -8
  55. package/templates/default/components/form/Form.tsx +5 -4
  56. package/templates/default/components/form/FormInputElement.tsx +3 -1
  57. package/templates/default/components/form/helpers/_section-hot-reload.js +1 -1
  58. package/templates/default/components/form/inputs/DateFormInput.tsx +3 -2
  59. package/templates/default/components/form/inputs/DocumentFormInput.tsx +16 -15
  60. package/templates/default/components/form/inputs/MapFormInput.tsx +5 -4
  61. package/templates/default/components/form/inputs/PhotoFormInput.tsx +16 -15
  62. package/templates/default/components/form/inputs/SelectFormInput.tsx +3 -2
  63. package/templates/default/components/form/inputs/TagsFormInput.tsx +3 -2
  64. package/templates/default/components/form/inputs/VideoFormInput.tsx +7 -6
  65. package/templates/default/components/pagination/PaginationButtons.tsx +8 -6
  66. package/templates/default/package.json +2 -2
  67. package/templates/default/app/layout.tsx +0 -40
@@ -2,7 +2,7 @@ import React, { CSSProperties, forwardRef, Ref, useEffect, useImperativeHandle,
2
2
  import { DropEvent, FileRejection, useDropzone } from 'react-dropzone'
3
3
  import { DropzoneFile } from 'nextjs-cms/core/types'
4
4
  import ContainerBox from '@/components/ContainerBox'
5
- import getString from 'nextjs-cms/translations'
5
+ import { useI18n } from 'nextjs-cms/translations/client'
6
6
  import { MinusIcon } from '@radix-ui/react-icons'
7
7
  import { useToast } from '@/components/ui/use-toast'
8
8
 
@@ -36,6 +36,7 @@ export interface DropzoneHandles {
36
36
  }
37
37
 
38
38
  function Dropzone(props: any, ref: Ref<DropzoneHandles>) {
39
+ const t = useI18n()
39
40
  const acceptedFileTypes = {
40
41
  'image/png': ['.png'],
41
42
  'image/jpeg': ['.jpg', '.jpeg'],
@@ -62,17 +63,17 @@ function Dropzone(props: any, ref: Ref<DropzoneHandles>) {
62
63
  onDropRejected: (rejectedFiles: FileRejection[], e: DropEvent) => {
63
64
  toast({
64
65
  variant: 'destructive',
65
- title: getString('delete_gallery_photo'),
66
+ title: t('deleteGalleryPhoto'),
66
67
  description: rejectedFiles[0]?.errors[0]?.message
67
68
  ? rejectedFiles[0].errors[0].message
68
- : getString('error'),
69
+ : t('error'),
69
70
  })
70
71
  },
71
72
 
72
73
  onError: (error: any) => {
73
74
  toast({
74
75
  variant: 'destructive',
75
- title: getString('delete_gallery_photo'),
76
+ title: t('deleteGalleryPhoto'),
76
77
  description: error.displayName,
77
78
  })
78
79
  },
@@ -131,16 +132,16 @@ function Dropzone(props: any, ref: Ref<DropzoneHandles>) {
131
132
  }, [])
132
133
 
133
134
  return (
134
- <ContainerBox title={getString('uploadPhotosToGallery')}>
135
+ <ContainerBox title={t('uploadPhotosToGallery')}>
135
136
  <section className='container mt-4 rounded-2xl border-4 border-dashed border-gray-400 bg-accent py-8'>
136
137
  <div {...getRootProps({ className: 'dropzone' })}>
137
138
  <input {...getInputProps()} />
138
139
  <div className='flex flex-col items-center justify-center'>
139
140
  <div className='p-4 text-xl font-semibold text-card-foreground'>
140
- {getString('dropzoneText')}
141
+ {t('dropzoneText')}
141
142
  </div>
142
143
  <div className='text-muted-foreground'>
143
- Accepts: {Object.values(acceptedFileTypes).flat().join(', ')}
144
+ {t('accepts')}: {Object.values(acceptedFileTypes).flat().join(', ')}
144
145
  </div>
145
146
  </div>
146
147
  </div>
@@ -1,4 +1,4 @@
1
- import getString from 'nextjs-cms/translations'
1
+ import { useI18n } from 'nextjs-cms/translations/client'
2
2
  import React from 'react'
3
3
  import { EmailItem } from 'nextjs-cms/core/types'
4
4
  import useModal from '@/hooks/useModal'
@@ -11,6 +11,7 @@ import EmailPasswordForm from '@/components/EmailPasswordForm'
11
11
  import { trpc } from '@/app/_trpc/client'
12
12
 
13
13
  export default function EmailCard({ email, action }: { email: EmailItem; action: any }) {
14
+ const t = useI18n()
14
15
  const { setModal, modal, modalResponse, setModalResponse } = useModal()
15
16
  const { toast } = useToast()
16
17
 
@@ -29,7 +30,7 @@ export default function EmailCard({ email, action }: { email: EmailItem; action:
29
30
  setModalResponse(null)
30
31
  toast({
31
32
  variant: 'success',
32
- title: getString('deleteEmailAccount'),
33
+ title: t('deleteEmailAccount'),
33
34
  description: data.message,
34
35
  })
35
36
  },
@@ -61,7 +62,7 @@ export default function EmailCard({ email, action }: { email: EmailItem; action:
61
62
  variant='default'
62
63
  onClick={() => {
63
64
  setModal({
64
- title: getString('emailQuota'),
65
+ title: t('emailQuota'),
65
66
  body: (
66
67
  <div className='p-4'>
67
68
  <EmailQuotaForm email={email.user} action={() => action()} />
@@ -73,14 +74,14 @@ export default function EmailCard({ email, action }: { email: EmailItem; action:
73
74
  })
74
75
  }}
75
76
  >
76
- {getString('quota')}
77
+ {t('quota')}
77
78
  </Button>
78
79
  <Button
79
80
  size='sm'
80
81
  variant='default'
81
82
  onClick={() => {
82
83
  setModal({
83
- title: getString('change_password'),
84
+ title: t('changePassword'),
84
85
  body: (
85
86
  <div className='p-4'>
86
87
  <EmailPasswordForm email={email.user} action={() => action()} />
@@ -92,24 +93,24 @@ export default function EmailCard({ email, action }: { email: EmailItem; action:
92
93
  })
93
94
  }}
94
95
  >
95
- {getString('password')}
96
+ {t('password')}
96
97
  </Button>
97
98
  <Button
98
99
  size='sm'
99
100
  variant='destructive'
100
101
  onClick={() => {
101
102
  setModal({
102
- title: getString('delete'),
103
+ title: t('delete'),
103
104
  body: (
104
105
  <div className='p-4'>
105
106
  <div className='flex flex-col gap-4'>
106
- <div>{getString('deleteEmailText')}</div>
107
+ <div>{t('deleteEmailText')}</div>
107
108
  <div className='flex gap-2'>
108
109
  <button
109
110
  className='rounded bg-green-600 px-2 py-1 text-white'
110
111
  onClick={deleteEmail}
111
112
  >
112
- Yes
113
+ {t('yes')}
113
114
  </button>
114
115
  <button
115
116
  className='rounded bg-red-800 px-2 py-1 text-white'
@@ -117,7 +118,7 @@ export default function EmailCard({ email, action }: { email: EmailItem; action:
117
118
  setModal(null)
118
119
  }}
119
120
  >
120
- No
121
+ {t('no')}
121
122
  </button>
122
123
  </div>
123
124
  </div>
@@ -129,7 +130,7 @@ export default function EmailCard({ email, action }: { email: EmailItem; action:
129
130
  })
130
131
  }}
131
132
  >
132
- {getString('delete')}
133
+ {t('delete')}
133
134
  </Button>
134
135
  </CardFooter>
135
136
  </Card>
@@ -1,4 +1,4 @@
1
- import getString from 'nextjs-cms/translations'
1
+ import { useI18n } from 'nextjs-cms/translations/client'
2
2
  import React from 'react'
3
3
  import useModal from '@/hooks/useModal'
4
4
  import { useToast } from '@/components/ui/use-toast'
@@ -6,13 +6,14 @@ import InfoCard from '@/components/InfoCard'
6
6
  import { trpc } from '@/app/_trpc/client'
7
7
 
8
8
  export default function EmailPasswordForm({ email, action }: { email: string; action: any }) {
9
+ const t = useI18n()
9
10
  const { setModal, modal, modalResponse, setModalResponse } = useModal()
10
11
 
11
12
  const { toast } = useToast()
12
13
  const quotaMutation = trpc.cpanelEmails.passwordChange.useMutation({
13
14
  onError: (error) => {
14
15
  setModal({
15
- title: getString('error'),
16
+ title: t('error'),
16
17
  body: (
17
18
  <div className='p-4'>
18
19
  <InfoCard result={{ key: 'danger', title: error.message, status: false }} />
@@ -29,7 +30,7 @@ export default function EmailPasswordForm({ email, action }: { email: string; ac
29
30
  setModalResponse(null)
30
31
  toast({
31
32
  variant: 'success',
32
- title: getString('success'),
33
+ title: t('success'),
33
34
  })
34
35
  },
35
36
  })
@@ -46,7 +47,7 @@ export default function EmailPasswordForm({ email, action }: { email: string; ac
46
47
  <form onSubmit={handleSubmit} className='flex flex-col gap-2'>
47
48
  <div>
48
49
  <label htmlFor='password' className='block text-sm font-medium leading-6 text-foreground'>
49
- {getString('new_password')}
50
+ {t('newPassword')}
50
51
  </label>
51
52
  <div className=''>
52
53
  <input
@@ -61,7 +62,7 @@ export default function EmailPasswordForm({ email, action }: { email: string; ac
61
62
  </div>
62
63
  <div>
63
64
  <label htmlFor='passwordConfirm' className='block text-sm font-medium leading-6 text-foreground'>
64
- {getString('confirm_new_password')}
65
+ {t('confirmNewPassword')}
65
66
  </label>
66
67
  <div className=''>
67
68
  <input
@@ -76,7 +77,7 @@ export default function EmailPasswordForm({ email, action }: { email: string; ac
76
77
  </div>
77
78
  <div className='mt-4'>
78
79
  <button className='rounded bg-blue-700 px-2 py-1 font-bold text-white'>
79
- {getString('change_password')}
80
+ {t('changePassword')}
80
81
  </button>
81
82
  </div>
82
83
  </form>
@@ -1,4 +1,4 @@
1
- import getString from 'nextjs-cms/translations'
1
+ import { useI18n } from 'nextjs-cms/translations/client'
2
2
  import React from 'react'
3
3
  import useModal from '@/hooks/useModal'
4
4
  import { Badge } from '@/components/ui/badge'
@@ -7,13 +7,14 @@ import InfoCard from '@/components/InfoCard'
7
7
  import { trpc } from '@/app/_trpc/client'
8
8
 
9
9
  export default function EmailQuotaForm({ email, action }: { email: string; action: any }) {
10
+ const t = useI18n()
10
11
  const { setModal, modal, modalResponse, setModalResponse } = useModal()
11
12
  const { toast } = useToast()
12
13
 
13
14
  const quotaMutation = trpc.cpanelEmails.quotaChange.useMutation({
14
15
  onError: (error) => {
15
16
  setModal({
16
- title: getString('error'),
17
+ title: t('error'),
17
18
  body: (
18
19
  <div className='p-4'>
19
20
  <InfoCard result={{ key: 'danger', title: error.message, status: false }} />
@@ -30,7 +31,7 @@ export default function EmailQuotaForm({ email, action }: { email: string; actio
30
31
  setModalResponse(null)
31
32
  toast({
32
33
  variant: 'success',
33
- title: getString('success'),
34
+ title: t('success'),
34
35
  })
35
36
  },
36
37
  })
@@ -46,7 +47,7 @@ export default function EmailQuotaForm({ email, action }: { email: string; actio
46
47
  <form onSubmit={handleSubmit} className='flex flex-col gap-2'>
47
48
  <div>
48
49
  <label htmlFor='quota' className='block text-sm font-medium leading-6 text-foreground'>
49
- {getString('emailQuota')}
50
+ {t('emailQuota')}
50
51
  </label>
51
52
  <div className=''>
52
53
  <input
@@ -58,13 +59,13 @@ export default function EmailQuotaForm({ email, action }: { email: string; actio
58
59
  className='block rounded-md border-0 bg-input p-2.5 text-foreground shadow-xs outline-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6'
59
60
  />
60
61
  <Badge className='rounded-sm border-muted-foreground text-muted-foreground' variant='secondary'>
61
- 0 = {getString('unlimited')}
62
+ 0 = {t('unlimited')}
62
63
  </Badge>
63
64
  </div>
64
65
  </div>
65
66
  <div className='mt-4'>
66
67
  <button className='rounded bg-blue-700 px-2 py-1 font-bold text-white'>
67
- {getString('update_quota')}
68
+ {t('updateQuota')}
68
69
  </button>
69
70
  </div>
70
71
  </form>
@@ -2,13 +2,14 @@
2
2
 
3
3
  import { useEffect } from 'react'
4
4
  import { EmailItem } from 'nextjs-cms/core/types'
5
- import getString from 'nextjs-cms/translations'
5
+ import { useI18n } from 'nextjs-cms/translations/client'
6
6
  import ContainerBox from '@/components/ContainerBox'
7
7
  import NewEmailForm from '@/components/NewEmailForm'
8
8
  import EmailCard from '@/components/EmailCard'
9
9
  import { trpc } from '@/app/_trpc/client'
10
10
 
11
11
  const EmailsPage = () => {
12
+ const t = useI18n()
12
13
  const controller = new AbortController()
13
14
 
14
15
  const { isLoading, isError, data, error, refetch } = trpc.cpanelEmails.getEmails.useQuery()
@@ -22,17 +23,17 @@ const EmailsPage = () => {
22
23
  return (
23
24
  <div className='bg-white dark:bg-slate-900'>
24
25
  <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'>
25
- <h1 className='text-3xl'>{getString('email_accounts')}</h1>
26
+ <h1 className='text-3xl'>{t('emailAccounts')}</h1>
26
27
  </div>
27
28
 
28
29
  <div className='flex flex-col gap-2 p-4'>
29
- <ContainerBox title={getString('create_new_email_account')}>
30
+ <ContainerBox title={t('createNewEmailAccount')}>
30
31
  <NewEmailForm action={() => refetch()} />
31
32
  </ContainerBox>
32
33
 
33
- {isLoading && <div>Loading...</div>}
34
+ {isLoading && <div>{t('loading')}</div>}
34
35
  {data && data.emails && data.emails.length > 0 && (
35
- <ContainerBox title={getString('admins_list')}>
36
+ <ContainerBox title={t('emailAccountsList')}>
36
37
  <div className='mt-2 grid w-full grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3'>
37
38
  {data.emails.map((email: EmailItem) => (
38
39
  <EmailCard key={email.user} action={() => refetch()} email={email} />
@@ -1,5 +1,5 @@
1
1
  import { PhotoGalleryItem } from 'nextjs-cms/core/types'
2
- import getString from 'nextjs-cms/translations'
2
+ import { useI18n } from 'nextjs-cms/translations/client'
3
3
  import { MinusIcon } from '@radix-ui/react-icons'
4
4
  import ProtectedImage from '@/components/ProtectedImage'
5
5
  import useModal from '@/hooks/useModal'
@@ -7,12 +7,13 @@ import { useToast } from '@/components/ui/use-toast'
7
7
  import { trpc } from '@/app/_trpc/client'
8
8
 
9
9
  const GalleryPhoto = ({ item, sectionName, action }: { item: PhotoGalleryItem; sectionName: string; action?: any }) => {
10
+ const t = useI18n()
10
11
  const { setModal, setModalResponse } = useModal()
11
12
  const deleteMutation = trpc.gallery.deletePhoto.useMutation({
12
13
  onError: (error) => {
13
14
  toast({
14
15
  variant: 'destructive',
15
- title: getString('delete_gallery_photo'),
16
+ title: t('deleteGalleryPhoto'),
16
17
  description: error.message,
17
18
  })
18
19
  },
@@ -22,7 +23,7 @@ const GalleryPhoto = ({ item, sectionName, action }: { item: PhotoGalleryItem; s
22
23
  setModalResponse(null)
23
24
  toast({
24
25
  variant: 'success',
25
- title: getString('gallery_photo_deleted'),
26
+ title: t('galleryPhotoDeleted'),
26
27
  })
27
28
  action()
28
29
  },
@@ -43,11 +44,11 @@ const GalleryPhoto = ({ item, sectionName, action }: { item: PhotoGalleryItem; s
43
44
  className='absolute -end-2 -top-2 z-10 h-6 w-6 rounded-full bg-red-500 p-1'
44
45
  onClick={() => {
45
46
  setModal({
46
- title: getString('delete_gallery_photo'),
47
+ title: t('deleteGalleryPhoto'),
47
48
  body: (
48
49
  <div className='p-4'>
49
50
  <div className='flex flex-col gap-4'>
50
- <div>{getString('delete_gallery_photo_text')}</div>
51
+ <div>{t('deleteGalleryPhotoText')}</div>
51
52
  <div className='flex gap-2'>
52
53
  <button
53
54
  className='rounded bg-green-600 px-2 py-1 text-white'
@@ -3,7 +3,7 @@
3
3
  import { useAxiosPrivate } from 'nextjs-cms/auth/hooks'
4
4
  import { RefObject, useEffect, useRef, useState } from 'react'
5
5
  import { SectionType } from 'nextjs-cms/core/types'
6
- import getString from 'nextjs-cms/translations'
6
+ import { useI18n } from 'nextjs-cms/translations/client'
7
7
  import useModal from '@/hooks/useModal'
8
8
  import { AxiosError } from 'axios'
9
9
  import InfoCard from '@/components/InfoCard'
@@ -31,6 +31,7 @@ export default function ItemEditPage({
31
31
  }[]
32
32
  action?: any
33
33
  }) {
34
+ const t = useI18n()
34
35
  const [response, setResponse] = useState<any>(null)
35
36
  const [progress, setProgress] = useState(0)
36
37
  const [progressVariant, setProgressVariant] = useState<'determinate' | 'query'>('determinate')
@@ -133,7 +134,7 @@ export default function ItemEditPage({
133
134
  setModal(null)
134
135
  toast({
135
136
  variant: 'success',
136
- description: getString('itemUpdatedSuccessfully'),
137
+ description: t('itemUpdatedSuccessfully'),
137
138
  })
138
139
  if (action) {
139
140
  await action().then(() => {
@@ -149,7 +150,7 @@ export default function ItemEditPage({
149
150
  setResponse(null)
150
151
  toast({
151
152
  variant: 'success',
152
- description: getString('itemUpdatedSuccessfully'),
153
+ description: t('itemUpdatedSuccessfully'),
153
154
  })
154
155
 
155
156
  // Redirect to the edit page
@@ -193,7 +194,7 @@ export default function ItemEditPage({
193
194
  <div className='absolute left-0 top-0 z-2 h-4 w-full bg-linear-to-r from-emerald-800 via-emerald-400 to-sky-600'></div>
194
195
  <h1 className='pb-4 text-4xl'>{data.section?.title.section}</h1>
195
196
  <span>
196
- /{getString('edit')} {data.section?.title.singular}
197
+ /{t('edit')} {data.section?.title.singular}
197
198
  </span>
198
199
  </div>
199
200
  <Form
@@ -55,7 +55,7 @@ function Layout({
55
55
  <div
56
56
  className={classNames({
57
57
  'transition-all duration-100 ease-in-out': true,
58
- 'float-right': true,
58
+ 'float-right rtl:float-left': true,
59
59
  'w-full md:w-[calc(100%-275px)]': true,
60
60
  })}
61
61
  >
@@ -59,7 +59,7 @@ const LoadingSpinners = ({ single = false }: { single?: boolean }) => {
59
59
  </div>
60
60
  </>
61
61
  )}
62
- <span className='sr-only'>Loading...</span>
62
+ <span className='sr-only'>Loading</span>
63
63
  </div>
64
64
  )
65
65
  }
@@ -1,9 +1,11 @@
1
1
  'use client'
2
2
 
3
- import getString from 'nextjs-cms/translations'
3
+ import { useI18n } from 'nextjs-cms/translations/client'
4
4
  import { trpc } from '@/app/_trpc/client'
5
5
  import { Badge } from '@/components/ui/badge'
6
6
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
7
+ import ErrorComponent from '@/components/ErrorComponent'
8
+ import { Spinner } from '@/components/ui/spinner'
7
9
 
8
10
  type LogMetadata = {
9
11
  fields?: string[]
@@ -28,34 +30,47 @@ const parseMetadata = (metadata?: string | null): LogMetadata | null => {
28
30
  }
29
31
 
30
32
  export default function LogPage() {
31
- const [data] = trpc.logs.list.useSuspenseQuery({
33
+ const t = useI18n()
34
+ const { data, isLoading, isError, error } = trpc.logs.list.useQuery({
32
35
  limit: 50,
33
36
  offset: 0,
34
37
  })
35
38
 
39
+ if (isLoading) {
40
+ return (
41
+ <div className='flex w-full items-center justify-center p-8'>
42
+ <Spinner className='size-8' />
43
+ </div>
44
+ )
45
+ }
46
+
47
+ if (isError) {
48
+ return <ErrorComponent message={error?.message || t('noAccessToSection')} />
49
+ }
50
+
36
51
  const logs = data?.items ?? []
37
52
  return (
38
53
  <div className='w-full'>
39
54
  <div className='text-foreground bg-linear-to-r from-sky-200 via-emerald-300 to-blue-600 p-8 font-extrabold dark:from-blue-800 dark:via-amber-700 dark:to-rose-900'>
40
- <h1 className='text-3xl'>{getString('logs')}</h1>
55
+ <h1 className='text-3xl'>{t('logs')}</h1>
41
56
  </div>
42
57
 
43
58
  <div className='flex flex-col gap-4 p-4'>
44
59
  <Table>
45
60
  <TableHeader>
46
61
  <TableRow>
47
- <TableHead>{getString('date')}</TableHead>
48
- <TableHead>{getString('action')}</TableHead>
49
- <TableHead>{getString('admin')}</TableHead>
50
- <TableHead>{getString('section')}</TableHead>
51
- <TableHead>{getString('details')}</TableHead>
62
+ <TableHead>{t('date')}</TableHead>
63
+ <TableHead>{t('action')}</TableHead>
64
+ <TableHead>{t('admin')}</TableHead>
65
+ <TableHead>{t('section')}</TableHead>
66
+ <TableHead>{t('details')}</TableHead>
52
67
  </TableRow>
53
68
  </TableHeader>
54
69
  <TableBody>
55
70
  {logs.length === 0 ? (
56
71
  <TableRow>
57
72
  <TableCell colSpan={5} className='text-muted-foreground text-center'>
58
- {getString('no_data')}
73
+ {t('noData')}
59
74
  </TableCell>
60
75
  </TableRow>
61
76
  ) : (
@@ -3,7 +3,7 @@ import classNames from 'classnames'
3
3
  import { MoonIcon, SunIcon, BellIcon, HamburgerMenuIcon } from '@radix-ui/react-icons'
4
4
  import { useTheme } from 'next-themes'
5
5
  import Link from 'next/link'
6
- import getString from 'nextjs-cms/translations'
6
+ import { useI18n } from 'nextjs-cms/translations/client'
7
7
  import { trpc } from '@/app/_trpc/client'
8
8
  import ProtectedImage from '@/components/ProtectedImage'
9
9
  import { Spinner } from '@/components/ui/spinner'
@@ -39,6 +39,7 @@ const formatTimestamp = (value?: Date | string | null) => {
39
39
  }
40
40
 
41
41
  export default function Navbar(props: Props) {
42
+ const t = useI18n()
42
43
  const { theme, setTheme } = useTheme()
43
44
  const session = useSession()
44
45
  const { toast } = useToast()
@@ -67,7 +68,7 @@ export default function Navbar(props: Props) {
67
68
  } catch (error: any) {
68
69
  toast({
69
70
  variant: 'destructive',
70
- title: getString('logoutError'),
71
+ title: t('logoutError'),
71
72
  description: error.message,
72
73
  })
73
74
  }
@@ -109,7 +110,7 @@ export default function Navbar(props: Props) {
109
110
  </div>*/}
110
111
  </div>
111
112
  <div className=''>
112
- <div className='ml-4 flex items-center md:ml-6'>
113
+ <div className='ms-4 flex items-center md:ms-6'>
113
114
  <div className='flex flex-row items-center gap-3'>
114
115
  <DropdownMenu onOpenChange={handleNotificationsOpenChange}>
115
116
  <DropdownMenuTrigger
@@ -125,20 +126,20 @@ export default function Navbar(props: Props) {
125
126
  alignOffset={-20}
126
127
  className='w-[400px] max-w-full ring-1 ring-sky-400/80 dark:ring-amber-900'
127
128
  >
128
- <DropdownMenuLabel>{getString('notifications')}</DropdownMenuLabel>
129
+ <DropdownMenuLabel>{t('notifications')}</DropdownMenuLabel>
129
130
  <DropdownMenuSeparator />
130
131
  <DropdownMenuGroup className='max-h-[320px] overflow-y-auto'>
131
132
  {logsQuery.isFetching && logs.length === 0 ? (
132
133
  <DropdownMenuItem disabled className='flex items-center gap-2'>
133
134
  <Spinner className='size-3' />
134
- <span>{getString('loading')}</span>
135
+ <span>{t('loading')}</span>
135
136
  </DropdownMenuItem>
136
137
  ) : logsQuery.isError ? (
137
138
  <DropdownMenuItem disabled>
138
- {getString('server_error')}
139
+ {logsQuery.error?.message || t('noAccessToSection')}
139
140
  </DropdownMenuItem>
140
141
  ) : logs.length === 0 ? (
141
- <DropdownMenuItem disabled>{getString('no_data')}</DropdownMenuItem>
142
+ <DropdownMenuItem disabled>{t('noData')}</DropdownMenuItem>
142
143
  ) : (
143
144
  logs.map((log) => {
144
145
  const actorLabel = log.actorUsername || log.actorId || ''
@@ -170,7 +171,7 @@ export default function Navbar(props: Props) {
170
171
 
171
172
  <Link href='/log'>
172
173
  <DropdownMenuItem className='cursor-pointer'>
173
- <span>{getString('seeAll')}</span>
174
+ <span>{t('seeAll')}</span>
174
175
  </DropdownMenuItem>
175
176
  </Link>
176
177
  </DropdownMenuContent>
@@ -219,8 +220,8 @@ export default function Navbar(props: Props) {
219
220
  <DropdownMenuGroup>
220
221
  <Link href='/settings'>
221
222
  <DropdownMenuItem className='cursor-pointer'>
222
- <Settings className='mr-2 h-4 w-4' />
223
- <span>{getString('accountSettings')}</span>
223
+ <Settings className='me-2 h-4 w-4' />
224
+ <span>{t('accountSettings')}</span>
224
225
  <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
225
226
  </DropdownMenuItem>
226
227
  </Link>
@@ -228,8 +229,8 @@ export default function Navbar(props: Props) {
228
229
  <DropdownMenuSeparator />
229
230
 
230
231
  <DropdownMenuItem className='cursor-pointer' onClick={handleLogout}>
231
- <LogOut className='mr-2 h-4 w-4' />
232
- <span>{getString('logout')}</span>
232
+ <LogOut className='me-2 h-4 w-4' />
233
+ <span>{t('logout')}</span>
233
234
  <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
234
235
  </DropdownMenuItem>
235
236
  </DropdownMenuContent>