@nathapp/na-cli 1.0.8 → 1.0.9
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/dist/templates/db-schema/.gitlab-ci.yml +139 -0
- package/dist/templates/db-schema/Makefile +885 -0
- package/dist/templates/k8s-deployment/.gitlab-ci.yml +158 -0
- package/dist/templates/k8s-deployment/Makefile +233 -0
- package/dist/templates/k8s-deployment/resources/charts/mysql/charts/mysql-install/script/Makefile +12 -0
- package/dist/templates/k8s-deployment/resources/charts/redis/charts/redis-install/script/Makefile +8 -0
- package/dist/templates/k8s-deployment/services/charts/hooks/charts/pre-delete-rsc/script/Makefile +56 -0
- package/dist/templates/k8s-deployment/services/charts/tests/charts/e2e-test-cypress/Makefile +9 -0
- package/dist/templates/k8s-deployment/services/charts/tests/charts/e2e-test-js/script/Makefile +14 -0
- package/dist/templates/k8s-deployment/services/charts/tests/charts/e2e-test-js/tests/_t-0100-readiness-liveness.ts +84 -0
- package/dist/templates/k8s-deployment/services/charts/tests/charts/e2e-test-js/tests/hub.ts +372 -0
- package/dist/templates/k8s-deployment/services/charts/tests/charts/e2e-test-js/tests/individual-contact.ts +158 -0
- package/dist/templates/k8s-deployment/services/charts/tests/charts/e2e-test-js/tests/individual-contract.ts +328 -0
- package/dist/templates/k8s-deployment/services/charts/tests/charts/e2e-test-js/tests/individual-kyc.ts +304 -0
- package/dist/templates/k8s-deployment/services/charts/tests/charts/e2e-test-js/tests/individual-onboard-app.ts +823 -0
- package/dist/templates/k8s-deployment/services/charts/tests/charts/e2e-test-js/tests/individual-onboard-web.ts +493 -0
- package/dist/templates/k8s-deployment/services/charts/tests/charts/e2e-test-js/tests/individual-profile.ts +277 -0
- package/dist/templates/k8s-deployment/services/charts/tests/charts/e2e-test-js/tests/individual-whitelist.ts +133 -0
- package/dist/templates/k8s-deployment/services/charts/tests/charts/e2e-test-js/tests/t-0200-start.ts +172 -0
- package/dist/templates/nestjs-api/.dockerignore +9 -0
- package/dist/templates/nestjs-api/.env +31 -0
- package/dist/templates/nestjs-api/.gitlab-ci.yml +180 -0
- package/dist/templates/nestjs-api/.nvmrc +1 -0
- package/dist/templates/nestjs-api/.prettierrc +4 -0
- package/dist/templates/nestjs-api/Makefile +95 -0
- package/dist/templates/nestjs-api/nest-cli.json +10 -0
- package/dist/templates/nestjs-api/ormconfig.ts +18 -0
- package/dist/templates/nestjs-api/src/app.controller.spec.ts +24 -0
- package/dist/templates/nestjs-api/src/app.controller.ts +27 -0
- package/dist/templates/nestjs-api/src/app.module.ts +31 -0
- package/dist/templates/nestjs-api/src/app.service.ts +27 -0
- package/dist/templates/nestjs-api/src/main.ts +31 -0
- package/dist/templates/nestjs-api/test/app.e2e-spec.ts +47 -0
- package/dist/templates/nestjs-api/tsconfig.build.json +5 -0
- package/dist/templates/nestjs-api/tsconfig.json +28 -0
- package/dist/templates/nestjs-api/tsconfig.spec.json +9 -0
- package/dist/templates/nestjs-library/.gitlab-ci.yml +134 -0
- package/dist/templates/nestjs-library/.nvmrc +1 -0
- package/dist/templates/nestjs-library/.prettierrc +6 -0
- package/dist/templates/nestjs-library/Makefile +60 -0
- package/dist/templates/nestjs-library/lib/index.ts +2 -0
- package/dist/templates/nestjs-library/test/app/controller/hello.controller.spec.ts +20 -0
- package/dist/templates/nestjs-library/test/app/controller/hello.controller.ts +7 -0
- package/dist/templates/nestjs-library/tsconfig.build.json +8 -0
- package/dist/templates/nestjs-library/tsconfig.json +21 -0
- package/dist/templates/nestjs-library/tsconfig.spec.json +9 -0
- package/dist/templates/ts-library/.dockerignore +2 -0
- package/dist/templates/ts-library/.eslintrc.js +24 -0
- package/dist/templates/ts-library/.gitlab-ci.yml +136 -0
- package/dist/templates/ts-library/.prettierrc +6 -0
- package/dist/templates/ts-library/Makefile +58 -0
- package/dist/templates/ts-library/index.ts +1 -0
- package/dist/templates/ts-library/lib/index.ts +1 -0
- package/dist/templates/ts-library/lib/samples/index.ts +5 -0
- package/dist/templates/ts-library/lib/samples/samples-function.ts +3 -0
- package/dist/templates/ts-library/test/sample.e2e-spec.ts +10 -0
- package/dist/templates/ts-library/tsconfig.build.json +4 -0
- package/dist/templates/ts-library/tsconfig.json +18 -0
- package/dist/templates/turbo-nestjs-vuexy/.dockerignore +2 -0
- package/dist/templates/turbo-nestjs-vuexy/.gitignore-rename +40 -0
- package/dist/templates/turbo-nestjs-vuexy/.npmrc-rename +0 -0
- package/dist/templates/turbo-nestjs-vuexy/Makefile +48 -0
- package/dist/templates/vuexy-portal/.dockerignore +2 -0
- package/dist/templates/vuexy-portal/.env +1 -0
- package/dist/templates/vuexy-portal/.gitlab-ci.yml +233 -0
- package/dist/templates/vuexy-portal/.npmrc-rename +2 -0
- package/dist/templates/vuexy-portal/.nvmrc +1 -0
- package/dist/templates/vuexy-portal/Makefile +55 -0
- package/dist/templates/vuexy-portal/src/@core/composable/createUrl.ts +20 -0
- package/dist/templates/vuexy-portal/src/@core/composable/useCookie.ts +43 -0
- package/dist/templates/vuexy-portal/src/@core/composable/useGenerateImageVariant.ts +25 -0
- package/dist/templates/vuexy-portal/src/@core/composable/useResponsiveSidebar.ts +29 -0
- package/dist/templates/vuexy-portal/src/@core/composable/useSkins.ts +42 -0
- package/dist/templates/vuexy-portal/src/@core/enums.ts +21 -0
- package/dist/templates/vuexy-portal/src/@core/index.ts +43 -0
- package/dist/templates/vuexy-portal/src/@core/initCore.ts +93 -0
- package/dist/templates/vuexy-portal/src/@core/libs/apex-chart/apexCharConfig.ts +701 -0
- package/dist/templates/vuexy-portal/src/@core/libs/chartjs/chartjsConfig.ts +372 -0
- package/dist/templates/vuexy-portal/src/@core/libs/chartjs/components/BarChart.ts +58 -0
- package/dist/templates/vuexy-portal/src/@core/libs/chartjs/components/BubbleChart.ts +58 -0
- package/dist/templates/vuexy-portal/src/@core/libs/chartjs/components/DoughnutChart.ts +58 -0
- package/dist/templates/vuexy-portal/src/@core/libs/chartjs/components/LineChart.ts +58 -0
- package/dist/templates/vuexy-portal/src/@core/libs/chartjs/components/PolarAreaChart.ts +58 -0
- package/dist/templates/vuexy-portal/src/@core/libs/chartjs/components/RadarChart.ts +58 -0
- package/dist/templates/vuexy-portal/src/@core/libs/chartjs/components/ScatterChart.ts +58 -0
- package/dist/templates/vuexy-portal/src/@core/stores/config.ts +80 -0
- package/dist/templates/vuexy-portal/src/@core/types.ts +49 -0
- package/dist/templates/vuexy-portal/src/@core/utils/formatters.ts +48 -0
- package/dist/templates/vuexy-portal/src/@core/utils/helpers.ts +32 -0
- package/dist/templates/vuexy-portal/src/@core/utils/plugins.ts +53 -0
- package/dist/templates/vuexy-portal/src/@core/utils/validators.ts +105 -0
- package/dist/templates/vuexy-portal/src/@core/utils/vuetify.ts +13 -0
- package/dist/templates/vuexy-portal/src/@custom/composables/toasts.ts +45 -0
- package/dist/templates/vuexy-portal/src/@custom/composables/validator.ts +160 -0
- package/dist/templates/vuexy-portal/src/@custom/enums/index.ts +0 -0
- package/dist/templates/vuexy-portal/src/@custom/types/common.ts +17 -0
- package/dist/templates/vuexy-portal/src/@custom/types/index.ts +0 -0
- package/dist/templates/vuexy-portal/src/@custom/types/pagination.ts +19 -0
- package/dist/templates/vuexy-portal/src/@custom/types/user.ts +5 -0
- package/dist/templates/vuexy-portal/src/@custom/utils/formatters.ts +83 -0
- package/dist/templates/vuexy-portal/src/@custom/utils/index.ts +0 -0
- package/dist/templates/vuexy-portal/src/@custom/utils/validators.ts +51 -0
- package/dist/templates/vuexy-portal/src/@layouts/components.ts +11 -0
- package/dist/templates/vuexy-portal/src/@layouts/config.ts +43 -0
- package/dist/templates/vuexy-portal/src/@layouts/enums.ts +27 -0
- package/dist/templates/vuexy-portal/src/@layouts/index.ts +49 -0
- package/dist/templates/vuexy-portal/src/@layouts/plugins/casl.ts +48 -0
- package/dist/templates/vuexy-portal/src/@layouts/stores/config.ts +115 -0
- package/dist/templates/vuexy-portal/src/@layouts/symbols.ts +1 -0
- package/dist/templates/vuexy-portal/src/@layouts/types.ts +126 -0
- package/dist/templates/vuexy-portal/src/@layouts/utils.ts +180 -0
- package/dist/templates/vuexy-portal/src/api/api.ts +1 -0
- package/dist/templates/vuexy-portal/src/api/commonApi.ts +25 -0
- package/dist/templates/vuexy-portal/src/api/index.ts +58 -0
- package/dist/templates/vuexy-portal/src/api/requestInterceptor.ts +21 -0
- package/dist/templates/vuexy-portal/src/api/responseInterceptor.ts +52 -0
- package/dist/templates/vuexy-portal/src/api/types.ts +14 -0
- package/dist/templates/vuexy-portal/src/api/useApi.ts +68 -0
- package/dist/templates/vuexy-portal/src/api/useFetchApi.ts +41 -0
- package/dist/templates/vuexy-portal/src/api/userApi.ts +11 -0
- package/dist/templates/vuexy-portal/src/composables/useApiFetch.ts +48 -0
- package/dist/templates/vuexy-portal/src/composables/useJwt.ts +108 -0
- package/dist/templates/vuexy-portal/src/main.ts +17 -0
- package/dist/templates/vuexy-portal/src/navigation/horizontal/index.ts +5 -0
- package/dist/templates/vuexy-portal/src/navigation/index.ts +7 -0
- package/dist/templates/vuexy-portal/src/navigation/vertical/index.ts +5 -0
- package/dist/templates/vuexy-portal/src/plugins/1.router/additional-routes.ts +23 -0
- package/dist/templates/vuexy-portal/src/plugins/1.router/guards.ts +50 -0
- package/dist/templates/vuexy-portal/src/plugins/1.router/index.ts +45 -0
- package/dist/templates/vuexy-portal/src/plugins/2.pinia.ts +8 -0
- package/dist/templates/vuexy-portal/src/plugins/casl/ability.ts +10 -0
- package/dist/templates/vuexy-portal/src/plugins/casl/composables/useAbility.ts +4 -0
- package/dist/templates/vuexy-portal/src/plugins/casl/index.ts +14 -0
- package/dist/templates/vuexy-portal/src/plugins/i18n/index.ts +29 -0
- package/dist/templates/vuexy-portal/src/plugins/iconify/build-icons.ts +299 -0
- package/dist/templates/vuexy-portal/src/plugins/iconify/index.ts +5 -0
- package/dist/templates/vuexy-portal/src/plugins/layouts.ts +13 -0
- package/dist/templates/vuexy-portal/src/plugins/toast/index.ts +9 -0
- package/dist/templates/vuexy-portal/src/plugins/vuetify/defaults.ts +192 -0
- package/dist/templates/vuexy-portal/src/plugins/vuetify/icons.ts +84 -0
- package/dist/templates/vuexy-portal/src/plugins/vuetify/index.ts +47 -0
- package/dist/templates/vuexy-portal/src/plugins/vuetify/theme.ts +155 -0
- package/dist/templates/vuexy-portal/src/plugins/webfontloader.ts +19 -0
- package/dist/templates/vuexy-portal/src/stores/auth.ts +22 -0
- package/dist/templates/vuexy-portal/src/stores/common.ts +41 -0
- package/dist/templates/vuexy-portal/src/stores/index.ts +0 -0
- package/dist/templates/vuexy-portal/src/utils/constants.ts +3 -0
- package/dist/templates/vuexy-portal/src/utils/paginationMeta.ts +10 -0
- package/dist/templates/vuexy-portal/themeConfig.ts +66 -0
- package/dist/templates/vuexy-portal/tsconfig.json +86 -0
- package/dist/templates/vuexy-portal/vite.config.ts +111 -0
- package/dist/utils/template.js +2 -0
- package/dist/utils/template.js.map +1 -1
- package/package.json +4 -1
- package/alias-script.sh +0 -30
- package/bunfig.toml +0 -3
- package/nax/config.json +0 -23
- package/nax/features/enhancements/prd.json +0 -61
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { CustomerAddress } from "../types/customer"
|
|
2
|
+
|
|
3
|
+
export const formatCurrency = (value: number | string, currency: string = 'MYR', locale: string = 'en-US') => {
|
|
4
|
+
if (typeof value === 'string')
|
|
5
|
+
value = Number(value)
|
|
6
|
+
|
|
7
|
+
return new Intl.NumberFormat(locale, {
|
|
8
|
+
style: 'currency',
|
|
9
|
+
currency: currency === 'RM' ? 'MYR' : currency,
|
|
10
|
+
}).format(value)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const formatNumber = (value: number | string, locale: string = 'en-US') => {
|
|
14
|
+
if (typeof value === 'string')
|
|
15
|
+
value = Number(value)
|
|
16
|
+
|
|
17
|
+
return new Intl.NumberFormat(locale).format(value)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const formatNumberDecimal = (value: number | string, decimalPlaces: number = 2) => {
|
|
21
|
+
if (typeof value === 'string')
|
|
22
|
+
value = Number(value)
|
|
23
|
+
|
|
24
|
+
return value.toFixed(decimalPlaces)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const formatAddress = (
|
|
28
|
+
address1: string,
|
|
29
|
+
address2?: string,
|
|
30
|
+
address3?: string,
|
|
31
|
+
city?: string,
|
|
32
|
+
postalCode?: string,
|
|
33
|
+
state?: string,
|
|
34
|
+
country?: string,
|
|
35
|
+
linebreak = ',',
|
|
36
|
+
): string => {
|
|
37
|
+
let formattedAddress = ''
|
|
38
|
+
|
|
39
|
+
if (address1)
|
|
40
|
+
formattedAddress += `${address1}${linebreak}`
|
|
41
|
+
|
|
42
|
+
if (address2)
|
|
43
|
+
formattedAddress += `${address2}${linebreak}`
|
|
44
|
+
|
|
45
|
+
if (address3)
|
|
46
|
+
formattedAddress += `${address3}${linebreak}`
|
|
47
|
+
|
|
48
|
+
formattedAddress += `${postalCode} ${city}${linebreak}`
|
|
49
|
+
|
|
50
|
+
if (state)
|
|
51
|
+
formattedAddress += `${state}${linebreak}`
|
|
52
|
+
|
|
53
|
+
if (country)
|
|
54
|
+
formattedAddress += `${country}`
|
|
55
|
+
|
|
56
|
+
return formattedAddress
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const formatCustomerAddress = (
|
|
60
|
+
customerAddress: CustomerAddress,
|
|
61
|
+
linebreak = ',',
|
|
62
|
+
): string => {
|
|
63
|
+
const {
|
|
64
|
+
address1,
|
|
65
|
+
address2,
|
|
66
|
+
address3,
|
|
67
|
+
city,
|
|
68
|
+
postcode,
|
|
69
|
+
stateName,
|
|
70
|
+
countryName,
|
|
71
|
+
} = customerAddress
|
|
72
|
+
|
|
73
|
+
return formatAddress(
|
|
74
|
+
address1,
|
|
75
|
+
address2,
|
|
76
|
+
address3,
|
|
77
|
+
city,
|
|
78
|
+
postcode,
|
|
79
|
+
stateName,
|
|
80
|
+
countryName,
|
|
81
|
+
linebreak,
|
|
82
|
+
)
|
|
83
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* eslint-disable @stylistic/js/operator-linebreak */
|
|
2
|
+
import type { Country } from '../types/common'
|
|
3
|
+
|
|
4
|
+
export const conditionalRequiredValidator = (value: unknown, condition: boolean) => {
|
|
5
|
+
if (!condition)
|
|
6
|
+
return true
|
|
7
|
+
|
|
8
|
+
return requiredValidator(value)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 👉 mobile Validator
|
|
12
|
+
export const mobileValidator = (country: Country | string, value: unknown) => {
|
|
13
|
+
if (isEmpty(value))
|
|
14
|
+
return true
|
|
15
|
+
let countryIso2 = ''
|
|
16
|
+
let autoDialCode = '' // only auto add dialcode when country Object is provided
|
|
17
|
+
if (typeof country === 'string') {
|
|
18
|
+
countryIso2 = country
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
countryIso2 = country.countryCode
|
|
22
|
+
autoDialCode = country.dialCode
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const regex
|
|
26
|
+
= countryIso2.toLocaleLowerCase() === 'my'
|
|
27
|
+
? // malaysia mobile phone
|
|
28
|
+
/^(\+?6?01)[02-46-9]-*\d{7}$|^(\+?6?01)1-*\d{8}$/
|
|
29
|
+
: // other mobile no +919367788755, 8989829304, +16308520397
|
|
30
|
+
/^\+?\(?\d{3}\)?\d{3}[-\s.]?\d{3,8}$/
|
|
31
|
+
|
|
32
|
+
if (Array.isArray(value))
|
|
33
|
+
return value.every(val => regex.test(String(`${autoDialCode}${val}`))) || 'The mobile number field must be a valid mobile number'
|
|
34
|
+
|
|
35
|
+
return regex.test(String(`${autoDialCode}${value}`)) || 'The Mobile Number field must be a valid mobile number'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const positiveCurrencyValidator = (value: unknown, mustGreaterZero = false) => {
|
|
39
|
+
if (isEmpty(value))
|
|
40
|
+
return true
|
|
41
|
+
|
|
42
|
+
// /^\d+(\.\d{1,2})?$/
|
|
43
|
+
// eslint-disable-next-line regexp/no-unused-capturing-group
|
|
44
|
+
const regex = /^\d+(\.\d{1,2})?$/
|
|
45
|
+
|
|
46
|
+
const isValidCurrency = regex.test(String(value))
|
|
47
|
+
|
|
48
|
+
const valid = mustGreaterZero && isValidCurrency ? Number(value) > 0 : isValidCurrency
|
|
49
|
+
|
|
50
|
+
return valid || 'The currency field must be a valid currency and must be greater than 0'
|
|
51
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { default as HorizontalNav } from './components/HorizontalNav.vue'
|
|
2
|
+
export { default as HorizontalNavGroup } from './components/HorizontalNavGroup.vue'
|
|
3
|
+
export { default as HorizontalNavLayout } from './components/HorizontalNavLayout.vue'
|
|
4
|
+
export { default as HorizontalNavLink } from './components/HorizontalNavLink.vue'
|
|
5
|
+
export { default as HorizontalNavPopper } from './components/HorizontalNavPopper.vue'
|
|
6
|
+
export { default as TransitionExpand } from './components/TransitionExpand.vue'
|
|
7
|
+
export { default as VerticalNav } from './components/VerticalNav.vue'
|
|
8
|
+
export { default as VerticalNavGroup } from './components/VerticalNavGroup.vue'
|
|
9
|
+
export { default as VerticalNavLayout } from './components/VerticalNavLayout.vue'
|
|
10
|
+
export { default as VerticalNavLink } from './components/VerticalNavLink.vue'
|
|
11
|
+
export { default as VerticalNavSectionTitle } from './components/VerticalNavSectionTitle.vue'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { breakpointsVuetify } from '@vueuse/core'
|
|
2
|
+
import { AppContentLayoutNav, ContentWidth, FooterType, HorizontalNavType, NavbarType } from '@layouts/enums'
|
|
3
|
+
import type { LayoutConfig } from '@layouts/types'
|
|
4
|
+
|
|
5
|
+
export const layoutConfig: LayoutConfig = {
|
|
6
|
+
app: {
|
|
7
|
+
title: 'my-layout',
|
|
8
|
+
logo: h('img', { src: '/src/assets/logo.svg' }),
|
|
9
|
+
contentWidth: ContentWidth.Boxed,
|
|
10
|
+
contentLayoutNav: AppContentLayoutNav.Vertical,
|
|
11
|
+
overlayNavFromBreakpoint: breakpointsVuetify.md,
|
|
12
|
+
|
|
13
|
+
// isRTL: false,
|
|
14
|
+
i18n: {
|
|
15
|
+
enable: true,
|
|
16
|
+
},
|
|
17
|
+
iconRenderer: h('div'),
|
|
18
|
+
},
|
|
19
|
+
navbar: {
|
|
20
|
+
type: NavbarType.Sticky,
|
|
21
|
+
navbarBlur: true,
|
|
22
|
+
},
|
|
23
|
+
footer: {
|
|
24
|
+
type: FooterType.Static,
|
|
25
|
+
},
|
|
26
|
+
verticalNav: {
|
|
27
|
+
isVerticalNavCollapsed: false,
|
|
28
|
+
defaultNavItemIconProps: { icon: 'tabler-circle' },
|
|
29
|
+
},
|
|
30
|
+
horizontalNav: {
|
|
31
|
+
type: HorizontalNavType.Sticky,
|
|
32
|
+
transition: 'none',
|
|
33
|
+
popoverOffset: 0,
|
|
34
|
+
},
|
|
35
|
+
icons: {
|
|
36
|
+
chevronDown: { icon: 'tabler-chevron-down' },
|
|
37
|
+
chevronRight: { icon: 'tabler-chevron-right' },
|
|
38
|
+
close: { icon: 'tabler-x' },
|
|
39
|
+
verticalNavPinned: { icon: 'tabler-circle-dot' },
|
|
40
|
+
verticalNavUnPinned: { icon: 'tabler-circle' },
|
|
41
|
+
sectionTitlePlaceholder: { icon: 'tabler-minus' },
|
|
42
|
+
},
|
|
43
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const ContentWidth = {
|
|
2
|
+
Fluid: 'fluid',
|
|
3
|
+
Boxed: 'boxed',
|
|
4
|
+
} as const
|
|
5
|
+
|
|
6
|
+
export const NavbarType = {
|
|
7
|
+
Sticky: 'sticky',
|
|
8
|
+
Static: 'static',
|
|
9
|
+
Hidden: 'hidden',
|
|
10
|
+
} as const
|
|
11
|
+
|
|
12
|
+
export const FooterType = {
|
|
13
|
+
Sticky: 'sticky',
|
|
14
|
+
Static: 'static',
|
|
15
|
+
Hidden: 'hidden',
|
|
16
|
+
} as const
|
|
17
|
+
|
|
18
|
+
export const AppContentLayoutNav = {
|
|
19
|
+
Vertical: 'vertical',
|
|
20
|
+
Horizontal: 'horizontal',
|
|
21
|
+
} as const
|
|
22
|
+
|
|
23
|
+
export const HorizontalNavType = {
|
|
24
|
+
Sticky: 'sticky',
|
|
25
|
+
Static: 'static',
|
|
26
|
+
Hidden: 'hidden',
|
|
27
|
+
} as const
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { PartialDeep } from 'type-fest'
|
|
2
|
+
import type { Plugin } from 'vue'
|
|
3
|
+
import { layoutConfig } from '@layouts/config'
|
|
4
|
+
import { cookieRef, useLayoutConfigStore } from '@layouts/stores/config'
|
|
5
|
+
import type { LayoutConfig } from '@layouts/types'
|
|
6
|
+
import { _setDirAttr } from '@layouts/utils'
|
|
7
|
+
|
|
8
|
+
// 🔌 Plugin
|
|
9
|
+
export const createLayouts = (userConfig: PartialDeep<LayoutConfig>): Plugin => {
|
|
10
|
+
return (): void => {
|
|
11
|
+
const configStore = useLayoutConfigStore()
|
|
12
|
+
|
|
13
|
+
// Non reactive Values
|
|
14
|
+
layoutConfig.app.title = userConfig.app?.title ?? layoutConfig.app.title
|
|
15
|
+
layoutConfig.app.logo = userConfig.app?.logo ?? layoutConfig.app.logo as any
|
|
16
|
+
layoutConfig.app.overlayNavFromBreakpoint = userConfig.app?.overlayNavFromBreakpoint ?? layoutConfig.app.overlayNavFromBreakpoint
|
|
17
|
+
layoutConfig.app.i18n.enable = userConfig.app?.i18n?.enable ?? layoutConfig.app.i18n.enable
|
|
18
|
+
layoutConfig.app.iconRenderer = userConfig.app?.iconRenderer as LayoutConfig['app']['iconRenderer'] ?? layoutConfig.app.iconRenderer
|
|
19
|
+
|
|
20
|
+
layoutConfig.verticalNav.defaultNavItemIconProps = userConfig.verticalNav?.defaultNavItemIconProps ?? layoutConfig.verticalNav.defaultNavItemIconProps
|
|
21
|
+
|
|
22
|
+
layoutConfig.icons.chevronDown = userConfig.icons?.chevronDown ?? layoutConfig.icons.chevronDown
|
|
23
|
+
layoutConfig.icons.chevronRight = userConfig.icons?.chevronRight ?? layoutConfig.icons.chevronRight
|
|
24
|
+
layoutConfig.icons.close = userConfig.icons?.close ?? layoutConfig.icons.close
|
|
25
|
+
layoutConfig.icons.verticalNavPinned = userConfig.icons?.verticalNavPinned ?? layoutConfig.icons.verticalNavPinned
|
|
26
|
+
layoutConfig.icons.verticalNavUnPinned = userConfig.icons?.verticalNavUnPinned ?? layoutConfig.icons.verticalNavUnPinned
|
|
27
|
+
layoutConfig.icons.sectionTitlePlaceholder = userConfig.icons?.sectionTitlePlaceholder ?? layoutConfig.icons.sectionTitlePlaceholder
|
|
28
|
+
|
|
29
|
+
// Reactive Values (Store)
|
|
30
|
+
configStore.$patch({
|
|
31
|
+
appContentLayoutNav: cookieRef('appContentLayoutNav', userConfig.app?.contentLayoutNav ?? layoutConfig.app.contentLayoutNav).value,
|
|
32
|
+
appContentWidth: cookieRef('appContentWidth', userConfig.app?.contentWidth ?? layoutConfig.app.contentWidth).value,
|
|
33
|
+
footerType: cookieRef('footerType', userConfig.footer?.type ?? layoutConfig.footer.type).value,
|
|
34
|
+
navbarType: cookieRef('navbarType', userConfig.navbar?.type ?? layoutConfig.navbar.type).value,
|
|
35
|
+
isNavbarBlurEnabled: cookieRef('isNavbarBlurEnabled', userConfig.navbar?.navbarBlur ?? layoutConfig.navbar.navbarBlur).value,
|
|
36
|
+
isVerticalNavCollapsed: cookieRef('isVerticalNavCollapsed', userConfig.verticalNav?.isVerticalNavCollapsed ?? layoutConfig.verticalNav.isVerticalNavCollapsed).value,
|
|
37
|
+
|
|
38
|
+
// isAppRTL: userConfig.app?.isRTL ?? config.app.isRTL,
|
|
39
|
+
// isLessThanOverlayNavBreakpoint: false,
|
|
40
|
+
horizontalNavType: cookieRef('horizontalNavType', userConfig.horizontalNav?.type ?? layoutConfig.horizontalNav.type).value,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// _setDirAttr(config.app.isRTL ? 'rtl' : 'ltr')
|
|
44
|
+
_setDirAttr(configStore.isAppRTL ? 'rtl' : 'ltr')
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export * from './components'
|
|
49
|
+
export { layoutConfig }
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useAbility } from '@casl/vue'
|
|
2
|
+
import type { RouteLocationNormalized } from 'vue-router'
|
|
3
|
+
import type { NavGroup } from '@layouts/types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns ability result if ACL is configured or else just return true
|
|
7
|
+
* We should allow passing string | undefined to can because for admin ability we omit defining action & subject
|
|
8
|
+
*
|
|
9
|
+
* Useful if you don't know if ACL is configured or not
|
|
10
|
+
* Used in @core files to handle absence of ACL without errors
|
|
11
|
+
*
|
|
12
|
+
* @param {string} action CASL Actions // https://casl.js.org/v4/en/guide/intro#basics
|
|
13
|
+
* @param {string} subject CASL Subject // https://casl.js.org/v4/en/guide/intro#basics
|
|
14
|
+
*/
|
|
15
|
+
export const can = (action: string | undefined, subject: string | undefined) => {
|
|
16
|
+
const vm = getCurrentInstance()
|
|
17
|
+
|
|
18
|
+
if (!vm)
|
|
19
|
+
return false
|
|
20
|
+
|
|
21
|
+
const localCan = vm.proxy && '$can' in vm.proxy
|
|
22
|
+
|
|
23
|
+
// @ts-expect-error We will get TS error in below line because we aren't using $can in component instance
|
|
24
|
+
return localCan ? vm.proxy?.$can(action, subject) : true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if user can view item based on it's ability
|
|
29
|
+
* Based on item's action and subject & Hide group if all of it's children are hidden
|
|
30
|
+
* @param {object} item navigation object item
|
|
31
|
+
*/
|
|
32
|
+
export const canViewNavMenuGroup = (item: NavGroup) => {
|
|
33
|
+
const hasAnyVisibleChild = item.children.some(i => can(i.action, i.subject))
|
|
34
|
+
|
|
35
|
+
// If subject and action is defined in item => Return based on children visibility (Hide group if no child is visible)
|
|
36
|
+
// Else check for ability using provided subject and action along with checking if has any visible child
|
|
37
|
+
if (!(item.action && item.subject))
|
|
38
|
+
return hasAnyVisibleChild
|
|
39
|
+
|
|
40
|
+
return can(item.action, item.subject) && hasAnyVisibleChild
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const canNavigate = (to: RouteLocationNormalized) => {
|
|
44
|
+
const ability = useAbility()
|
|
45
|
+
|
|
46
|
+
// @ts-expect-error We should allow passing string | undefined to can because for admin ability we omit defining action & subject
|
|
47
|
+
return to.matched.some(route => ability.can(route.meta.action, route.meta.subject))
|
|
48
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { AppContentLayoutNav, NavbarType } from '@layouts/enums'
|
|
2
|
+
import { injectionKeyIsVerticalNavHovered } from '@layouts/symbols'
|
|
3
|
+
import { _setDirAttr } from '@layouts/utils'
|
|
4
|
+
|
|
5
|
+
// ℹ️ We should not import themeConfig here but in urgency we are doing it for now
|
|
6
|
+
import { layoutConfig } from '@themeConfig'
|
|
7
|
+
|
|
8
|
+
export const namespaceConfig = (str: string) => `${layoutConfig.app.title}-${str}`
|
|
9
|
+
|
|
10
|
+
export const cookieRef = <T>(key: string, defaultValue: T) => {
|
|
11
|
+
return useCookie<T>(namespaceConfig(key), { default: () => defaultValue })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const useLayoutConfigStore = defineStore('layoutConfig', () => {
|
|
15
|
+
const route = useRoute()
|
|
16
|
+
|
|
17
|
+
// 👉 Navbar Type
|
|
18
|
+
const navbarType = ref(layoutConfig.navbar.type)
|
|
19
|
+
|
|
20
|
+
// 👉 Navbar Type
|
|
21
|
+
const isNavbarBlurEnabled = cookieRef('isNavbarBlurEnabled', layoutConfig.navbar.navbarBlur)
|
|
22
|
+
|
|
23
|
+
// 👉 Vertical Nav Collapsed
|
|
24
|
+
const isVerticalNavCollapsed = cookieRef('isVerticalNavCollapsed', layoutConfig.verticalNav.isVerticalNavCollapsed)
|
|
25
|
+
|
|
26
|
+
// 👉 App Content Width
|
|
27
|
+
const appContentWidth = cookieRef('appContentWidth', layoutConfig.app.contentWidth)
|
|
28
|
+
|
|
29
|
+
// 👉 App Content Layout Nav
|
|
30
|
+
const appContentLayoutNav = ref(layoutConfig.app.contentLayoutNav)
|
|
31
|
+
|
|
32
|
+
watch(appContentLayoutNav, val => {
|
|
33
|
+
// If Navbar type is hidden while switching to horizontal nav => Reset it to sticky
|
|
34
|
+
if (val === AppContentLayoutNav.Horizontal) {
|
|
35
|
+
if (navbarType.value === NavbarType.Hidden)
|
|
36
|
+
navbarType.value = NavbarType.Sticky
|
|
37
|
+
|
|
38
|
+
isVerticalNavCollapsed.value = false
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// 👉 Horizontal Nav Type
|
|
43
|
+
const horizontalNavType = ref(layoutConfig.horizontalNav.type)
|
|
44
|
+
|
|
45
|
+
// 👉 Horizontal Nav Popover Offset
|
|
46
|
+
const horizontalNavPopoverOffset = ref(layoutConfig.horizontalNav.popoverOffset)
|
|
47
|
+
|
|
48
|
+
// 👉 Footer Type
|
|
49
|
+
const footerType = ref(layoutConfig.footer.type)
|
|
50
|
+
|
|
51
|
+
// 👉 Misc
|
|
52
|
+
const isLessThanOverlayNavBreakpoint = computed(() => useMediaQuery(`(max-width: ${layoutConfig.app.overlayNavFromBreakpoint}px)`).value)
|
|
53
|
+
|
|
54
|
+
// 👉 Layout Classes
|
|
55
|
+
const _layoutClasses = computed(() => {
|
|
56
|
+
const { y: windowScrollY } = useWindowScroll()
|
|
57
|
+
|
|
58
|
+
return [
|
|
59
|
+
`layout-nav-type-${appContentLayoutNav.value}`,
|
|
60
|
+
`layout-navbar-${navbarType.value}`,
|
|
61
|
+
`layout-footer-${footerType.value}`,
|
|
62
|
+
{
|
|
63
|
+
'layout-vertical-nav-collapsed':
|
|
64
|
+
isVerticalNavCollapsed.value
|
|
65
|
+
&& appContentLayoutNav.value === 'vertical'
|
|
66
|
+
&& !isLessThanOverlayNavBreakpoint.value,
|
|
67
|
+
},
|
|
68
|
+
{ [`horizontal-nav-${horizontalNavType.value}`]: appContentLayoutNav.value === 'horizontal' },
|
|
69
|
+
`layout-content-width-${appContentWidth.value}`,
|
|
70
|
+
{ 'layout-overlay-nav': isLessThanOverlayNavBreakpoint.value },
|
|
71
|
+
{ 'window-scrolled': unref(windowScrollY) },
|
|
72
|
+
route.meta.layoutWrapperClasses ? route.meta.layoutWrapperClasses : null,
|
|
73
|
+
]
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// 👉 RTL
|
|
77
|
+
// const isAppRTL = ref(layoutConfig.app.isRTL)
|
|
78
|
+
const isAppRTL = ref(false)
|
|
79
|
+
|
|
80
|
+
watch(isAppRTL, val => {
|
|
81
|
+
_setDirAttr(val ? 'rtl' : 'ltr')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// 👉 Is Vertical Nav Mini
|
|
85
|
+
/*
|
|
86
|
+
This function will return true if current state is mini. Mini state means vertical nav is:
|
|
87
|
+
- Collapsed
|
|
88
|
+
- Isn't hovered by mouse
|
|
89
|
+
- nav is not less than overlay breakpoint (hence, isn't overlay menu)
|
|
90
|
+
|
|
91
|
+
ℹ️ We are getting `isVerticalNavHovered` as param instead of via `inject` because
|
|
92
|
+
we are using this in `VerticalNav.vue` component which provide it and I guess because
|
|
93
|
+
same component is providing & injecting we are getting undefined error
|
|
94
|
+
*/
|
|
95
|
+
const isVerticalNavMini = (isVerticalNavHovered: Ref<boolean> | null = null) => {
|
|
96
|
+
const isVerticalNavHoveredLocal = isVerticalNavHovered || inject(injectionKeyIsVerticalNavHovered) || ref(false)
|
|
97
|
+
|
|
98
|
+
return computed(() => isVerticalNavCollapsed.value && !isVerticalNavHoveredLocal.value && !isLessThanOverlayNavBreakpoint.value)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
appContentWidth,
|
|
103
|
+
appContentLayoutNav,
|
|
104
|
+
navbarType,
|
|
105
|
+
isNavbarBlurEnabled,
|
|
106
|
+
isVerticalNavCollapsed,
|
|
107
|
+
horizontalNavType,
|
|
108
|
+
horizontalNavPopoverOffset,
|
|
109
|
+
footerType,
|
|
110
|
+
isLessThanOverlayNavBreakpoint,
|
|
111
|
+
isAppRTL,
|
|
112
|
+
_layoutClasses,
|
|
113
|
+
isVerticalNavMini,
|
|
114
|
+
}
|
|
115
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const injectionKeyIsVerticalNavHovered: InjectionKey<Ref<boolean>> = Symbol('isVerticalNavHovered')
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { OffsetOptions } from '@floating-ui/dom'
|
|
2
|
+
import type { RouteLocationRaw } from 'vue-router'
|
|
3
|
+
import type { AppContentLayoutNav, ContentWidth, FooterType, HorizontalNavType, NavbarType } from '@layouts/enums'
|
|
4
|
+
|
|
5
|
+
export interface LayoutConfig {
|
|
6
|
+
app: {
|
|
7
|
+
title: Lowercase<string>
|
|
8
|
+
logo: VNode
|
|
9
|
+
contentWidth: typeof ContentWidth[keyof typeof ContentWidth]
|
|
10
|
+
contentLayoutNav: typeof AppContentLayoutNav[keyof typeof AppContentLayoutNav]
|
|
11
|
+
overlayNavFromBreakpoint: number
|
|
12
|
+
|
|
13
|
+
// isRTL: boolean
|
|
14
|
+
i18n: {
|
|
15
|
+
enable: boolean
|
|
16
|
+
}
|
|
17
|
+
iconRenderer: Component
|
|
18
|
+
}
|
|
19
|
+
navbar: {
|
|
20
|
+
type: typeof NavbarType[keyof typeof NavbarType]
|
|
21
|
+
navbarBlur: boolean
|
|
22
|
+
}
|
|
23
|
+
footer: {
|
|
24
|
+
type: typeof FooterType[keyof typeof FooterType]
|
|
25
|
+
}
|
|
26
|
+
verticalNav: {
|
|
27
|
+
isVerticalNavCollapsed: boolean
|
|
28
|
+
defaultNavItemIconProps: unknown
|
|
29
|
+
}
|
|
30
|
+
horizontalNav: {
|
|
31
|
+
type: typeof HorizontalNavType[keyof typeof HorizontalNavType]
|
|
32
|
+
transition: string | Component
|
|
33
|
+
popoverOffset?: OffsetOptions
|
|
34
|
+
}
|
|
35
|
+
icons: {
|
|
36
|
+
chevronDown: any
|
|
37
|
+
chevronRight: any
|
|
38
|
+
close: any
|
|
39
|
+
verticalNavPinned: any
|
|
40
|
+
verticalNavUnPinned: any
|
|
41
|
+
sectionTitlePlaceholder: any
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface AclProperties {
|
|
46
|
+
action: string
|
|
47
|
+
subject: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 👉 Vertical nav section title
|
|
51
|
+
export interface NavSectionTitle extends Partial<AclProperties> {
|
|
52
|
+
heading: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 👉 Vertical nav link
|
|
56
|
+
declare type ATagTargetAttrValues = '_blank' | '_self' | '_parent' | '_top' | 'framename'
|
|
57
|
+
declare type ATagRelAttrValues =
|
|
58
|
+
| 'alternate'
|
|
59
|
+
| 'author'
|
|
60
|
+
| 'bookmark'
|
|
61
|
+
| 'external'
|
|
62
|
+
| 'help'
|
|
63
|
+
| 'license'
|
|
64
|
+
| 'next'
|
|
65
|
+
| 'nofollow'
|
|
66
|
+
| 'noopener'
|
|
67
|
+
| 'noreferrer'
|
|
68
|
+
| 'prev'
|
|
69
|
+
| 'search'
|
|
70
|
+
| 'tag'
|
|
71
|
+
|
|
72
|
+
export interface NavLinkProps {
|
|
73
|
+
to?: RouteLocationRaw | string | null
|
|
74
|
+
href?: string
|
|
75
|
+
target?: ATagTargetAttrValues
|
|
76
|
+
rel?: ATagRelAttrValues
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface NavLink extends NavLinkProps, Partial<AclProperties> {
|
|
80
|
+
title: string
|
|
81
|
+
icon?: unknown
|
|
82
|
+
badgeContent?: string
|
|
83
|
+
badgeClass?: string
|
|
84
|
+
disable?: boolean
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 👉 Vertical nav group
|
|
88
|
+
export interface NavGroup extends Partial<AclProperties> {
|
|
89
|
+
title: string
|
|
90
|
+
icon?: unknown
|
|
91
|
+
badgeContent?: string
|
|
92
|
+
badgeClass?: string
|
|
93
|
+
children: (NavLink | NavGroup)[]
|
|
94
|
+
disable?: boolean
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export declare type VerticalNavItems = (NavLink | NavGroup | NavSectionTitle)[]
|
|
98
|
+
export declare type HorizontalNavItems = (NavLink | NavGroup)[]
|
|
99
|
+
|
|
100
|
+
// 👉 Components ========================
|
|
101
|
+
|
|
102
|
+
export interface I18nLanguage {
|
|
103
|
+
label: string
|
|
104
|
+
i18nLang: string
|
|
105
|
+
isRTL: boolean
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// avatar | text | icon
|
|
109
|
+
// Thanks: https://stackoverflow.com/a/60617060/10796681
|
|
110
|
+
export type Notification = {
|
|
111
|
+
id: number
|
|
112
|
+
title: string
|
|
113
|
+
subtitle: string
|
|
114
|
+
time: string
|
|
115
|
+
color?: string
|
|
116
|
+
isSeen: boolean
|
|
117
|
+
} & (
|
|
118
|
+
| { img: string; text?: never; icon?: never }
|
|
119
|
+
| { img?: never; text: string; icon?: never }
|
|
120
|
+
| { img?: never; text?: never; icon: string }
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
export interface ThemeSwitcherTheme {
|
|
124
|
+
name: string
|
|
125
|
+
icon: string
|
|
126
|
+
}
|