create-nextjs-cms 0.5.8
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 -0
- package/README.md +71 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +395 -0
- package/dist/lib/utils.d.ts +11 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +48 -0
- package/package.json +44 -0
- package/templates/default/.env +24 -0
- package/templates/default/.env.development +8 -0
- package/templates/default/.eslintrc.json +5 -0
- package/templates/default/.prettierignore +7 -0
- package/templates/default/.prettierrc.json +19 -0
- package/templates/default/CHANGELOG.md +77 -0
- package/templates/default/README.md +45 -0
- package/templates/default/app/(auth)/auth/login/LoginPage.tsx +175 -0
- package/templates/default/app/(auth)/auth/login/page.tsx +12 -0
- package/templates/default/app/(rootLayout)/admins/page.tsx +5 -0
- package/templates/default/app/(rootLayout)/advanced/page.tsx +5 -0
- package/templates/default/app/(rootLayout)/analytics/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/browse/[section]/[page]/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/categorized/[section]/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/dashboard/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/edit/[section]/[itemId]/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/emails/page.tsx +6 -0
- package/templates/default/app/(rootLayout)/layout.tsx +5 -0
- package/templates/default/app/(rootLayout)/loading.tsx +10 -0
- package/templates/default/app/(rootLayout)/log/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/new/[section]/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/page.tsx +9 -0
- package/templates/default/app/(rootLayout)/section/[section]/page.tsx +7 -0
- package/templates/default/app/(rootLayout)/settings/page.tsx +7 -0
- package/templates/default/app/_trpc/client.ts +4 -0
- package/templates/default/app/api/auth/csrf/route.ts +25 -0
- package/templates/default/app/api/auth/refresh/route.ts +10 -0
- package/templates/default/app/api/auth/route.ts +23 -0
- package/templates/default/app/api/auth/session/route.ts +20 -0
- package/templates/default/app/api/editor/photo/route.ts +42 -0
- package/templates/default/app/api/photo/route.ts +27 -0
- package/templates/default/app/api/placeholder/route.ts +7 -0
- package/templates/default/app/api/submit/section/item/[slug]/route.ts +63 -0
- package/templates/default/app/api/submit/section/item/route.ts +53 -0
- package/templates/default/app/api/submit/section/simple/route.ts +54 -0
- package/templates/default/app/api/trpc/[trpc]/route.ts +33 -0
- package/templates/default/app/api/video/route.ts +174 -0
- package/templates/default/app/dictionaries.ts +14 -0
- package/templates/default/app/layout.tsx +28 -0
- package/templates/default/app/providers.tsx +151 -0
- package/templates/default/cli.ts +4 -0
- package/templates/default/components/AdminCard.tsx +163 -0
- package/templates/default/components/AdminEditPage.tsx +123 -0
- package/templates/default/components/AdminPrivilegeCard.tsx +184 -0
- package/templates/default/components/AdminsPage.tsx +43 -0
- package/templates/default/components/AdvancedSettingsPage.tsx +167 -0
- package/templates/default/components/AnalyticsPage.tsx +127 -0
- package/templates/default/components/BarChartBox.tsx +43 -0
- package/templates/default/components/BrowsePage.tsx +119 -0
- package/templates/default/components/CategorizedSectionPage.tsx +36 -0
- package/templates/default/components/CategoryDeleteConfirmPage.tsx +129 -0
- package/templates/default/components/CategorySectionSelectInput.tsx +139 -0
- package/templates/default/components/ConditionalFields.tsx +49 -0
- package/templates/default/components/ContainerBox.tsx +24 -0
- package/templates/default/components/DashboardPage.tsx +187 -0
- package/templates/default/components/DashboardPageAlt.tsx +43 -0
- package/templates/default/components/DefaultNavItems.tsx +3 -0
- package/templates/default/components/Dropzone.tsx +153 -0
- package/templates/default/components/EmailCard.tsx +137 -0
- package/templates/default/components/EmailPasswordForm.tsx +84 -0
- package/templates/default/components/EmailQuotaForm.tsx +72 -0
- package/templates/default/components/EmailsPage.tsx +48 -0
- package/templates/default/components/GalleryPhoto.tsx +93 -0
- package/templates/default/components/InfoCard.tsx +94 -0
- package/templates/default/components/ItemEditPage.tsx +217 -0
- package/templates/default/components/Layout.tsx +70 -0
- package/templates/default/components/LoadingSpinners.tsx +67 -0
- package/templates/default/components/LogPage.tsx +17 -0
- package/templates/default/components/Modal.tsx +99 -0
- package/templates/default/components/Navbar.tsx +29 -0
- package/templates/default/components/NavbarAlt.tsx +182 -0
- package/templates/default/components/NewAdminForm.tsx +172 -0
- package/templates/default/components/NewEmailForm.tsx +131 -0
- package/templates/default/components/NewPage.tsx +206 -0
- package/templates/default/components/NewVariantComponent.tsx +228 -0
- package/templates/default/components/PhotoGallery.tsx +35 -0
- package/templates/default/components/PieChartBox.tsx +101 -0
- package/templates/default/components/ProgressBar.tsx +24 -0
- package/templates/default/components/ProtectedDocument.tsx +78 -0
- package/templates/default/components/ProtectedImage.tsx +143 -0
- package/templates/default/components/ProtectedVideo.tsx +76 -0
- package/templates/default/components/SectionItemCard.tsx +143 -0
- package/templates/default/components/SectionItemStatusBadge.tsx +16 -0
- package/templates/default/components/SectionPage.tsx +124 -0
- package/templates/default/components/SelectBox.tsx +99 -0
- package/templates/default/components/SelectInputButtons.tsx +124 -0
- package/templates/default/components/SettingsPage.tsx +238 -0
- package/templates/default/components/Sidebar.tsx +209 -0
- package/templates/default/components/SidebarDropdownItem.tsx +74 -0
- package/templates/default/components/SidebarItem.tsx +19 -0
- package/templates/default/components/TempPage.tsx +12 -0
- package/templates/default/components/ThemeProvider.tsx +8 -0
- package/templates/default/components/TooltipComponent.tsx +27 -0
- package/templates/default/components/VariantCard.tsx +123 -0
- package/templates/default/components/VariantEditPage.tsx +229 -0
- package/templates/default/components/analytics/BounceRate.tsx +69 -0
- package/templates/default/components/analytics/LivePageViews.tsx +54 -0
- package/templates/default/components/analytics/LiveUsersCount.tsx +32 -0
- package/templates/default/components/analytics/MonthlyPageViews.tsx +41 -0
- package/templates/default/components/analytics/TopCountries.tsx +51 -0
- package/templates/default/components/analytics/TopDevices.tsx +45 -0
- package/templates/default/components/analytics/TopMediums.tsx +57 -0
- package/templates/default/components/analytics/TopSources.tsx +44 -0
- package/templates/default/components/analytics/TotalPageViews.tsx +40 -0
- package/templates/default/components/analytics/TotalSessions.tsx +40 -0
- package/templates/default/components/analytics/TotalUniqueUsers.tsx +40 -0
- package/templates/default/components/custom/RightHomeRoomVariantCard.tsx +137 -0
- package/templates/default/components/dndKit/Draggable.tsx +21 -0
- package/templates/default/components/dndKit/Droppable.tsx +20 -0
- package/templates/default/components/dndKit/SortableItem.tsx +18 -0
- package/templates/default/components/form/DateRangeFormInput.tsx +55 -0
- package/templates/default/components/form/Form.tsx +298 -0
- package/templates/default/components/form/FormInputElement.tsx +68 -0
- package/templates/default/components/form/FormInputs.tsx +108 -0
- package/templates/default/components/form/helpers/util.ts +20 -0
- package/templates/default/components/form/inputs/CheckboxFormInput.tsx +33 -0
- package/templates/default/components/form/inputs/ColorFormInput.tsx +44 -0
- package/templates/default/components/form/inputs/DateFormInput.tsx +107 -0
- package/templates/default/components/form/inputs/DocumentFormInput.tsx +124 -0
- package/templates/default/components/form/inputs/MapFormInput.tsx +139 -0
- package/templates/default/components/form/inputs/MultipleSelectFormInput.tsx +150 -0
- package/templates/default/components/form/inputs/NumberFormInput.tsx +42 -0
- package/templates/default/components/form/inputs/PasswordFormInput.tsx +47 -0
- package/templates/default/components/form/inputs/PhotoFormInput.tsx +218 -0
- package/templates/default/components/form/inputs/RichTextFormInput.tsx +133 -0
- package/templates/default/components/form/inputs/SelectFormInput.tsx +164 -0
- package/templates/default/components/form/inputs/TagsFormInput.tsx +63 -0
- package/templates/default/components/form/inputs/TextFormInput.tsx +48 -0
- package/templates/default/components/form/inputs/TextareaFormInput.tsx +47 -0
- package/templates/default/components/form/inputs/VideoFormInput.tsx +117 -0
- package/templates/default/components/pagination/Pagination.tsx +36 -0
- package/templates/default/components/pagination/PaginationButtons.tsx +145 -0
- package/templates/default/components/ui/accordion.tsx +57 -0
- package/templates/default/components/ui/alert.tsx +46 -0
- package/templates/default/components/ui/badge.tsx +33 -0
- package/templates/default/components/ui/button.tsx +57 -0
- package/templates/default/components/ui/calendar.tsx +68 -0
- package/templates/default/components/ui/card.tsx +76 -0
- package/templates/default/components/ui/checkbox.tsx +29 -0
- package/templates/default/components/ui/dropdown-menu.tsx +205 -0
- package/templates/default/components/ui/input.tsx +25 -0
- package/templates/default/components/ui/label.tsx +26 -0
- package/templates/default/components/ui/popover.tsx +31 -0
- package/templates/default/components/ui/scroll-area.tsx +42 -0
- package/templates/default/components/ui/select.tsx +164 -0
- package/templates/default/components/ui/sheet.tsx +107 -0
- package/templates/default/components/ui/switch.tsx +29 -0
- package/templates/default/components/ui/table.tsx +120 -0
- package/templates/default/components/ui/tabs.tsx +55 -0
- package/templates/default/components/ui/toast.tsx +113 -0
- package/templates/default/components/ui/toaster.tsx +35 -0
- package/templates/default/components/ui/tooltip.tsx +30 -0
- package/templates/default/components/ui/use-toast.ts +188 -0
- package/templates/default/components.json +16 -0
- package/templates/default/context/ModalProvider.tsx +53 -0
- package/templates/default/drizzle.config.ts +4 -0
- package/templates/default/dynamic-schemas/schema.ts +373 -0
- package/templates/default/env/env.js +130 -0
- package/templates/default/envConfig.ts +4 -0
- package/templates/default/hooks/useModal.ts +8 -0
- package/templates/default/lib/apiHelpers.ts +106 -0
- package/templates/default/lz.config.ts +40 -0
- package/templates/default/middleware.ts +33 -0
- package/templates/default/next.config.ts +46 -0
- package/templates/default/package.json +134 -0
- package/templates/default/postcss.config.js +6 -0
- package/templates/default/postinstall.js +14 -0
- package/templates/default/public/blank_avatar.png +0 -0
- package/templates/default/public/favicon.ico +0 -0
- package/templates/default/public/img/placeholder.svg +1 -0
- package/templates/default/public/lazemni_logo.png +0 -0
- package/templates/default/public/next.svg +1 -0
- package/templates/default/public/vercel.svg +1 -0
- package/templates/default/section-tests.ts +92 -0
- package/templates/default/styles/globals.css +88 -0
- package/templates/default/tailwind.config.js +95 -0
- package/templates/default/test.ts +77 -0
- package/templates/default/tsconfig.json +44 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import getString from 'nextjs-cms/translations'
|
|
2
|
+
import { BarChartDataItem } from 'nextjs-cms/core/types'
|
|
3
|
+
import { Badge } from '@/components/ui/badge'
|
|
4
|
+
import { formatNumber } from 'nextjs-cms/utils'
|
|
5
|
+
import ContainerBox from '@/components/ContainerBox'
|
|
6
|
+
import React from 'react'
|
|
7
|
+
import { useAxiosPrivate } from 'nextjs-cms/auth/hooks'
|
|
8
|
+
import { useQuery } from '@tanstack/react-query'
|
|
9
|
+
import { getAnalytics } from '@/lib/apiHelpers'
|
|
10
|
+
import LoadingSpinners from '@/components/LoadingSpinners'
|
|
11
|
+
|
|
12
|
+
export const TopDevices = ({ fromDate, toDate }: { fromDate: Date | string | null; toDate: Date | string | null }) => {
|
|
13
|
+
const axiosPrivate = useAxiosPrivate()
|
|
14
|
+
const controller = new AbortController()
|
|
15
|
+
|
|
16
|
+
const { isLoading, isError, data, error } = useQuery({
|
|
17
|
+
queryKey: ['analyticsPage', 'topDevices', fromDate, toDate],
|
|
18
|
+
queryFn: () =>
|
|
19
|
+
getAnalytics({
|
|
20
|
+
requestType: 'topDevices',
|
|
21
|
+
axiosPrivate,
|
|
22
|
+
controller,
|
|
23
|
+
fromDate,
|
|
24
|
+
toDate,
|
|
25
|
+
}),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<ContainerBox title={getString('devices')}>
|
|
30
|
+
<div className='w-full'>
|
|
31
|
+
<h1 className='font-bold border-b pb-1 my-1'>{getString('topDevices')}</h1>
|
|
32
|
+
{isLoading && <LoadingSpinners single={true} />}
|
|
33
|
+
{data?.topDevices?.length > 0 &&
|
|
34
|
+
data.topDevices.map((device: BarChartDataItem, index: number) => (
|
|
35
|
+
<div key={`${index}-${device.name}`} className='w-full text-sm flex justify-between'>
|
|
36
|
+
<span className='font-bold text-foreground'>{device.name}:</span>
|
|
37
|
+
<Badge variant='outline' className='ms-2'>
|
|
38
|
+
{formatNumber(device.value)}
|
|
39
|
+
</Badge>
|
|
40
|
+
</div>
|
|
41
|
+
))}
|
|
42
|
+
</div>
|
|
43
|
+
</ContainerBox>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import getString from 'nextjs-cms/translations'
|
|
2
|
+
import { BarChartDataItem } from 'nextjs-cms/core/types'
|
|
3
|
+
import { Badge } from '@/components/ui/badge'
|
|
4
|
+
import { formatNumber } from 'nextjs-cms/utils'
|
|
5
|
+
import ContainerBox from '@/components/ContainerBox'
|
|
6
|
+
import React, { useEffect } from 'react'
|
|
7
|
+
import { useAxiosPrivate } from 'nextjs-cms/auth/hooks'
|
|
8
|
+
import { useQuery } from '@tanstack/react-query'
|
|
9
|
+
import { getAnalytics } from '@/lib/apiHelpers'
|
|
10
|
+
import LoadingSpinners from '@/components/LoadingSpinners'
|
|
11
|
+
|
|
12
|
+
export const TopMediums = ({ fromDate, toDate }: { fromDate: Date | string | null; toDate: Date | string | null }) => {
|
|
13
|
+
const axiosPrivate = useAxiosPrivate()
|
|
14
|
+
let controller = new AbortController()
|
|
15
|
+
|
|
16
|
+
const { isLoading, isError, data, error } = useQuery({
|
|
17
|
+
queryKey: ['analyticsPage', 'topMediums', fromDate, toDate],
|
|
18
|
+
queryFn: () => {
|
|
19
|
+
// Abort the previous request
|
|
20
|
+
controller.abort()
|
|
21
|
+
|
|
22
|
+
// Create a new AbortController for the new request
|
|
23
|
+
controller = new AbortController()
|
|
24
|
+
return getAnalytics({
|
|
25
|
+
requestType: 'topMediums',
|
|
26
|
+
axiosPrivate,
|
|
27
|
+
controller,
|
|
28
|
+
fromDate,
|
|
29
|
+
toDate,
|
|
30
|
+
})
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
return () => {
|
|
36
|
+
controller.abort()
|
|
37
|
+
}
|
|
38
|
+
}, [])
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<ContainerBox title={getString('mediums')}>
|
|
42
|
+
<div className='w-full mb-6'>
|
|
43
|
+
<h1 className='font-bold border-b pb-1 my-1'>{getString('topMediums')}</h1>
|
|
44
|
+
{isLoading && <LoadingSpinners single={true} />}
|
|
45
|
+
{data?.topMediums?.length > 0 &&
|
|
46
|
+
data.topMediums.map((medium: BarChartDataItem, index: number) => (
|
|
47
|
+
<div key={`${index}-${medium.name}`} className='w-full text-sm flex justify-between'>
|
|
48
|
+
<span className='font-bold text-foreground'>{medium.name}:</span>
|
|
49
|
+
<Badge variant='outline' className='ms-2'>
|
|
50
|
+
{formatNumber(medium.value)}
|
|
51
|
+
</Badge>
|
|
52
|
+
</div>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
</ContainerBox>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import getString from 'nextjs-cms/translations'
|
|
2
|
+
import { BarChartDataItem } from 'nextjs-cms/core/types'
|
|
3
|
+
import { Badge } from '@/components/ui/badge'
|
|
4
|
+
import { formatNumber } from 'nextjs-cms/utils'
|
|
5
|
+
import ContainerBox from '@/components/ContainerBox'
|
|
6
|
+
import React from 'react'
|
|
7
|
+
import { useAxiosPrivate } from 'nextjs-cms/auth/hooks'
|
|
8
|
+
import { useQuery } from '@tanstack/react-query'
|
|
9
|
+
import { getAnalytics } from '@/lib/apiHelpers'
|
|
10
|
+
import LoadingSpinners from '@/components/LoadingSpinners'
|
|
11
|
+
|
|
12
|
+
export const TopSources = ({ fromDate, toDate }: { fromDate: Date | string | null; toDate: Date | string | null }) => {
|
|
13
|
+
const axiosPrivate = useAxiosPrivate()
|
|
14
|
+
const controller = new AbortController()
|
|
15
|
+
|
|
16
|
+
const { isLoading, isError, data, error } = useQuery({
|
|
17
|
+
queryKey: ['analyticsPage', 'topSources', fromDate, toDate],
|
|
18
|
+
queryFn: () =>
|
|
19
|
+
getAnalytics({
|
|
20
|
+
requestType: 'topSources',
|
|
21
|
+
axiosPrivate,
|
|
22
|
+
controller,
|
|
23
|
+
fromDate,
|
|
24
|
+
toDate,
|
|
25
|
+
}),
|
|
26
|
+
})
|
|
27
|
+
return (
|
|
28
|
+
<ContainerBox title={getString('sources')}>
|
|
29
|
+
<div className='w-full mb-6'>
|
|
30
|
+
<h1 className='font-bold border-b pb-1 my-1'>{getString('topSources')}</h1>
|
|
31
|
+
{isLoading && <LoadingSpinners single={true} />}
|
|
32
|
+
{data?.topSources?.length > 0 &&
|
|
33
|
+
data.topSources.map((source: BarChartDataItem, index: number) => (
|
|
34
|
+
<div key={`${index}-${source.name}`} className='w-full text-sm flex justify-between'>
|
|
35
|
+
<span className='font-bold text-foreground'>{source.name}:</span>
|
|
36
|
+
<Badge variant='outline' className='ms-2'>
|
|
37
|
+
{formatNumber(source.value)}
|
|
38
|
+
</Badge>
|
|
39
|
+
</div>
|
|
40
|
+
))}
|
|
41
|
+
</div>
|
|
42
|
+
</ContainerBox>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import getString from 'nextjs-cms/translations'
|
|
2
|
+
import { formatNumber } from 'nextjs-cms/utils'
|
|
3
|
+
import ContainerBox from '@/components/ContainerBox'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import { useAxiosPrivate } from 'nextjs-cms/auth/hooks'
|
|
6
|
+
import { useQuery } from '@tanstack/react-query'
|
|
7
|
+
import { getAnalytics } from '@/lib/apiHelpers'
|
|
8
|
+
import LoadingSpinners from '@/components/LoadingSpinners'
|
|
9
|
+
|
|
10
|
+
export const TotalPageViews = ({
|
|
11
|
+
fromDate,
|
|
12
|
+
toDate,
|
|
13
|
+
}: {
|
|
14
|
+
fromDate: Date | string | null
|
|
15
|
+
toDate: Date | string | null
|
|
16
|
+
}) => {
|
|
17
|
+
const axiosPrivate = useAxiosPrivate()
|
|
18
|
+
const controller = new AbortController()
|
|
19
|
+
|
|
20
|
+
const { isLoading, isError, data, error } = useQuery({
|
|
21
|
+
queryKey: ['analyticsPage', 'statistics', fromDate, toDate],
|
|
22
|
+
queryFn: () =>
|
|
23
|
+
getAnalytics({
|
|
24
|
+
requestType: 'statistics',
|
|
25
|
+
axiosPrivate,
|
|
26
|
+
controller,
|
|
27
|
+
fromDate,
|
|
28
|
+
toDate,
|
|
29
|
+
}),
|
|
30
|
+
})
|
|
31
|
+
return (
|
|
32
|
+
<ContainerBox title={getString('totalPageViews')}>
|
|
33
|
+
{isLoading ? (
|
|
34
|
+
<LoadingSpinners single={true} />
|
|
35
|
+
) : (
|
|
36
|
+
<div className='text-4xl'>{formatNumber(data?.totalPageViews)}</div>
|
|
37
|
+
)}
|
|
38
|
+
</ContainerBox>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import getString from 'nextjs-cms/translations'
|
|
2
|
+
import { formatNumber } from 'nextjs-cms/utils'
|
|
3
|
+
import ContainerBox from '@/components/ContainerBox'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import { useAxiosPrivate } from 'nextjs-cms/auth/hooks'
|
|
6
|
+
import { useQuery } from '@tanstack/react-query'
|
|
7
|
+
import { getAnalytics } from '@/lib/apiHelpers'
|
|
8
|
+
import LoadingSpinners from '@/components/LoadingSpinners'
|
|
9
|
+
|
|
10
|
+
export const TotalSessions = ({
|
|
11
|
+
fromDate,
|
|
12
|
+
toDate,
|
|
13
|
+
}: {
|
|
14
|
+
fromDate: Date | string | null
|
|
15
|
+
toDate: Date | string | null
|
|
16
|
+
}) => {
|
|
17
|
+
const axiosPrivate = useAxiosPrivate()
|
|
18
|
+
const controller = new AbortController()
|
|
19
|
+
|
|
20
|
+
const { isLoading, isError, data, error } = useQuery({
|
|
21
|
+
queryKey: ['analyticsPage', 'statistics', fromDate, toDate],
|
|
22
|
+
queryFn: () =>
|
|
23
|
+
getAnalytics({
|
|
24
|
+
requestType: 'statistics',
|
|
25
|
+
axiosPrivate,
|
|
26
|
+
controller,
|
|
27
|
+
fromDate,
|
|
28
|
+
toDate,
|
|
29
|
+
}),
|
|
30
|
+
})
|
|
31
|
+
return (
|
|
32
|
+
<ContainerBox title={getString('totalSessions')}>
|
|
33
|
+
{isLoading ? (
|
|
34
|
+
<LoadingSpinners single={true} />
|
|
35
|
+
) : (
|
|
36
|
+
<div className='text-4xl'>{formatNumber(data?.totalSessions)}</div>
|
|
37
|
+
)}
|
|
38
|
+
</ContainerBox>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import getString from 'nextjs-cms/translations'
|
|
2
|
+
import { formatNumber } from 'nextjs-cms/utils'
|
|
3
|
+
import ContainerBox from '@/components/ContainerBox'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import { useAxiosPrivate } from 'nextjs-cms/auth/hooks'
|
|
6
|
+
import { useQuery } from '@tanstack/react-query'
|
|
7
|
+
import { getAnalytics } from '@/lib/apiHelpers'
|
|
8
|
+
import LoadingSpinners from '@/components/LoadingSpinners'
|
|
9
|
+
|
|
10
|
+
export const TotalUniqueUsers = ({
|
|
11
|
+
fromDate,
|
|
12
|
+
toDate,
|
|
13
|
+
}: {
|
|
14
|
+
fromDate: Date | string | null
|
|
15
|
+
toDate: Date | string | null
|
|
16
|
+
}) => {
|
|
17
|
+
const axiosPrivate = useAxiosPrivate()
|
|
18
|
+
const controller = new AbortController()
|
|
19
|
+
|
|
20
|
+
const { isLoading, isError, data, error } = useQuery({
|
|
21
|
+
queryKey: ['analyticsPage', 'statistics', fromDate, toDate],
|
|
22
|
+
queryFn: () =>
|
|
23
|
+
getAnalytics({
|
|
24
|
+
requestType: 'statistics',
|
|
25
|
+
axiosPrivate,
|
|
26
|
+
controller,
|
|
27
|
+
fromDate,
|
|
28
|
+
toDate,
|
|
29
|
+
}),
|
|
30
|
+
})
|
|
31
|
+
return (
|
|
32
|
+
<ContainerBox title={getString('totalUniqueUsers')}>
|
|
33
|
+
{isLoading ? (
|
|
34
|
+
<LoadingSpinners single={true} />
|
|
35
|
+
) : (
|
|
36
|
+
<div className='text-4xl'>{formatNumber(data?.totalUniqueUsers)}</div>
|
|
37
|
+
)}
|
|
38
|
+
</ContainerBox>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import getString from 'nextjs-cms/translations'
|
|
2
|
+
import ContainerBox from '@/components/ContainerBox'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { Variant } from 'nextjs-cms/core/types'
|
|
5
|
+
import useModal from '@/hooks/useModal'
|
|
6
|
+
import { useAxiosPrivate } from 'nextjs-cms/auth/hooks'
|
|
7
|
+
import { handleVariantDeletion } from '@/lib/apiHelpers'
|
|
8
|
+
import { useToast } from '@/components/ui/use-toast'
|
|
9
|
+
import { Button } from '@/components/ui/button'
|
|
10
|
+
import VariantEditPage from '@/components/VariantEditPage'
|
|
11
|
+
|
|
12
|
+
const RightHomeRoomVariantCard = ({
|
|
13
|
+
variant,
|
|
14
|
+
variantItem,
|
|
15
|
+
sectionItemId,
|
|
16
|
+
action,
|
|
17
|
+
}: {
|
|
18
|
+
variant: Variant
|
|
19
|
+
variantItem: any
|
|
20
|
+
sectionItemId: string
|
|
21
|
+
action: any
|
|
22
|
+
}) => {
|
|
23
|
+
const { setModal, modal, modalResponse, setModalResponse } = useModal()
|
|
24
|
+
const axiosPrivate = useAxiosPrivate()
|
|
25
|
+
const controller = new AbortController()
|
|
26
|
+
const { toast } = useToast()
|
|
27
|
+
|
|
28
|
+
const handleDelete = async () => {
|
|
29
|
+
const response = await handleVariantDeletion(variant.variant_name, variantItem.id, axiosPrivate, controller)
|
|
30
|
+
if (response.error) {
|
|
31
|
+
toast({
|
|
32
|
+
variant: 'destructive',
|
|
33
|
+
title: getString('delete_admin'),
|
|
34
|
+
description: response.error.message,
|
|
35
|
+
})
|
|
36
|
+
} else if (response.code === 200) {
|
|
37
|
+
action ? action() : null
|
|
38
|
+
setModal(null)
|
|
39
|
+
setModalResponse(null)
|
|
40
|
+
toast({
|
|
41
|
+
variant: 'success',
|
|
42
|
+
title: getString('itemDeletedSuccessfully'),
|
|
43
|
+
description: response.message,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return (
|
|
48
|
+
<ContainerBox title={variantItem.display_name} key={variantItem.id}>
|
|
49
|
+
<div className='flex flex-col gap-4'>
|
|
50
|
+
<div className='flex flex-col gap-1'>
|
|
51
|
+
<div className='flex justify-between'>
|
|
52
|
+
<span className='font-bold'>Size From:</span>
|
|
53
|
+
<span>{variantItem.size_from}</span>
|
|
54
|
+
</div>
|
|
55
|
+
<div className='flex justify-between'>
|
|
56
|
+
<span className='font-bold'>Size To:</span>
|
|
57
|
+
<span>{variantItem.size_to}</span>
|
|
58
|
+
</div>
|
|
59
|
+
<div className='flex justify-between'>
|
|
60
|
+
<span className='font-bold'>Price:</span>
|
|
61
|
+
<span>{variantItem.price}</span>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div className='flex flex-row gap-2'>
|
|
65
|
+
<Button
|
|
66
|
+
size='sm'
|
|
67
|
+
type='button'
|
|
68
|
+
variant='default'
|
|
69
|
+
onClick={() => {
|
|
70
|
+
setModal({
|
|
71
|
+
size: 'lg',
|
|
72
|
+
title: getString('edit'),
|
|
73
|
+
body: (
|
|
74
|
+
<div className='p-4'>
|
|
75
|
+
<VariantEditPage
|
|
76
|
+
section={variant.section_name}
|
|
77
|
+
variant={variant.variant_name}
|
|
78
|
+
sectionItemId={sectionItemId}
|
|
79
|
+
itemId={variantItem.id}
|
|
80
|
+
action={action}
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
),
|
|
84
|
+
headerColor: 'bg-sky-700',
|
|
85
|
+
titleColor: 'text-white',
|
|
86
|
+
lang: 'en',
|
|
87
|
+
})
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
{getString('edit')}
|
|
91
|
+
</Button>
|
|
92
|
+
{/* Delete Button */}
|
|
93
|
+
<Button
|
|
94
|
+
size='sm'
|
|
95
|
+
type='button'
|
|
96
|
+
variant='destructive'
|
|
97
|
+
onClick={() => {
|
|
98
|
+
setModal({
|
|
99
|
+
title: getString('delete'),
|
|
100
|
+
body: (
|
|
101
|
+
<div className='p-4'>
|
|
102
|
+
<div className='flex flex-col gap-4'>
|
|
103
|
+
<div>{getString('deleteItemText')}</div>
|
|
104
|
+
<div className='flex gap-2'>
|
|
105
|
+
<button
|
|
106
|
+
className='rounded bg-green-600 px-2 py-1 text-white'
|
|
107
|
+
onClick={handleDelete}
|
|
108
|
+
>
|
|
109
|
+
Yes
|
|
110
|
+
</button>
|
|
111
|
+
<button
|
|
112
|
+
className='rounded bg-red-800 px-2 py-1 text-white'
|
|
113
|
+
onClick={() => {
|
|
114
|
+
setModal(null)
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
No
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
),
|
|
123
|
+
headerColor: 'bg-red-700',
|
|
124
|
+
titleColor: 'text-white',
|
|
125
|
+
lang: 'en',
|
|
126
|
+
})
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
{getString('delete')}
|
|
130
|
+
</Button>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</ContainerBox>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default RightHomeRoomVariantCard
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useDraggable } from '@dnd-kit/core'
|
|
3
|
+
|
|
4
|
+
function Draggable({ draggableId, children }: { draggableId: string | number; children: React.ReactNode }) {
|
|
5
|
+
const { attributes, listeners, setNodeRef, transform } = useDraggable({
|
|
6
|
+
id: draggableId,
|
|
7
|
+
})
|
|
8
|
+
const style = transform
|
|
9
|
+
? {
|
|
10
|
+
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
|
|
11
|
+
}
|
|
12
|
+
: undefined
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div ref={setNodeRef} className='float-start' style={style} {...listeners} {...attributes}>
|
|
16
|
+
{children}
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default Draggable
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useDroppable } from '@dnd-kit/core'
|
|
3
|
+
|
|
4
|
+
function Droppable({ droppableId, children }: { droppableId: string | number; children: React.ReactNode }) {
|
|
5
|
+
const { isOver, setNodeRef } = useDroppable({
|
|
6
|
+
id: droppableId,
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
const style = {
|
|
10
|
+
color: isOver ? 'green' : undefined,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div ref={setNodeRef} style={style}>
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default Droppable
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useSortable } from '@dnd-kit/sortable'
|
|
3
|
+
import { CSS } from '@dnd-kit/utilities'
|
|
4
|
+
|
|
5
|
+
export default function SortableItem({ id, children }: { id: string | number; children: React.ReactNode }) {
|
|
6
|
+
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: id })
|
|
7
|
+
|
|
8
|
+
const style = {
|
|
9
|
+
transform: CSS.Transform.toString(transform),
|
|
10
|
+
transition,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { addDays, format } from 'date-fns'
|
|
5
|
+
import { DateRange } from 'react-day-picker'
|
|
6
|
+
|
|
7
|
+
import { cn } from 'nextjs-cms/utils'
|
|
8
|
+
import { Button } from '@/components/ui/button'
|
|
9
|
+
import { Calendar } from '@/components/ui/calendar'
|
|
10
|
+
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
|
11
|
+
import { CalendarIcon } from '@radix-ui/react-icons'
|
|
12
|
+
|
|
13
|
+
export function DatePickerWithRange({ className }: React.HTMLAttributes<HTMLDivElement>) {
|
|
14
|
+
const [date, setDate] = React.useState<DateRange | undefined>({
|
|
15
|
+
from: new Date(2020, 0, 20),
|
|
16
|
+
to: addDays(new Date(2023, 0, 20), 20),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className={cn('grid gap-2', className)}>
|
|
21
|
+
<Popover>
|
|
22
|
+
<PopoverTrigger asChild>
|
|
23
|
+
<Button
|
|
24
|
+
id='date'
|
|
25
|
+
variant={'outline'}
|
|
26
|
+
className={cn('justify-start text-left font-normal', !date && 'text-muted-foreground')}
|
|
27
|
+
>
|
|
28
|
+
<CalendarIcon className='mr-2 h-4 w-4' />
|
|
29
|
+
{date?.from ? (
|
|
30
|
+
date.to ? (
|
|
31
|
+
<>
|
|
32
|
+
{format(date.from, 'LLL dd, y')} - {format(date.to, 'LLL dd, y')}
|
|
33
|
+
</>
|
|
34
|
+
) : (
|
|
35
|
+
format(date.from, 'LLL dd, y')
|
|
36
|
+
)
|
|
37
|
+
) : (
|
|
38
|
+
<span>Pick a date</span>
|
|
39
|
+
)}
|
|
40
|
+
</Button>
|
|
41
|
+
</PopoverTrigger>
|
|
42
|
+
<PopoverContent className='w-auto p-0' align='start'>
|
|
43
|
+
<Calendar
|
|
44
|
+
initialFocus
|
|
45
|
+
mode='range'
|
|
46
|
+
defaultMonth={date?.from}
|
|
47
|
+
selected={date}
|
|
48
|
+
onSelect={setDate}
|
|
49
|
+
numberOfMonths={2}
|
|
50
|
+
/>
|
|
51
|
+
</PopoverContent>
|
|
52
|
+
</Popover>
|
|
53
|
+
</div>
|
|
54
|
+
)
|
|
55
|
+
}
|