create-nextjs-cms 0.9.0 → 0.9.1
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 +1 -1
- 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)/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 +16 -16
- 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 +20 -20
- 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 +19 -19
- 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/route.ts +49 -49
- package/templates/default/app/api/auth/session/route.ts +20 -20
- package/templates/default/app/api/document/route.ts +165 -165
- 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 +95 -95
- package/templates/default/app/api/submit/section/item/route.ts +56 -56
- package/templates/default/app/api/submit/section/simple/route.ts +86 -86
- 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 +228 -228
- package/templates/default/app/providers.tsx +152 -152
- package/templates/default/cms.config.ts +57 -58
- 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 +294 -294
- package/templates/default/components/Layout.tsx +84 -84
- package/templates/default/components/LoadingSpinners.tsx +67 -67
- package/templates/default/components/LocaleSwitcher.tsx +89 -89
- 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 +206 -206
- 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 +44 -44
- package/templates/default/components/ProtectedImage.tsx +143 -143
- package/templates/default/components/ProtectedVideo.tsx +76 -76
- package/templates/default/components/SectionIcon.tsx +8 -8
- package/templates/default/components/SectionItemCard.tsx +144 -144
- package/templates/default/components/SectionItemStatusBadge.tsx +17 -17
- package/templates/default/components/SectionPage.tsx +205 -205
- 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 +204 -204
- package/templates/default/components/SidebarDropdownItem.tsx +83 -83
- package/templates/default/components/SidebarItem.tsx +24 -24
- 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 +360 -360
- package/templates/default/components/form/FormInputElement.tsx +70 -70
- package/templates/default/components/form/FormInputs.tsx +111 -111
- 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 +46 -46
- 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 +85 -85
- package/templates/default/components/form/inputs/NumberFormInput.tsx +43 -43
- package/templates/default/components/form/inputs/PasswordFormInput.tsx +47 -47
- package/templates/default/components/form/inputs/PhotoFormInput.tsx +275 -275
- package/templates/default/components/form/inputs/RichTextFormInput.tsx +138 -138
- package/templates/default/components/form/inputs/SelectFormInput.tsx +175 -175
- package/templates/default/components/form/inputs/SlugFormInput.tsx +131 -131
- package/templates/default/components/form/inputs/TagsFormInput.tsx +260 -260
- package/templates/default/components/form/inputs/TextFormInput.tsx +51 -51
- package/templates/default/components/form/inputs/TextareaFormInput.tsx +50 -50
- package/templates/default/components/form/inputs/VideoFormInput.tsx +118 -118
- package/templates/default/components/multi-select.tsx +1146 -1146
- 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 +39 -39
- 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 +47 -47
- 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/direction.tsx +22 -22
- 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/spinner.tsx +16 -16
- 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 +475 -474
- 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 +1 -1
- package/templates/default/postcss.config.mjs +6 -6
- package/templates/default/proxy.ts +32 -32
- package/templates/default/tsconfig.json +48 -48
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
import React, { useEffect } from 'react'
|
|
2
|
-
import { useRefreshToken } from 'nextjs-cms/auth/hooks'
|
|
3
|
-
import { useSession } from 'nextjs-cms/auth/react'
|
|
4
|
-
|
|
5
|
-
const ProtectedVideo = ({
|
|
6
|
-
section,
|
|
7
|
-
fieldName,
|
|
8
|
-
file,
|
|
9
|
-
width,
|
|
10
|
-
height,
|
|
11
|
-
className = 'rounded-3xl object-cover',
|
|
12
|
-
}: {
|
|
13
|
-
section: string
|
|
14
|
-
fieldName: string
|
|
15
|
-
file: string
|
|
16
|
-
width: number
|
|
17
|
-
height: number
|
|
18
|
-
className?: string
|
|
19
|
-
}) => {
|
|
20
|
-
const refresh = useRefreshToken()
|
|
21
|
-
const ref = React.useRef<HTMLVideoElement>(null)
|
|
22
|
-
const [currentTime, setCurrentTime] = React.useState(0)
|
|
23
|
-
const [isSeeking, setIsSeeking] = React.useState(false)
|
|
24
|
-
|
|
25
|
-
const session = useSession()
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
if (ref.current && currentTime) {
|
|
28
|
-
ref.current.currentTime = currentTime
|
|
29
|
-
ref.current.play().then(() => {})
|
|
30
|
-
}
|
|
31
|
-
}, [session])
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<div className={className}>
|
|
35
|
-
{/**
|
|
36
|
-
* The video will stream as long as the access token is valid
|
|
37
|
-
* If the access token expires, the video will pause
|
|
38
|
-
* A refresh token request will be sent to `/api/auth/refresh`
|
|
39
|
-
* If the refresh token is valid, the video will resume playing
|
|
40
|
-
* If the refresh token is invalid, the user will be logged out
|
|
41
|
-
*/}
|
|
42
|
-
<video
|
|
43
|
-
ref={ref}
|
|
44
|
-
src={`/api/video?sectionName=${section}&fieldName=${fieldName}&name=${file}`}
|
|
45
|
-
className='max-w-full'
|
|
46
|
-
width={width}
|
|
47
|
-
controls={true}
|
|
48
|
-
height={height}
|
|
49
|
-
onErrorCapture={async (e) => {
|
|
50
|
-
if (isSeeking) return
|
|
51
|
-
setIsSeeking(true)
|
|
52
|
-
// console.log('seeked')
|
|
53
|
-
/**
|
|
54
|
-
* Pause the video
|
|
55
|
-
*/
|
|
56
|
-
e.currentTarget.pause()
|
|
57
|
-
/**
|
|
58
|
-
* Get the current time of the video and save it in the state
|
|
59
|
-
*/
|
|
60
|
-
setCurrentTime(e.currentTarget.currentTime)
|
|
61
|
-
/**
|
|
62
|
-
* Refresh the access token
|
|
63
|
-
* This will trigger the `useEffect` hook
|
|
64
|
-
* and set the current time of the video
|
|
65
|
-
* to the time it was paused at
|
|
66
|
-
* and play the video
|
|
67
|
-
*/
|
|
68
|
-
await refresh()
|
|
69
|
-
setIsSeeking(false)
|
|
70
|
-
}}
|
|
71
|
-
/>
|
|
72
|
-
</div>
|
|
73
|
-
)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export default ProtectedVideo
|
|
1
|
+
import React, { useEffect } from 'react'
|
|
2
|
+
import { useRefreshToken } from 'nextjs-cms/auth/hooks'
|
|
3
|
+
import { useSession } from 'nextjs-cms/auth/react'
|
|
4
|
+
|
|
5
|
+
const ProtectedVideo = ({
|
|
6
|
+
section,
|
|
7
|
+
fieldName,
|
|
8
|
+
file,
|
|
9
|
+
width,
|
|
10
|
+
height,
|
|
11
|
+
className = 'rounded-3xl object-cover',
|
|
12
|
+
}: {
|
|
13
|
+
section: string
|
|
14
|
+
fieldName: string
|
|
15
|
+
file: string
|
|
16
|
+
width: number
|
|
17
|
+
height: number
|
|
18
|
+
className?: string
|
|
19
|
+
}) => {
|
|
20
|
+
const refresh = useRefreshToken()
|
|
21
|
+
const ref = React.useRef<HTMLVideoElement>(null)
|
|
22
|
+
const [currentTime, setCurrentTime] = React.useState(0)
|
|
23
|
+
const [isSeeking, setIsSeeking] = React.useState(false)
|
|
24
|
+
|
|
25
|
+
const session = useSession()
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (ref.current && currentTime) {
|
|
28
|
+
ref.current.currentTime = currentTime
|
|
29
|
+
ref.current.play().then(() => {})
|
|
30
|
+
}
|
|
31
|
+
}, [session])
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className={className}>
|
|
35
|
+
{/**
|
|
36
|
+
* The video will stream as long as the access token is valid
|
|
37
|
+
* If the access token expires, the video will pause
|
|
38
|
+
* A refresh token request will be sent to `/api/auth/refresh`
|
|
39
|
+
* If the refresh token is valid, the video will resume playing
|
|
40
|
+
* If the refresh token is invalid, the user will be logged out
|
|
41
|
+
*/}
|
|
42
|
+
<video
|
|
43
|
+
ref={ref}
|
|
44
|
+
src={`/api/video?sectionName=${section}&fieldName=${fieldName}&name=${file}`}
|
|
45
|
+
className='max-w-full'
|
|
46
|
+
width={width}
|
|
47
|
+
controls={true}
|
|
48
|
+
height={height}
|
|
49
|
+
onErrorCapture={async (e) => {
|
|
50
|
+
if (isSeeking) return
|
|
51
|
+
setIsSeeking(true)
|
|
52
|
+
// console.log('seeked')
|
|
53
|
+
/**
|
|
54
|
+
* Pause the video
|
|
55
|
+
*/
|
|
56
|
+
e.currentTarget.pause()
|
|
57
|
+
/**
|
|
58
|
+
* Get the current time of the video and save it in the state
|
|
59
|
+
*/
|
|
60
|
+
setCurrentTime(e.currentTarget.currentTime)
|
|
61
|
+
/**
|
|
62
|
+
* Refresh the access token
|
|
63
|
+
* This will trigger the `useEffect` hook
|
|
64
|
+
* and set the current time of the video
|
|
65
|
+
* to the time it was paused at
|
|
66
|
+
* and play the video
|
|
67
|
+
*/
|
|
68
|
+
await refresh()
|
|
69
|
+
setIsSeeking(false)
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default ProtectedVideo
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import {DynamicIcon} from 'lucide-react/dynamic'
|
|
4
|
-
import type { IconName } from 'lucide-react/dynamic'
|
|
5
|
-
|
|
6
|
-
export default function SectionIcon({ name, className }: { name: string; className?: string }) {
|
|
7
|
-
return <DynamicIcon name={name as IconName} className={className} />
|
|
8
|
-
}
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {DynamicIcon} from 'lucide-react/dynamic'
|
|
4
|
+
import type { IconName } from 'lucide-react/dynamic'
|
|
5
|
+
|
|
6
|
+
export default function SectionIcon({ name, className }: { name: string; className?: string }) {
|
|
7
|
+
return <DynamicIcon name={name as IconName} className={className} />
|
|
8
|
+
}
|
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { useI18n } from 'nextjs-cms/translations/client'
|
|
3
|
-
import useModal from '@/hooks/useModal'
|
|
4
|
-
import ProtectedImage from '@/components/ProtectedImage'
|
|
5
|
-
import SectionItemStatusBadge from '@/components/SectionItemStatusBadge'
|
|
6
|
-
import { displayDateFromString } from 'nextjs-cms/utils'
|
|
7
|
-
import Link from 'next/link'
|
|
8
|
-
import { CardTitle, CardHeader, CardFooter, Card, CardContent } from '@/components/ui/card'
|
|
9
|
-
import { Button } from '@/components/ui/button'
|
|
10
|
-
import { PersonIcon } from '@radix-ui/react-icons'
|
|
11
|
-
import { trpc } from '@/app/_trpc/client'
|
|
12
|
-
import type { RouterOutputs } from 'nextjs-cms/api'
|
|
13
|
-
|
|
14
|
-
// Used to get elements of array types as types
|
|
15
|
-
type ArrElement<ArrType> = ArrType extends readonly (infer ElementType)[] ? ElementType : never
|
|
16
|
-
|
|
17
|
-
export default function SectionItemCard({
|
|
18
|
-
sectionName,
|
|
19
|
-
item,
|
|
20
|
-
action,
|
|
21
|
-
}: {
|
|
22
|
-
sectionName: string
|
|
23
|
-
item: ArrElement<RouterOutputs['hasItemsSections']['listItems']['items']>
|
|
24
|
-
action: any
|
|
25
|
-
}) {
|
|
26
|
-
const t = useI18n()
|
|
27
|
-
const { setModal } = useModal()
|
|
28
|
-
const utils = trpc.useUtils()
|
|
29
|
-
const deleteMutation = trpc.hasItemsSections.deleteItem.useMutation({
|
|
30
|
-
onError: (error) => {
|
|
31
|
-
setModal({
|
|
32
|
-
title: t('delete'),
|
|
33
|
-
body: (
|
|
34
|
-
<div className='rounded border-2 border-dashed border-red-500 bg-red-100 p-2 text-red-800'>
|
|
35
|
-
{error.message}
|
|
36
|
-
</div>
|
|
37
|
-
),
|
|
38
|
-
headerColor: 'bg-red-700',
|
|
39
|
-
titleColor: 'text-white',
|
|
40
|
-
lang: 'en',
|
|
41
|
-
})
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
onSuccess: async (data) => {
|
|
45
|
-
await utils.hasItemsSections.listItems.invalidate({
|
|
46
|
-
sectionName: sectionName,
|
|
47
|
-
})
|
|
48
|
-
action()
|
|
49
|
-
setModal({
|
|
50
|
-
title: t('delete'),
|
|
51
|
-
body: (
|
|
52
|
-
<div className='rounded border-2 border-dashed border-green-500 bg-green-100 p-2 text-green-800'>
|
|
53
|
-
{t('itemDeletedSuccessfully')}
|
|
54
|
-
</div>
|
|
55
|
-
),
|
|
56
|
-
headerColor: 'bg-green-700',
|
|
57
|
-
titleColor: 'text-white',
|
|
58
|
-
lang: 'en',
|
|
59
|
-
})
|
|
60
|
-
},
|
|
61
|
-
})
|
|
62
|
-
const handleDelete = async () => {
|
|
63
|
-
deleteMutation.mutate({
|
|
64
|
-
sectionName: sectionName,
|
|
65
|
-
sectionItemId: item.id,
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return (
|
|
70
|
-
<>
|
|
71
|
-
<Card className='relative mx-auto w-full max-w-md overflow-hidden rounded-lg border-secondary-foreground pb-[85px] shadow-sm'>
|
|
72
|
-
{item.coverPhoto ? (
|
|
73
|
-
<div className='relative h-[200px]'>
|
|
74
|
-
{[0, 1].includes(item.permission) ? <SectionItemStatusBadge status={item.permission} /> : null}
|
|
75
|
-
<ProtectedImage
|
|
76
|
-
className='h-64 w-full object-cover'
|
|
77
|
-
photo={item.coverPhoto}
|
|
78
|
-
section={sectionName}
|
|
79
|
-
isThumb={true}
|
|
80
|
-
alt={'item'}
|
|
81
|
-
width={100}
|
|
82
|
-
height={50}
|
|
83
|
-
fill={true}
|
|
84
|
-
/>
|
|
85
|
-
</div>
|
|
86
|
-
) : null}
|
|
87
|
-
<CardHeader className='p-6'>
|
|
88
|
-
<CardTitle className='text-xl font-bold'>{item.headingTitle}</CardTitle>
|
|
89
|
-
</CardHeader>
|
|
90
|
-
<CardContent className='mt-2'>
|
|
91
|
-
<div className='flex flex-row flex-wrap gap-1'>
|
|
92
|
-
<PersonIcon fontSize='small' /> {item.createdBy},{' '}
|
|
93
|
-
{displayDateFromString(item.createdAt, 'en', false, ',')}
|
|
94
|
-
</div>
|
|
95
|
-
</CardContent>
|
|
96
|
-
<CardFooter className='absolute bottom-0 start-0 flex h-[85px] w-full justify-between p-6'>
|
|
97
|
-
<Link
|
|
98
|
-
href={`/edit/${sectionName}/${item.id}`}
|
|
99
|
-
className='rounded bg-primary px-3 py-1 font-semibold text-primary-foreground'
|
|
100
|
-
>
|
|
101
|
-
{t('edit')}
|
|
102
|
-
</Link>
|
|
103
|
-
<Button
|
|
104
|
-
onClick={() => {
|
|
105
|
-
setModal({
|
|
106
|
-
title: t('delete'),
|
|
107
|
-
body: (
|
|
108
|
-
<div className='p-4'>
|
|
109
|
-
<div className='flex flex-col gap-4'>
|
|
110
|
-
<div>{t('deleteItemText')}</div>
|
|
111
|
-
<div className='flex gap-2'>
|
|
112
|
-
<button
|
|
113
|
-
className='rounded bg-success px-2 py-1 text-success-foreground'
|
|
114
|
-
onClick={handleDelete}
|
|
115
|
-
>
|
|
116
|
-
Yes
|
|
117
|
-
</button>
|
|
118
|
-
<button
|
|
119
|
-
className='rounded bg-destructive px-2 py-1 text-destructive-foreground'
|
|
120
|
-
onClick={() => {
|
|
121
|
-
setModal(null)
|
|
122
|
-
}}
|
|
123
|
-
>
|
|
124
|
-
No
|
|
125
|
-
</button>
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
),
|
|
130
|
-
headerColor: 'bg-red-700',
|
|
131
|
-
titleColor: 'text-white',
|
|
132
|
-
lang: 'en',
|
|
133
|
-
})
|
|
134
|
-
}}
|
|
135
|
-
variant='destructive'
|
|
136
|
-
className='rounded px-3 py-1 font-semibold'
|
|
137
|
-
>
|
|
138
|
-
{t('delete')}
|
|
139
|
-
</Button>
|
|
140
|
-
</CardFooter>
|
|
141
|
-
</Card>
|
|
142
|
-
</>
|
|
143
|
-
)
|
|
144
|
-
}
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useI18n } from 'nextjs-cms/translations/client'
|
|
3
|
+
import useModal from '@/hooks/useModal'
|
|
4
|
+
import ProtectedImage from '@/components/ProtectedImage'
|
|
5
|
+
import SectionItemStatusBadge from '@/components/SectionItemStatusBadge'
|
|
6
|
+
import { displayDateFromString } from 'nextjs-cms/utils'
|
|
7
|
+
import Link from 'next/link'
|
|
8
|
+
import { CardTitle, CardHeader, CardFooter, Card, CardContent } from '@/components/ui/card'
|
|
9
|
+
import { Button } from '@/components/ui/button'
|
|
10
|
+
import { PersonIcon } from '@radix-ui/react-icons'
|
|
11
|
+
import { trpc } from '@/app/_trpc/client'
|
|
12
|
+
import type { RouterOutputs } from 'nextjs-cms/api'
|
|
13
|
+
|
|
14
|
+
// Used to get elements of array types as types
|
|
15
|
+
type ArrElement<ArrType> = ArrType extends readonly (infer ElementType)[] ? ElementType : never
|
|
16
|
+
|
|
17
|
+
export default function SectionItemCard({
|
|
18
|
+
sectionName,
|
|
19
|
+
item,
|
|
20
|
+
action,
|
|
21
|
+
}: {
|
|
22
|
+
sectionName: string
|
|
23
|
+
item: ArrElement<RouterOutputs['hasItemsSections']['listItems']['items']>
|
|
24
|
+
action: any
|
|
25
|
+
}) {
|
|
26
|
+
const t = useI18n()
|
|
27
|
+
const { setModal } = useModal()
|
|
28
|
+
const utils = trpc.useUtils()
|
|
29
|
+
const deleteMutation = trpc.hasItemsSections.deleteItem.useMutation({
|
|
30
|
+
onError: (error) => {
|
|
31
|
+
setModal({
|
|
32
|
+
title: t('delete'),
|
|
33
|
+
body: (
|
|
34
|
+
<div className='rounded border-2 border-dashed border-red-500 bg-red-100 p-2 text-red-800'>
|
|
35
|
+
{error.message}
|
|
36
|
+
</div>
|
|
37
|
+
),
|
|
38
|
+
headerColor: 'bg-red-700',
|
|
39
|
+
titleColor: 'text-white',
|
|
40
|
+
lang: 'en',
|
|
41
|
+
})
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
onSuccess: async (data) => {
|
|
45
|
+
await utils.hasItemsSections.listItems.invalidate({
|
|
46
|
+
sectionName: sectionName,
|
|
47
|
+
})
|
|
48
|
+
action()
|
|
49
|
+
setModal({
|
|
50
|
+
title: t('delete'),
|
|
51
|
+
body: (
|
|
52
|
+
<div className='rounded border-2 border-dashed border-green-500 bg-green-100 p-2 text-green-800'>
|
|
53
|
+
{t('itemDeletedSuccessfully')}
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
headerColor: 'bg-green-700',
|
|
57
|
+
titleColor: 'text-white',
|
|
58
|
+
lang: 'en',
|
|
59
|
+
})
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
const handleDelete = async () => {
|
|
63
|
+
deleteMutation.mutate({
|
|
64
|
+
sectionName: sectionName,
|
|
65
|
+
sectionItemId: item.id,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
<Card className='relative mx-auto w-full max-w-md overflow-hidden rounded-lg border-secondary-foreground pb-[85px] shadow-sm'>
|
|
72
|
+
{item.coverPhoto ? (
|
|
73
|
+
<div className='relative h-[200px]'>
|
|
74
|
+
{[0, 1].includes(item.permission) ? <SectionItemStatusBadge status={item.permission} /> : null}
|
|
75
|
+
<ProtectedImage
|
|
76
|
+
className='h-64 w-full object-cover'
|
|
77
|
+
photo={item.coverPhoto}
|
|
78
|
+
section={sectionName}
|
|
79
|
+
isThumb={true}
|
|
80
|
+
alt={'item'}
|
|
81
|
+
width={100}
|
|
82
|
+
height={50}
|
|
83
|
+
fill={true}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
) : null}
|
|
87
|
+
<CardHeader className='p-6'>
|
|
88
|
+
<CardTitle className='text-xl font-bold'>{item.headingTitle}</CardTitle>
|
|
89
|
+
</CardHeader>
|
|
90
|
+
<CardContent className='mt-2'>
|
|
91
|
+
<div className='flex flex-row flex-wrap gap-1'>
|
|
92
|
+
<PersonIcon fontSize='small' /> {item.createdBy},{' '}
|
|
93
|
+
{displayDateFromString(item.createdAt, 'en', false, ',')}
|
|
94
|
+
</div>
|
|
95
|
+
</CardContent>
|
|
96
|
+
<CardFooter className='absolute bottom-0 start-0 flex h-[85px] w-full justify-between p-6'>
|
|
97
|
+
<Link
|
|
98
|
+
href={`/edit/${sectionName}/${item.id}`}
|
|
99
|
+
className='rounded bg-primary px-3 py-1 font-semibold text-primary-foreground'
|
|
100
|
+
>
|
|
101
|
+
{t('edit')}
|
|
102
|
+
</Link>
|
|
103
|
+
<Button
|
|
104
|
+
onClick={() => {
|
|
105
|
+
setModal({
|
|
106
|
+
title: t('delete'),
|
|
107
|
+
body: (
|
|
108
|
+
<div className='p-4'>
|
|
109
|
+
<div className='flex flex-col gap-4'>
|
|
110
|
+
<div>{t('deleteItemText')}</div>
|
|
111
|
+
<div className='flex gap-2'>
|
|
112
|
+
<button
|
|
113
|
+
className='rounded bg-success px-2 py-1 text-success-foreground'
|
|
114
|
+
onClick={handleDelete}
|
|
115
|
+
>
|
|
116
|
+
Yes
|
|
117
|
+
</button>
|
|
118
|
+
<button
|
|
119
|
+
className='rounded bg-destructive px-2 py-1 text-destructive-foreground'
|
|
120
|
+
onClick={() => {
|
|
121
|
+
setModal(null)
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
No
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
),
|
|
130
|
+
headerColor: 'bg-red-700',
|
|
131
|
+
titleColor: 'text-white',
|
|
132
|
+
lang: 'en',
|
|
133
|
+
})
|
|
134
|
+
}}
|
|
135
|
+
variant='destructive'
|
|
136
|
+
className='rounded px-3 py-1 font-semibold'
|
|
137
|
+
>
|
|
138
|
+
{t('delete')}
|
|
139
|
+
</Button>
|
|
140
|
+
</CardFooter>
|
|
141
|
+
</Card>
|
|
142
|
+
</>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { useI18n } from 'nextjs-cms/translations/client'
|
|
3
|
-
import { Badge } from '@/components/ui/badge'
|
|
4
|
-
|
|
5
|
-
const SectionItemStatusBadge = ({ status }: { status: number }) => {
|
|
6
|
-
const t = useI18n()
|
|
7
|
-
return (
|
|
8
|
-
<Badge
|
|
9
|
-
variant={status === 1 ? 'success' : 'destructive'}
|
|
10
|
-
className='absolute top-3 shadow-sm left-3 z-30 font-bold'
|
|
11
|
-
>
|
|
12
|
-
{status === 1 ? t('approved') : t('pendingApproval')}
|
|
13
|
-
</Badge>
|
|
14
|
-
)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export default SectionItemStatusBadge
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useI18n } from 'nextjs-cms/translations/client'
|
|
3
|
+
import { Badge } from '@/components/ui/badge'
|
|
4
|
+
|
|
5
|
+
const SectionItemStatusBadge = ({ status }: { status: number }) => {
|
|
6
|
+
const t = useI18n()
|
|
7
|
+
return (
|
|
8
|
+
<Badge
|
|
9
|
+
variant={status === 1 ? 'success' : 'destructive'}
|
|
10
|
+
className='absolute top-3 shadow-sm left-3 z-30 font-bold'
|
|
11
|
+
>
|
|
12
|
+
{status === 1 ? t('approved') : t('pendingApproval')}
|
|
13
|
+
</Badge>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default SectionItemStatusBadge
|