create-nextjs-cms 0.9.23 → 0.9.24
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 +3 -3
- package/templates/default/app/(auth)/auth/login/LoginPage.tsx +2 -2
- package/templates/default/app/(auth)/layout.tsx +1 -1
- package/templates/default/app/(rootLayout)/admins/page.tsx +1 -1
- package/templates/default/app/(rootLayout)/browse/[section]/[page]/page.tsx +1 -1
- package/templates/default/app/(rootLayout)/categorized/[section]/page.tsx +1 -1
- package/templates/default/app/(rootLayout)/edit/[section]/[itemId]/page.tsx +1 -1
- package/templates/default/app/(rootLayout)/layout.tsx +2 -2
- package/templates/default/app/(rootLayout)/loading.tsx +1 -1
- package/templates/default/app/(rootLayout)/log/page.tsx +1 -1
- package/templates/default/app/(rootLayout)/new/[section]/page.tsx +1 -1
- package/templates/default/app/(rootLayout)/section/[section]/page.tsx +1 -1
- package/templates/default/app/(rootLayout)/settings/page.tsx +1 -1
- package/templates/default/app/providers.tsx +1 -1
- package/templates/default/cms.config.ts +4 -2
- package/templates/default/components/{AdminCard.tsx → admin/admin-card.tsx} +3 -3
- package/templates/default/components/{AdminEditPage.tsx → admin/admin-edit-page.tsx} +3 -3
- package/templates/default/components/{NewAdminForm.tsx → admin/new-admin-form.tsx} +3 -3
- package/templates/default/components/{ContainerBox.tsx → container-box.tsx} +1 -1
- package/templates/default/components/{ErrorComponent.tsx → feedback/error-component.tsx} +1 -1
- package/templates/default/{context/ModalProvider.tsx → components/feedback/modal-context.tsx} +56 -53
- package/templates/default/components/{Modal.tsx → feedback/modal.tsx} +1 -1
- package/templates/default/components/form/{FormInputs.tsx → form-inputs.tsx} +17 -17
- package/templates/default/components/form/{Form.tsx → form.tsx} +8 -10
- package/templates/default/components/form/inputs/{CheckboxFormInput.tsx → checkbox-form-input.tsx} +1 -1
- package/templates/default/components/form/inputs/{ColorFormInput.tsx → color-form-input.tsx} +1 -1
- package/templates/default/components/form/inputs/{DateFormInput.tsx → date-form-input.tsx} +1 -1
- package/templates/default/components/form/inputs/{DateRangeFormInput.tsx → date-range-form-input.tsx} +1 -1
- package/templates/default/components/form/inputs/{DocumentFormInput.tsx → document-form-input.tsx} +3 -3
- package/templates/default/components/form/inputs/{MapFormInput.tsx → map-form-input.tsx} +5 -4
- package/templates/default/components/form/inputs/{MultipleSelectFormInput.tsx → multiple-select-form-input.tsx} +1 -1
- package/templates/default/components/form/inputs/{NumberFormInput.tsx → number-form-input.tsx} +1 -1
- package/templates/default/components/form/inputs/{PasswordFormInput.tsx → password-form-input.tsx} +1 -1
- package/templates/default/components/form/inputs/{PhotoFormInput.tsx → photo-form-input.tsx} +3 -3
- package/templates/default/components/form/inputs/{RichTextFormInput.tsx → rich-text-form-input.tsx} +2 -2
- package/templates/default/components/form/inputs/{SelectFormInput.tsx → select-form-input.tsx} +4 -4
- package/templates/default/components/form/inputs/{SlugFormInput.tsx → slug-form-input.tsx} +1 -1
- package/templates/default/components/form/inputs/{TagsFormInput.tsx → tags-form-input.tsx} +1 -1
- package/templates/default/components/form/inputs/{TextFormInput.tsx → text-form-input.tsx} +2 -2
- package/templates/default/components/form/inputs/{TextareaFormInput.tsx → textarea-form-input.tsx} +2 -2
- package/templates/default/components/form/inputs/{VideoFormInput.tsx → video-form-input.tsx} +3 -3
- package/templates/default/components/{Layout.tsx → layout/layout.tsx} +4 -4
- package/templates/default/components/{Navbar.tsx → layout/navbar.tsx} +2 -2
- package/templates/default/components/{SidebarDropdownItem.tsx → layout/sidebar-dropdown-item.tsx} +1 -1
- package/templates/default/components/{SidebarItem.tsx → layout/sidebar-item.tsx} +1 -1
- package/templates/default/components/{SidebarPluginGroup.tsx → layout/sidebar-plugin-group.tsx} +1 -1
- package/templates/default/components/{Sidebar.tsx → layout/sidebar.tsx} +4 -4
- package/templates/default/components/{LocaleSwitcher.tsx → locale/locale-switcher.tsx} +2 -2
- package/templates/default/components/{Dropzone.tsx → media/dropzone.tsx} +1 -1
- package/templates/default/components/{GalleryPhoto.tsx → media/gallery-photo.tsx} +2 -2
- package/templates/default/components/{PhotoGallery.tsx → media/photo-gallery.tsx} +2 -2
- package/templates/default/components/multi-select.tsx +8 -4
- package/templates/default/components/{AdminsPage.tsx → pages/admins-page.tsx} +4 -4
- package/templates/default/components/{BrowsePage.tsx → pages/browse-page.tsx} +7 -7
- package/templates/default/components/{CategorizedSectionPage.tsx → pages/categorized-section-page.tsx} +2 -2
- package/templates/default/components/{ItemEditPage.tsx → pages/item-edit-page.tsx} +7 -33
- package/templates/default/components/{LogPage.tsx → pages/log-page.tsx} +1 -1
- package/templates/default/components/{NewPage.tsx → pages/new-page.tsx} +27 -50
- package/templates/default/components/{SectionPage.tsx → pages/section-page.tsx} +6 -6
- package/templates/default/components/{SettingsPage.tsx → pages/settings-page.tsx} +4 -4
- package/templates/default/components/pagination/{Pagination.tsx → pagination.tsx} +1 -1
- package/templates/default/components/{CategoryDeleteConfirmPage.tsx → sections/category-delete-confirm-page.tsx} +4 -4
- package/templates/default/components/{CategorySectionSelectInput.tsx → sections/category-section-select-input.tsx} +4 -4
- package/templates/default/components/{ConditionalFields.tsx → sections/conditional-fields.tsx} +1 -1
- package/templates/default/components/{SectionItemCard.tsx → sections/section-item-card.tsx} +3 -3
- package/templates/default/components/{SelectInputButtons.tsx → sections/select-input-buttons.tsx} +4 -4
- package/templates/default/env/env.ts +42 -0
- package/templates/default/next-env.d.ts +1 -1
- package/templates/default/next.config.ts +1 -0
- package/templates/default/package.json +1 -0
- package/templates/default/components/AnalyticsPage.tsx +0 -144
- package/templates/default/components/BarChartBox.tsx +0 -42
- package/templates/default/components/NewVariantComponent.tsx +0 -229
- package/templates/default/components/PieChartBox.tsx +0 -101
- package/templates/default/components/VariantCard.tsx +0 -124
- package/templates/default/components/VariantEditPage.tsx +0 -230
- package/templates/default/components/analytics/BounceRate.tsx +0 -70
- package/templates/default/components/analytics/LivePageViews.tsx +0 -55
- package/templates/default/components/analytics/LiveUsersCount.tsx +0 -33
- package/templates/default/components/analytics/MonthlyPageViews.tsx +0 -42
- package/templates/default/components/analytics/TopCountries.tsx +0 -52
- package/templates/default/components/analytics/TopDevices.tsx +0 -46
- package/templates/default/components/analytics/TopMediums.tsx +0 -58
- package/templates/default/components/analytics/TopSources.tsx +0 -45
- package/templates/default/components/analytics/TotalPageViews.tsx +0 -41
- package/templates/default/components/analytics/TotalSessions.tsx +0 -41
- package/templates/default/components/analytics/TotalUniqueUsers.tsx +0 -41
- package/templates/default/components/custom/RightHomeRoomVariantCard.tsx +0 -138
- package/templates/default/env/env.js +0 -130
- package/templates/default/hooks/useModal.ts +0 -8
- package/templates/default/lib/apiHelpers.ts +0 -92
- /package/templates/default/components/{AdminPrivilegeCard.tsx → admin/admin-privilege-card.tsx} +0 -0
- /package/templates/default/components/{dndKit/Draggable.tsx → dnd-kit/draggable.tsx} +0 -0
- /package/templates/default/components/{dndKit/Droppable.tsx → dnd-kit/droppable.tsx} +0 -0
- /package/templates/default/components/{dndKit/SortableItem.tsx → dnd-kit/sortable-item.tsx} +0 -0
- /package/templates/default/components/{InfoCard.tsx → feedback/info-card.tsx} +0 -0
- /package/templates/default/components/{LoadingSpinners.tsx → feedback/loading-spinners.tsx} +0 -0
- /package/templates/default/components/{ProgressBar.tsx → feedback/progress-bar.tsx} +0 -0
- /package/templates/default/components/{TooltipComponent.tsx → feedback/tooltip-component.tsx} +0 -0
- /package/templates/default/components/form/{ContentLocaleContext.tsx → content-locale-context.tsx} +0 -0
- /package/templates/default/components/form/{FormInputElement.tsx → form-input-element.tsx} +0 -0
- /package/templates/default/components/{language-dropdown.tsx → i18n/language-dropdown.tsx} +0 -0
- /package/templates/default/components/{language-picker.tsx → i18n/language-picker.tsx} +0 -0
- /package/templates/default/components/{login-language-dropdown.tsx → i18n/login-language-dropdown.tsx} +0 -0
- /package/templates/default/components/{DefaultNavItems.tsx → layout/default-nav-items.tsx} +0 -0
- /package/templates/default/components/{ThemeProvider.tsx → layout/theme-provider.tsx} +0 -0
- /package/templates/default/components/{theme-toggle.tsx → layout/theme-toggle.tsx} +0 -0
- /package/templates/default/components/{ProtectedDocument.tsx → media/protected-document.tsx} +0 -0
- /package/templates/default/components/{ProtectedImage.tsx → media/protected-image.tsx} +0 -0
- /package/templates/default/components/{ProtectedVideo.tsx → media/protected-video.tsx} +0 -0
- /package/templates/default/components/{DashboardPageAlt.tsx → pages/dashboard-page-alt.tsx} +0 -0
- /package/templates/default/components/pagination/{PaginationButtons.tsx → pagination-buttons.tsx} +0 -0
- /package/templates/default/components/{SectionIcon.tsx → sections/section-icon.tsx} +0 -0
- /package/templates/default/components/{SectionItemStatusBadge.tsx → sections/section-item-status-badge.tsx} +0 -0
- /package/templates/default/components/{SelectBox.tsx → select-box.tsx} +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createEnv } from '@t3-oss/env-nextjs'
|
|
2
|
+
import * as z from 'zod'
|
|
3
|
+
|
|
4
|
+
export const env = createEnv({
|
|
5
|
+
/**
|
|
6
|
+
* Specify your server-side environment variables schema here. This way you can ensure the app
|
|
7
|
+
* isn't built with invalid env vars.
|
|
8
|
+
*/
|
|
9
|
+
server: {
|
|
10
|
+
ACCESS_TOKEN_SECRET: z.string(),
|
|
11
|
+
REFRESH_TOKEN_SECRET: z.string(),
|
|
12
|
+
CSRF_TOKEN_SECRET: z.string(),
|
|
13
|
+
ACCESS_TOKEN_EXPIRATION: z.string(),
|
|
14
|
+
REFRESH_TOKEN_EXPIRATION: z.string(),
|
|
15
|
+
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Specify your client-side environment variables schema here. This way you can ensure the app
|
|
20
|
+
* isn't built with invalid env vars. To expose them to the client, prefix them with
|
|
21
|
+
* `NEXT_PUBLIC_`.
|
|
22
|
+
*/
|
|
23
|
+
client: {
|
|
24
|
+
NEXT_PUBLIC_RICHTEXT_INLINE_PUBLIC_URL: z.string().optional(),
|
|
25
|
+
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: z.string().optional(),
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
experimental__runtimeEnv: {
|
|
29
|
+
NEXT_PUBLIC_RICHTEXT_INLINE_PUBLIC_URL: process.env.NEXT_PUBLIC_RICHTEXT_INLINE_PUBLIC_URL,
|
|
30
|
+
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY,
|
|
31
|
+
},
|
|
32
|
+
/**
|
|
33
|
+
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
|
|
34
|
+
* useful for Docker builds.
|
|
35
|
+
*/
|
|
36
|
+
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
|
37
|
+
/**
|
|
38
|
+
* Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
|
|
39
|
+
* `SOME_VAR=''` will throw an error.
|
|
40
|
+
*/
|
|
41
|
+
emptyStringAsUndefined: true,
|
|
42
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="next" />
|
|
2
2
|
/// <reference types="next/image-types/global" />
|
|
3
|
-
import "./.next/types/routes.d.ts";
|
|
3
|
+
import "./.next/dev/types/routes.d.ts";
|
|
4
4
|
|
|
5
5
|
// NOTE: This file should not be edited
|
|
6
6
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"@radix-ui/react-tabs": "^1.1.3",
|
|
43
43
|
"@radix-ui/react-toast": "^1.2.6",
|
|
44
44
|
"@radix-ui/react-tooltip": "^1.1.8",
|
|
45
|
+
"@t3-oss/env-nextjs": "^0.13.11",
|
|
45
46
|
"@tanstack/react-query": "^5.66.0",
|
|
46
47
|
"@tanstack/react-query-devtools": "^5.66.0",
|
|
47
48
|
"@tinymce/tinymce-react": "^6.3.0",
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
3
|
-
import React, { useEffect, useRef } from 'react'
|
|
4
|
-
import { useI18n } from 'nextjs-cms/translations/client'
|
|
5
|
-
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'
|
|
6
|
-
import DateRangeFormInput from '@/components/form/inputs/DateRangeFormInput'
|
|
7
|
-
import { MonthlyPageViews } from '@/components/analytics/MonthlyPageViews'
|
|
8
|
-
import { TopSources } from '@/components/analytics/TopSources'
|
|
9
|
-
import { TopMediums } from '@/components/analytics/TopMediums'
|
|
10
|
-
import { LiveUsersCount } from '@/components/analytics/LiveUsersCount'
|
|
11
|
-
import { TotalPageViews } from '@/components/analytics/TotalPageViews'
|
|
12
|
-
import { TotalSessions } from '@/components/analytics/TotalSessions'
|
|
13
|
-
import { TotalUniqueUsers } from '@/components/analytics/TotalUniqueUsers'
|
|
14
|
-
import { BounceRate } from '@/components/analytics/BounceRate'
|
|
15
|
-
import { LivePageViews } from '@/components/analytics/LivePageViews'
|
|
16
|
-
import { TopCountries } from '@/components/analytics/TopCountries'
|
|
17
|
-
import { TopDevices } from '@/components/analytics/TopDevices'
|
|
18
|
-
// import { trpc } from '@/app/_trpc/client'
|
|
19
|
-
ChartJS.register(ArcElement, Tooltip, Legend)
|
|
20
|
-
|
|
21
|
-
// TODO: MOVE THIS TO THE PLUGIN
|
|
22
|
-
|
|
23
|
-
const AnalyticsPage = () => {
|
|
24
|
-
const t = useI18n()
|
|
25
|
-
const controller = new AbortController()
|
|
26
|
-
const datePickerRangeRef = useRef<HTMLDivElement>(null)
|
|
27
|
-
const [fromDate, setFromDate] = React.useState<string>('30daysAgo')
|
|
28
|
-
const [toDate, setToDate] = React.useState<string>('today')
|
|
29
|
-
|
|
30
|
-
// const { data, isError } = trpc.googleAnalytics.test.useQuery()
|
|
31
|
-
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
return () => {
|
|
34
|
-
controller.abort()
|
|
35
|
-
}
|
|
36
|
-
}, [])
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<div className='w-full'>
|
|
40
|
-
<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'>
|
|
41
|
-
<h1 className='text-3xl'>{t('analytics')}</h1>
|
|
42
|
-
</div>
|
|
43
|
-
<div className='p-4'>
|
|
44
|
-
<div className='flex gap-2'>
|
|
45
|
-
<Tabs
|
|
46
|
-
defaultValue='account'
|
|
47
|
-
onValueChange={(value) => {
|
|
48
|
-
if (value === 'custom') {
|
|
49
|
-
// Remove hidden class from date picker range
|
|
50
|
-
datePickerRangeRef.current?.classList.remove('hidden')
|
|
51
|
-
} else {
|
|
52
|
-
// Add hidden class to date picker range
|
|
53
|
-
datePickerRangeRef.current?.classList.add('hidden')
|
|
54
|
-
}
|
|
55
|
-
}}
|
|
56
|
-
>
|
|
57
|
-
<Tabs
|
|
58
|
-
defaultValue='lastMonth'
|
|
59
|
-
onValueChange={(value) => {
|
|
60
|
-
if (value === 'custom') {
|
|
61
|
-
// Remove hidden class from date picker range
|
|
62
|
-
datePickerRangeRef.current?.classList.remove('hidden')
|
|
63
|
-
} else {
|
|
64
|
-
switch (value) {
|
|
65
|
-
case 'today':
|
|
66
|
-
setFromDate('yesterday')
|
|
67
|
-
setToDate('today')
|
|
68
|
-
break
|
|
69
|
-
case 'lastWeek':
|
|
70
|
-
setFromDate('10daysAgo')
|
|
71
|
-
setToDate('today')
|
|
72
|
-
break
|
|
73
|
-
case 'lastMonth':
|
|
74
|
-
setFromDate('30daysAgo')
|
|
75
|
-
setToDate('today')
|
|
76
|
-
break
|
|
77
|
-
case 'lastYear':
|
|
78
|
-
setFromDate('365daysAgo')
|
|
79
|
-
setToDate('today')
|
|
80
|
-
break
|
|
81
|
-
}
|
|
82
|
-
// Add hidden class to date picker range
|
|
83
|
-
datePickerRangeRef.current?.classList.add('hidden')
|
|
84
|
-
}
|
|
85
|
-
}}
|
|
86
|
-
>
|
|
87
|
-
<TabsList>
|
|
88
|
-
<TabsTrigger value='today'>{t('today')}</TabsTrigger>
|
|
89
|
-
<TabsTrigger value='lastWeek'>{t('last7Days')}</TabsTrigger>
|
|
90
|
-
<TabsTrigger value='lastMonth'>{t('last30Days')}</TabsTrigger>
|
|
91
|
-
<TabsTrigger value='lastYear'>{t('last365Days')}</TabsTrigger>
|
|
92
|
-
<TabsTrigger value='custom'>{t('custom')}</TabsTrigger>
|
|
93
|
-
</TabsList>
|
|
94
|
-
</Tabs>
|
|
95
|
-
</Tabs>
|
|
96
|
-
<div ref={datePickerRangeRef} className='hidden'>
|
|
97
|
-
<DateRangeFormInput input={{
|
|
98
|
-
name: 'dateRange',
|
|
99
|
-
label: 'Date Range',
|
|
100
|
-
required: true,
|
|
101
|
-
conditionalFields: [],
|
|
102
|
-
readonly: false,
|
|
103
|
-
defaultValue: undefined,
|
|
104
|
-
value: undefined,
|
|
105
|
-
startName: 'fromDate',
|
|
106
|
-
endName: 'toDate',
|
|
107
|
-
format: 'date',
|
|
108
|
-
startValue: fromDate,
|
|
109
|
-
endValue: toDate,
|
|
110
|
-
type: 'date_range',
|
|
111
|
-
}} />
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
114
|
-
<div className='my-2 grid grid-cols-2 gap-4 sm-sidebar:grid-cols-2 md-sidebar:grid-cols-2 lg-sidebar:grid-cols-4'>
|
|
115
|
-
<LiveUsersCount />
|
|
116
|
-
<TotalPageViews fromDate={fromDate} toDate={toDate} />
|
|
117
|
-
<TotalSessions fromDate={fromDate} toDate={toDate} />
|
|
118
|
-
<TotalUniqueUsers fromDate={fromDate} toDate={toDate} />
|
|
119
|
-
</div>
|
|
120
|
-
<div className='my-4 grid grid-cols-1 gap-4 sm-sidebar:grid-cols-1 md-sidebar:grid-cols-2 lg-sidebar:grid-cols-4'>
|
|
121
|
-
<div className='col-span-2'>
|
|
122
|
-
<MonthlyPageViews fromDate={fromDate} toDate={toDate} />
|
|
123
|
-
</div>
|
|
124
|
-
<div className='col-span-2 grid grid-cols-1 gap-4 sm-sidebar:grid-cols-1 md-sidebar:grid-cols-2'>
|
|
125
|
-
<TopCountries fromDate={fromDate} toDate={toDate} />
|
|
126
|
-
<BounceRate fromDate={fromDate} toDate={toDate} />
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
<div className='my-4 grid grid-cols-1 gap-4 sm-sidebar:grid-cols-1 md-sidebar:grid-cols-2 lg-sidebar:grid-cols-4'>
|
|
130
|
-
<div className='col-span-2'>
|
|
131
|
-
<TopSources fromDate={fromDate} toDate={toDate} />
|
|
132
|
-
</div>
|
|
133
|
-
<TopMediums fromDate={fromDate} toDate={toDate} />
|
|
134
|
-
<TopDevices fromDate={fromDate} toDate={toDate} />
|
|
135
|
-
</div>
|
|
136
|
-
<div className='my-2'>
|
|
137
|
-
<LivePageViews />
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
</div>
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export default AnalyticsPage
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import ContainerBox from '@/components/ContainerBox'
|
|
2
|
-
import { BarChart, Bar, XAxis, YAxis, ResponsiveContainer } from 'recharts'
|
|
3
|
-
import { BarChartDataItem } from 'nextjs-cms/core/types'
|
|
4
|
-
import LoadingSpinners from '@/components/LoadingSpinners'
|
|
5
|
-
|
|
6
|
-
export default function BarChartBox({
|
|
7
|
-
chartData,
|
|
8
|
-
title,
|
|
9
|
-
fill = '#adfa1d',
|
|
10
|
-
isLoading,
|
|
11
|
-
}: {
|
|
12
|
-
chartData: BarChartDataItem[]
|
|
13
|
-
title: string
|
|
14
|
-
fill?: string
|
|
15
|
-
isLoading?: boolean
|
|
16
|
-
}) {
|
|
17
|
-
return (
|
|
18
|
-
<ContainerBox title={title}>
|
|
19
|
-
<div>
|
|
20
|
-
<div style={{ width: '100%', height: 300 }}>
|
|
21
|
-
{isLoading ? (
|
|
22
|
-
<LoadingSpinners single={true} />
|
|
23
|
-
) : (
|
|
24
|
-
<ResponsiveContainer width='100%' height='100%'>
|
|
25
|
-
<BarChart data={chartData}>
|
|
26
|
-
<XAxis
|
|
27
|
-
dataKey='name'
|
|
28
|
-
stroke='#888888'
|
|
29
|
-
fontSize={12}
|
|
30
|
-
tickLine={false}
|
|
31
|
-
axisLine={false}
|
|
32
|
-
/>
|
|
33
|
-
<YAxis stroke='#888888' fontSize={12} tickLine={false} axisLine={true} />
|
|
34
|
-
<Bar dataKey='value' fill={fill} radius={[4, 4, 0, 0]} />
|
|
35
|
-
</BarChart>
|
|
36
|
-
</ResponsiveContainer>
|
|
37
|
-
)}
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
</ContainerBox>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet'
|
|
2
|
-
import React, { forwardRef, Ref, useEffect, useImperativeHandle, useState } from 'react'
|
|
3
|
-
import ContainerBox from '@/components/ContainerBox'
|
|
4
|
-
import { useI18n } from 'nextjs-cms/translations/client'
|
|
5
|
-
import { Button } from '@/components/ui/button'
|
|
6
|
-
import { useQuery } from '@tanstack/react-query'
|
|
7
|
-
import { getNewVariantPage } from '@/lib/apiHelpers'
|
|
8
|
-
import { useAxiosPrivate } from 'nextjs-cms/api/client'
|
|
9
|
-
import LoadingSpinners from '@/components/LoadingSpinners'
|
|
10
|
-
import { InputGroup, Variant, VariantItem } from 'nextjs-cms/core/types'
|
|
11
|
-
import FormInputs from '@/components/form/FormInputs'
|
|
12
|
-
import classNames from 'classnames'
|
|
13
|
-
import ProgressBar from '@/components/ProgressBar'
|
|
14
|
-
import { AxiosError } from 'axios'
|
|
15
|
-
import InfoCard from '@/components/InfoCard'
|
|
16
|
-
import { useToast } from '@/components/ui/use-toast'
|
|
17
|
-
import { nanoid } from 'nanoid'
|
|
18
|
-
import { MinusIcon } from '@radix-ui/react-icons'
|
|
19
|
-
|
|
20
|
-
export interface VariantHandles {
|
|
21
|
-
getVariants: () => VariantItem[]
|
|
22
|
-
removeVariants: () => void
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function NewVariantComponent(
|
|
26
|
-
{
|
|
27
|
-
section,
|
|
28
|
-
variantInfo,
|
|
29
|
-
xsrfToken,
|
|
30
|
-
}: {
|
|
31
|
-
section: string
|
|
32
|
-
variantInfo: Variant
|
|
33
|
-
xsrfToken: string
|
|
34
|
-
},
|
|
35
|
-
ref: Ref<VariantHandles>,
|
|
36
|
-
) {
|
|
37
|
-
const t = useI18n()
|
|
38
|
-
const [open, setOpen] = React.useState(false)
|
|
39
|
-
const axiosPrivate = useAxiosPrivate()
|
|
40
|
-
const controller = new AbortController()
|
|
41
|
-
const { isLoading, isError, data, error } = useQuery({
|
|
42
|
-
queryKey: ['newVariant', variantInfo.variant_name],
|
|
43
|
-
queryFn: () => getNewVariantPage(section, variantInfo.variant_name, axiosPrivate, controller),
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const [variants, setVariants] = useState<VariantItem[]>([])
|
|
47
|
-
const [progress, setProgress] = useState(0)
|
|
48
|
-
const [progressVariant, setProgressVariant] = useState<'determinate' | 'query'>('determinate')
|
|
49
|
-
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
50
|
-
const [response, setResponse] = useState<any>(null)
|
|
51
|
-
const { toast } = useToast()
|
|
52
|
-
|
|
53
|
-
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
54
|
-
setIsSubmitting(true)
|
|
55
|
-
e.preventDefault()
|
|
56
|
-
e.stopPropagation()
|
|
57
|
-
setResponse(null)
|
|
58
|
-
const formData = new FormData(e.currentTarget)
|
|
59
|
-
|
|
60
|
-
formData.append('section', section)
|
|
61
|
-
formData.append('variant', variantInfo.variant_name)
|
|
62
|
-
formData.append('preSubmit', 'true')
|
|
63
|
-
formData.append('form_type', 'new')
|
|
64
|
-
|
|
65
|
-
const body = Object.fromEntries(formData.entries())
|
|
66
|
-
try {
|
|
67
|
-
const res = await axiosPrivate.post(`/api-submit`, body, {
|
|
68
|
-
signal: controller.signal,
|
|
69
|
-
onUploadProgress: (progressEvent) => {
|
|
70
|
-
if (!progressEvent.total) return
|
|
71
|
-
const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100)
|
|
72
|
-
// Update progress bar value here
|
|
73
|
-
setProgress(progress)
|
|
74
|
-
|
|
75
|
-
if (progress === 100) {
|
|
76
|
-
setProgressVariant('query')
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
headers: {
|
|
80
|
-
'Content-Type': 'multipart/form-data',
|
|
81
|
-
},
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
if (res) {
|
|
85
|
-
setIsSubmitting(false)
|
|
86
|
-
setProgress(0)
|
|
87
|
-
setProgressVariant('determinate')
|
|
88
|
-
if (res.data.code === 200) {
|
|
89
|
-
setOpen(false)
|
|
90
|
-
// Handle closure
|
|
91
|
-
setResponse(null)
|
|
92
|
-
|
|
93
|
-
// Add the new variant to the list
|
|
94
|
-
// But first, let's remove the 'preSubmit' key
|
|
95
|
-
delete body.preSubmit
|
|
96
|
-
setVariants([
|
|
97
|
-
...variants,
|
|
98
|
-
{
|
|
99
|
-
id: nanoid(),
|
|
100
|
-
// @ts-ignore
|
|
101
|
-
title: formData.get(data?.variant.heading_input_name),
|
|
102
|
-
data: body,
|
|
103
|
-
} as VariantItem,
|
|
104
|
-
])
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
} catch (error: AxiosError | any) {
|
|
108
|
-
setIsSubmitting(false)
|
|
109
|
-
setProgress(0)
|
|
110
|
-
setProgressVariant('determinate')
|
|
111
|
-
if (error?.response?.data) {
|
|
112
|
-
setResponse(
|
|
113
|
-
<InfoCard result={{ key: 'danger', title: error.response.data.error.message, status: false }} />,
|
|
114
|
-
)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function cancelSubmit() {
|
|
120
|
-
controller.abort()
|
|
121
|
-
setIsSubmitting(false)
|
|
122
|
-
setProgress(0)
|
|
123
|
-
setProgressVariant('determinate')
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
// console.log('variants', variants)
|
|
128
|
-
return () => {
|
|
129
|
-
cancelSubmit()
|
|
130
|
-
}
|
|
131
|
-
}, [variants])
|
|
132
|
-
|
|
133
|
-
useImperativeHandle(ref, () => ({
|
|
134
|
-
getVariants: () => variants,
|
|
135
|
-
removeVariants: () => {
|
|
136
|
-
setVariants([])
|
|
137
|
-
},
|
|
138
|
-
}))
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<ContainerBox title={`${t('add')} ${variantInfo.variant_html_name_en}`}>
|
|
142
|
-
<div className='mb-4 grid grid-cols-1 gap-4 rounded-2xl border-4 border-dashed border-gray-400 bg-accent p-2 md:grid-cols-2 lg:grid-cols-4'>
|
|
143
|
-
{variants.map((v) => (
|
|
144
|
-
<ContainerBox title={v.title} key={v.id}>
|
|
145
|
-
{/* Delete Button */}
|
|
146
|
-
<button
|
|
147
|
-
type='button'
|
|
148
|
-
className='absolute end-1 top-1 rounded-lg bg-red-500 p-1'
|
|
149
|
-
onClick={() => {
|
|
150
|
-
setVariants((prev) =>
|
|
151
|
-
prev.filter((prevVariant) => {
|
|
152
|
-
if (prevVariant.id !== v.id) {
|
|
153
|
-
return prevVariant
|
|
154
|
-
}
|
|
155
|
-
}),
|
|
156
|
-
)
|
|
157
|
-
}}
|
|
158
|
-
>
|
|
159
|
-
<MinusIcon className='text-white' />
|
|
160
|
-
</button>
|
|
161
|
-
<div className='truncate'>{JSON.stringify(v.data)}</div>
|
|
162
|
-
</ContainerBox>
|
|
163
|
-
))}
|
|
164
|
-
</div>
|
|
165
|
-
<Sheet open={open} onOpenChange={setOpen}>
|
|
166
|
-
<SheetTrigger>
|
|
167
|
-
<Button type='button'>{t('add')} +</Button>
|
|
168
|
-
</SheetTrigger>
|
|
169
|
-
<SheetContent className='w-1/2 max-w-[2/3] sm:w-[940px] sm:max-w-full'>
|
|
170
|
-
<SheetHeader>
|
|
171
|
-
<SheetTitle>{t('newVariant')}</SheetTitle>
|
|
172
|
-
<SheetDescription>
|
|
173
|
-
<form onSubmit={handleSubmit}>
|
|
174
|
-
<div className=''>
|
|
175
|
-
{isLoading && (
|
|
176
|
-
<div>
|
|
177
|
-
<LoadingSpinners />
|
|
178
|
-
</div>
|
|
179
|
-
)}
|
|
180
|
-
|
|
181
|
-
{data && data.inputGroups && (
|
|
182
|
-
<div className='flex flex-col gap-4'>
|
|
183
|
-
{data?.inputGroups?.length > 0 &&
|
|
184
|
-
data?.inputGroups?.map((inputGroup: InputGroup, index: number) => {
|
|
185
|
-
return (
|
|
186
|
-
<ContainerBox title={inputGroup.groupTitle} key={index}>
|
|
187
|
-
<FormInputs
|
|
188
|
-
inputs={[]}
|
|
189
|
-
sectionName={variantInfo.variant_name}
|
|
190
|
-
/>
|
|
191
|
-
</ContainerBox>
|
|
192
|
-
)
|
|
193
|
-
})}
|
|
194
|
-
</div>
|
|
195
|
-
)}
|
|
196
|
-
<div className='my-4 flex flex-col gap-3 p-4'>
|
|
197
|
-
<div className='mt-5'>
|
|
198
|
-
<button
|
|
199
|
-
className={classNames({
|
|
200
|
-
'w-full rounded bg-linear-to-r p-2 font-bold text-white drop-shadow-sm':
|
|
201
|
-
true,
|
|
202
|
-
'from-emerald-700 via-green-700 to-green-500 dark:from-blue-800 dark:via-sky-800 dark:to-slate-500':
|
|
203
|
-
!isSubmitting,
|
|
204
|
-
'from-gray-600 via-gray-500 to-gray-400': isSubmitting,
|
|
205
|
-
})}
|
|
206
|
-
type='submit'
|
|
207
|
-
disabled={isSubmitting}
|
|
208
|
-
>
|
|
209
|
-
{isSubmitting ? t('loading') : t('create')}
|
|
210
|
-
</button>
|
|
211
|
-
{isSubmitting && (
|
|
212
|
-
<div className='mt-0.5'>
|
|
213
|
-
<ProgressBar variant={progressVariant} value={progress} />
|
|
214
|
-
</div>
|
|
215
|
-
)}
|
|
216
|
-
</div>
|
|
217
|
-
{response && <div className='w-full'>{response}</div>}
|
|
218
|
-
</div>
|
|
219
|
-
</div>
|
|
220
|
-
</form>
|
|
221
|
-
</SheetDescription>
|
|
222
|
-
</SheetHeader>
|
|
223
|
-
</SheetContent>
|
|
224
|
-
</Sheet>
|
|
225
|
-
</ContainerBox>
|
|
226
|
-
)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export default forwardRef(NewVariantComponent)
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { PieChartDataItem, PieChartTitles } from 'nextjs-cms/core/types'
|
|
3
|
-
import ContainerBox from '@/components/ContainerBox'
|
|
4
|
-
import { Badge } from '@/components/ui/badge'
|
|
5
|
-
import { ResponsiveContainer, PieChart, Pie, Legend } from 'recharts'
|
|
6
|
-
import LoadingSpinners from '@/components/LoadingSpinners'
|
|
7
|
-
|
|
8
|
-
const RADIAN = Math.PI / 180
|
|
9
|
-
const renderCustomizedLabel = ({
|
|
10
|
-
cx,
|
|
11
|
-
cy,
|
|
12
|
-
midAngle,
|
|
13
|
-
innerRadius,
|
|
14
|
-
outerRadius,
|
|
15
|
-
percent,
|
|
16
|
-
index,
|
|
17
|
-
name,
|
|
18
|
-
}: {
|
|
19
|
-
cx: number
|
|
20
|
-
cy: number
|
|
21
|
-
midAngle: number
|
|
22
|
-
innerRadius: number
|
|
23
|
-
outerRadius: number
|
|
24
|
-
percent: number
|
|
25
|
-
index: number
|
|
26
|
-
name: string
|
|
27
|
-
}) => {
|
|
28
|
-
const radius = innerRadius + (outerRadius - innerRadius) * 0.5
|
|
29
|
-
const x = cx + radius * Math.cos(-midAngle * RADIAN)
|
|
30
|
-
const y = cy + radius * Math.sin(-midAngle * RADIAN)
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<text
|
|
34
|
-
x={x}
|
|
35
|
-
y={y}
|
|
36
|
-
fill='white'
|
|
37
|
-
className='font-semibold'
|
|
38
|
-
textAnchor={x > cx ? 'start' : 'end'}
|
|
39
|
-
dominantBaseline='central'
|
|
40
|
-
>
|
|
41
|
-
{`${(percent * 100).toFixed(0)}%`}
|
|
42
|
-
</text>
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export default function PieChartBox({
|
|
47
|
-
chartData,
|
|
48
|
-
chartBoxTitles,
|
|
49
|
-
height = 300,
|
|
50
|
-
legend = true,
|
|
51
|
-
isLoading,
|
|
52
|
-
}: {
|
|
53
|
-
chartData: PieChartDataItem[]
|
|
54
|
-
chartBoxTitles: PieChartTitles
|
|
55
|
-
height?: number
|
|
56
|
-
legend?: boolean
|
|
57
|
-
isLoading?: boolean
|
|
58
|
-
}) {
|
|
59
|
-
return (
|
|
60
|
-
<ContainerBox title={chartBoxTitles.mainTitle}>
|
|
61
|
-
{isLoading ? (
|
|
62
|
-
<LoadingSpinners single={true} />
|
|
63
|
-
) : (
|
|
64
|
-
<>
|
|
65
|
-
<div className='text-muted-foreground'>
|
|
66
|
-
{chartBoxTitles.totalUnitSubtitle && (
|
|
67
|
-
<div>
|
|
68
|
-
{`${chartBoxTitles.totalUnitSubtitle.key}: `}{' '}
|
|
69
|
-
<Badge variant='secondary'>{chartBoxTitles.totalUnitSubtitle.value}</Badge>
|
|
70
|
-
</div>
|
|
71
|
-
)}
|
|
72
|
-
|
|
73
|
-
{chartBoxTitles.usedUnitSubtitle && (
|
|
74
|
-
<div>
|
|
75
|
-
{`${chartBoxTitles.usedUnitSubtitle.key}: `}
|
|
76
|
-
<Badge variant='secondary'>{chartBoxTitles.usedUnitSubtitle.value}</Badge>
|
|
77
|
-
</div>
|
|
78
|
-
)}
|
|
79
|
-
</div>
|
|
80
|
-
<div>
|
|
81
|
-
<div style={{ width: '100%', height: height }}>
|
|
82
|
-
<ResponsiveContainer>
|
|
83
|
-
<PieChart>
|
|
84
|
-
{legend && <Legend />}
|
|
85
|
-
<Pie
|
|
86
|
-
dataKey='value'
|
|
87
|
-
data={chartData}
|
|
88
|
-
fill='#8884d8'
|
|
89
|
-
label={renderCustomizedLabel}
|
|
90
|
-
labelLine={false}
|
|
91
|
-
startAngle={-180}
|
|
92
|
-
/>
|
|
93
|
-
</PieChart>
|
|
94
|
-
</ResponsiveContainer>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
</>
|
|
98
|
-
)}
|
|
99
|
-
</ContainerBox>
|
|
100
|
-
)
|
|
101
|
-
}
|