@dargmuesli/nuxt-vio 20.0.0-beta.1 → 20.0.0-beta.2
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/.config/lint.js +0 -7
- package/app/assets/css/shadcn.css +119 -0
- package/app/assets/css/vio.css +5 -3
- package/app/components/scn/sonner/Sonner.vue +49 -0
- package/app/components/scn/sonner/index.ts +1 -0
- package/app/components/vio/_/VioApp.vue +1 -0
- package/app/components/vio/_/VioSonner.vue +16 -0
- package/app/components/vio/_/VioTime.vue +0 -1
- package/app/components/vio/button/VioButtonShare.vue +1 -1
- package/app/composables/alert.ts +19 -10
- package/app/plugins/ssr-width.ts +5 -0
- package/app/types/color-mode.d.ts +1 -0
- package/app/utils/notification.ts +1 -0
- package/app/utils/shadcn.ts +7 -0
- package/i18n/locales/de.json +1 -0
- package/i18n/locales/en.json +1 -0
- package/nuxt.config.ts +13 -7
- package/package.json +19 -12
- package/server/middleware/timezone.ts +1 -1
- package/server/plugins/security.ts +2 -4
- package/server/utils/timezone.ts +1 -2
- package/shared/utils/constants.ts +1 -9
- package/shared/utils/dateTime.ts +118 -0
- package/shared/utils/networking.ts +1 -3
- package/shared/utils/modal.ts +0 -16
package/.config/lint.js
CHANGED
|
@@ -36,13 +36,6 @@ export const VIO_ESLINT_CONFIG = [
|
|
|
36
36
|
},
|
|
37
37
|
},
|
|
38
38
|
rules: {
|
|
39
|
-
'@intlify/vue-i18n/no-missing-keys': 'error',
|
|
40
|
-
'@intlify/vue-i18n/no-raw-text': 'error',
|
|
41
|
-
'@intlify/vue-i18n/no-deprecated-i18n-component': 'error', // TODO: do not specify below rules manually, but have them included in `recommended` https://github.com/intlify/eslint-plugin-vue-i18n/issues/275
|
|
42
|
-
'@intlify/vue-i18n/no-deprecated-i18n-place-attr': 'error',
|
|
43
|
-
'@intlify/vue-i18n/no-deprecated-i18n-places-prop': 'error',
|
|
44
|
-
'@intlify/vue-i18n/no-i18n-t-path-prop': 'error',
|
|
45
|
-
'@intlify/vue-i18n/valid-message-syntax': 'error',
|
|
46
39
|
'@intlify/vue-i18n/key-format-style': 'error',
|
|
47
40
|
'@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error',
|
|
48
41
|
'@intlify/vue-i18n/no-dynamic-keys': 'error',
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* stylelint-disable at-rule-no-unknown */
|
|
2
|
+
/* stylelint-disable plugin/no-unsupported-browser-features */
|
|
3
|
+
|
|
4
|
+
.dark {
|
|
5
|
+
--background: oklch(14.5% 0 0deg);
|
|
6
|
+
--foreground: oklch(98.5% 0 0deg);
|
|
7
|
+
--card: oklch(20.5% 0 0deg);
|
|
8
|
+
--card-foreground: oklch(98.5% 0 0deg);
|
|
9
|
+
--popover: oklch(20.5% 0 0deg);
|
|
10
|
+
--popover-foreground: oklch(98.5% 0 0deg);
|
|
11
|
+
--primary: oklch(92.2% 0 0deg);
|
|
12
|
+
--primary-foreground: oklch(20.5% 0 0deg);
|
|
13
|
+
--secondary: oklch(26.9% 0 0deg);
|
|
14
|
+
--secondary-foreground: oklch(98.5% 0 0deg);
|
|
15
|
+
--muted: oklch(26.9% 0 0deg);
|
|
16
|
+
--muted-foreground: oklch(70.8% 0 0deg);
|
|
17
|
+
--accent: oklch(26.9% 0 0deg);
|
|
18
|
+
--accent-foreground: oklch(98.5% 0 0deg);
|
|
19
|
+
--destructive: oklch(70.4% 0.191 22.216deg);
|
|
20
|
+
--border: oklch(100% 0 0deg / 10%);
|
|
21
|
+
--input: oklch(100% 0 0deg / 15%);
|
|
22
|
+
--ring: oklch(55.6% 0 0deg);
|
|
23
|
+
--chart-1: oklch(48.8% 0.243 264.376deg);
|
|
24
|
+
--chart-2: oklch(69.6% 0.17 162.48deg);
|
|
25
|
+
--chart-3: oklch(76.9% 0.188 70.08deg);
|
|
26
|
+
--chart-4: oklch(62.7% 0.265 303.9deg);
|
|
27
|
+
--chart-5: oklch(64.5% 0.246 16.439deg);
|
|
28
|
+
--sidebar: oklch(20.5% 0 0deg);
|
|
29
|
+
--sidebar-foreground: oklch(98.5% 0 0deg);
|
|
30
|
+
--sidebar-primary: oklch(48.8% 0.243 264.37deg);
|
|
31
|
+
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
|
|
32
|
+
--sidebar-accent: oklch(26.9% 0 0deg);
|
|
33
|
+
--sidebar-accent-foreground: oklch(98.5% 0 0deg);
|
|
34
|
+
--sidebar-border: oklch(100% 0 0deg / 10%);
|
|
35
|
+
--sidebar-ring: oklch(55.6% 0 0deg);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@theme inline {
|
|
39
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
40
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
41
|
+
--radius-lg: var(--radius);
|
|
42
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
43
|
+
--color-background: var(--background);
|
|
44
|
+
--color-foreground: var(--foreground);
|
|
45
|
+
--color-card: var(--card);
|
|
46
|
+
--color-card-foreground: var(--card-foreground);
|
|
47
|
+
--color-popover: var(--popover);
|
|
48
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
49
|
+
--color-primary: var(--primary);
|
|
50
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
51
|
+
--color-secondary: var(--secondary);
|
|
52
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
53
|
+
--color-muted: var(--muted);
|
|
54
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
55
|
+
--color-accent: var(--accent);
|
|
56
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
57
|
+
--color-destructive: var(--destructive);
|
|
58
|
+
--color-border: var(--border);
|
|
59
|
+
--color-input: var(--input);
|
|
60
|
+
--color-ring: var(--ring);
|
|
61
|
+
--color-chart-1: var(--chart-1);
|
|
62
|
+
--color-chart-2: var(--chart-2);
|
|
63
|
+
--color-chart-3: var(--chart-3);
|
|
64
|
+
--color-chart-4: var(--chart-4);
|
|
65
|
+
--color-chart-5: var(--chart-5);
|
|
66
|
+
--color-sidebar: var(--sidebar);
|
|
67
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
68
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
69
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
70
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
71
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
72
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
73
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
:root {
|
|
77
|
+
--radius: 0.625rem;
|
|
78
|
+
--background: oklch(100% 0 0deg);
|
|
79
|
+
--foreground: oklch(14.5% 0 0deg);
|
|
80
|
+
--card: oklch(100% 0 0deg);
|
|
81
|
+
--card-foreground: oklch(14.5% 0 0deg);
|
|
82
|
+
--popover: oklch(100% 0 0deg);
|
|
83
|
+
--popover-foreground: oklch(14.5% 0 0deg);
|
|
84
|
+
--primary: oklch(20.5% 0 0deg);
|
|
85
|
+
--primary-foreground: oklch(98.5% 0 0deg);
|
|
86
|
+
--secondary: oklch(97% 0 0deg);
|
|
87
|
+
--secondary-foreground: oklch(20.5% 0 0deg);
|
|
88
|
+
--muted: oklch(97% 0 0deg);
|
|
89
|
+
--muted-foreground: oklch(55.6% 0 0deg);
|
|
90
|
+
--accent: oklch(97% 0 0deg);
|
|
91
|
+
--accent-foreground: oklch(20.5% 0 0deg);
|
|
92
|
+
--destructive: oklch(57.7% 0.245 27.32deg);
|
|
93
|
+
--border: oklch(92.2% 0 0deg);
|
|
94
|
+
--input: oklch(92.2% 0 0deg);
|
|
95
|
+
--ring: oklch(70.8% 0 0deg);
|
|
96
|
+
--chart-1: oklch(64.6% 0.222 41.11deg);
|
|
97
|
+
--chart-2: oklch(60% 0.118 184.7deg);
|
|
98
|
+
--chart-3: oklch(39.8% 0.07 227.39deg);
|
|
99
|
+
--chart-4: oklch(82.8% 0.189 84.42deg);
|
|
100
|
+
--chart-5: oklch(76.9% 0.188 70deg);
|
|
101
|
+
--sidebar: oklch(98.5% 0 0deg);
|
|
102
|
+
--sidebar-foreground: oklch(14.5% 0 0deg);
|
|
103
|
+
--sidebar-primary: oklch(20.5% 0 0deg);
|
|
104
|
+
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
|
|
105
|
+
--sidebar-accent: oklch(97% 0 0deg);
|
|
106
|
+
--sidebar-accent-foreground: oklch(20.5% 0 0deg);
|
|
107
|
+
--sidebar-border: oklch(92.2% 0 0deg);
|
|
108
|
+
--sidebar-ring: oklch(70.8% 0 0deg);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@layer base {
|
|
112
|
+
* {
|
|
113
|
+
@apply border-border outline-ring/50;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
body {
|
|
117
|
+
@apply bg-background text-foreground;
|
|
118
|
+
}
|
|
119
|
+
}
|
package/app/assets/css/vio.css
CHANGED
|
@@ -5,15 +5,17 @@
|
|
|
5
5
|
/* stylelint-disable plugin/no-unsupported-browser-features */
|
|
6
6
|
|
|
7
7
|
@import 'tailwindcss';
|
|
8
|
+
@import 'tw-animate-css';
|
|
8
9
|
@import './forms.css';
|
|
10
|
+
@import './shadcn.css';
|
|
9
11
|
|
|
10
|
-
@source
|
|
12
|
+
@source '../../';
|
|
11
13
|
|
|
12
|
-
@plugin
|
|
14
|
+
@plugin '@tailwindcss/forms' {
|
|
13
15
|
strategy: 'base';
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
@plugin
|
|
18
|
+
@plugin '@tailwindcss/typography';
|
|
17
19
|
|
|
18
20
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
19
21
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { ToasterProps } from 'vue-sonner'
|
|
3
|
+
import {
|
|
4
|
+
CircleCheckIcon,
|
|
5
|
+
InfoIcon,
|
|
6
|
+
Loader2Icon,
|
|
7
|
+
OctagonXIcon,
|
|
8
|
+
TriangleAlertIcon,
|
|
9
|
+
XIcon,
|
|
10
|
+
} from 'lucide-vue-next'
|
|
11
|
+
import { Toaster as Sonner } from 'vue-sonner'
|
|
12
|
+
import { cn } from '@/utils/shadcn'
|
|
13
|
+
|
|
14
|
+
const props = defineProps<ToasterProps>()
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<Sonner
|
|
19
|
+
:class="cn('toaster group', props.class)"
|
|
20
|
+
:style="{
|
|
21
|
+
'--normal-bg': 'var(--popover)',
|
|
22
|
+
'--normal-text': 'var(--popover-foreground)',
|
|
23
|
+
'--normal-border': 'var(--border)',
|
|
24
|
+
'--border-radius': 'var(--radius)',
|
|
25
|
+
}"
|
|
26
|
+
v-bind="props"
|
|
27
|
+
>
|
|
28
|
+
<template #success-icon>
|
|
29
|
+
<CircleCheckIcon class="size-4" />
|
|
30
|
+
</template>
|
|
31
|
+
<template #info-icon>
|
|
32
|
+
<InfoIcon class="size-4" />
|
|
33
|
+
</template>
|
|
34
|
+
<template #warning-icon>
|
|
35
|
+
<TriangleAlertIcon class="size-4" />
|
|
36
|
+
</template>
|
|
37
|
+
<template #error-icon>
|
|
38
|
+
<OctagonXIcon class="size-4" />
|
|
39
|
+
</template>
|
|
40
|
+
<template #loading-icon>
|
|
41
|
+
<div>
|
|
42
|
+
<Loader2Icon class="size-4 animate-spin" />
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
45
|
+
<template #close-icon>
|
|
46
|
+
<XIcon class="size-4" />
|
|
47
|
+
</template>
|
|
48
|
+
</Sonner>
|
|
49
|
+
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Toaster } from './Sonner.vue'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- TODO: add `class="pointer-events-auto"` -->
|
|
3
|
+
<Toaster rich-colors :theme="colorModePreference" />
|
|
4
|
+
</template>
|
|
5
|
+
|
|
6
|
+
<script setup lang="ts">
|
|
7
|
+
import type { ColorMode } from '~/types/color-mode'
|
|
8
|
+
|
|
9
|
+
const colorMode = useColorMode()
|
|
10
|
+
|
|
11
|
+
const colorModePreference = colorMode.preference as ColorMode
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<style>
|
|
15
|
+
@import url('vue-sonner/style.css');
|
|
16
|
+
</style>
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
import { reactiveOmit } from '@vueuse/core'
|
|
11
11
|
import { useForwardProps } from 'reka-ui'
|
|
12
12
|
|
|
13
|
-
// TODO: use imported type (https://github.com/nuxt/nuxt/issues/29757)
|
|
14
13
|
import type { NuxtTimeProps } from '#app'
|
|
15
14
|
|
|
16
15
|
const { locale: defaultLocale } = useI18n()
|
|
@@ -26,7 +26,7 @@ const copy = async (string: string) => {
|
|
|
26
26
|
|
|
27
27
|
try {
|
|
28
28
|
await copyText(string)
|
|
29
|
-
|
|
29
|
+
toast.success(t('donationUrlCopySuccess'))
|
|
30
30
|
} catch (error: unknown) {
|
|
31
31
|
console.error(error)
|
|
32
32
|
alert(t('donationUrlCopyError'))
|
package/app/composables/alert.ts
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { consola } from 'consola'
|
|
2
|
-
import Swal from 'sweetalert2'
|
|
3
|
-
import type { Ref } from 'vue'
|
|
4
2
|
|
|
5
|
-
export const
|
|
3
|
+
export const useAlertError = () => {
|
|
6
4
|
const { t } = useI18n({ useScope: 'global' })
|
|
7
5
|
|
|
8
|
-
return (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
return (
|
|
7
|
+
options:
|
|
8
|
+
| string
|
|
9
|
+
| {
|
|
10
|
+
error?: Error
|
|
11
|
+
messageI18n: string
|
|
12
|
+
toastOptions?: Parameters<typeof toast>[1]
|
|
13
|
+
},
|
|
14
|
+
) => {
|
|
15
|
+
const error =
|
|
16
|
+
typeof options === 'string' ? new Error(options) : options.error
|
|
17
|
+
const errorMessage =
|
|
18
|
+
typeof options === 'string' ? options : options.messageI18n
|
|
19
|
+
|
|
20
|
+
consola.error({ errorMessage, ...(error ? { error } : {}) })
|
|
21
|
+
toast.error(t('globalError'), {
|
|
22
|
+
...(typeof options !== 'string' ? options.toastOptions || {} : {}),
|
|
23
|
+
description: errorMessage,
|
|
13
24
|
})
|
|
14
|
-
api?.value.errors.push(error)
|
|
15
|
-
consola.error(error)
|
|
16
25
|
}
|
|
17
26
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type ColorMode = 'dark' | 'light' | 'system'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { toast } from 'vue-sonner'
|
package/i18n/locales/de.json
CHANGED
package/i18n/locales/en.json
CHANGED
package/nuxt.config.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
|
|
1
4
|
import tailwindcss from '@tailwindcss/vite'
|
|
2
5
|
import { defu } from 'defu'
|
|
3
6
|
|
|
@@ -10,6 +13,8 @@ import {
|
|
|
10
13
|
} from './shared/utils/constants'
|
|
11
14
|
import { VIO_NUXT_BASE_CONFIG } from './shared/utils/nuxt'
|
|
12
15
|
|
|
16
|
+
const currentDir = dirname(fileURLToPath(import.meta.url))
|
|
17
|
+
|
|
13
18
|
export default defineNuxtConfig(
|
|
14
19
|
defu(
|
|
15
20
|
{
|
|
@@ -50,6 +55,7 @@ export default defineNuxtConfig(
|
|
|
50
55
|
'@nuxtjs/seo',
|
|
51
56
|
'@pinia/nuxt',
|
|
52
57
|
'nuxt-gtag',
|
|
58
|
+
'shadcn-nuxt',
|
|
53
59
|
(_options, nuxt) => {
|
|
54
60
|
if (nuxt.options.nitro.static) {
|
|
55
61
|
nuxt.options.features.inlineStyles = false
|
|
@@ -65,11 +71,6 @@ export default defineNuxtConfig(
|
|
|
65
71
|
) {
|
|
66
72
|
if (nuxt.options.nitro.static) {
|
|
67
73
|
nuxtConfigSecurityHeaders.contentSecurityPolicy = defu(
|
|
68
|
-
{
|
|
69
|
-
'script-src-elem': [
|
|
70
|
-
"'unsafe-inline'", // TODO: remove (https://github.com/Baroshem/nuxt-security/pull/659)
|
|
71
|
-
],
|
|
72
|
-
},
|
|
73
74
|
VIO_GET_CSP({ siteUrl: new URL(SITE_URL) }),
|
|
74
75
|
nuxtConfigSecurityHeaders.contentSecurityPolicy,
|
|
75
76
|
)
|
|
@@ -242,6 +243,10 @@ export default defineNuxtConfig(
|
|
|
242
243
|
},
|
|
243
244
|
strict: true,
|
|
244
245
|
},
|
|
246
|
+
shadcn: {
|
|
247
|
+
prefix: '',
|
|
248
|
+
componentDir: join(currentDir, './app/components/scn'),
|
|
249
|
+
},
|
|
245
250
|
site: {
|
|
246
251
|
url: SITE_URL,
|
|
247
252
|
},
|
|
@@ -256,7 +261,8 @@ export default defineNuxtConfig(
|
|
|
256
261
|
},
|
|
257
262
|
nitro: {
|
|
258
263
|
experimental: {
|
|
259
|
-
|
|
264
|
+
asyncContext: true,
|
|
265
|
+
openAPI: true,
|
|
260
266
|
},
|
|
261
267
|
},
|
|
262
268
|
runtimeConfig: {
|
|
@@ -270,7 +276,6 @@ export default defineNuxtConfig(
|
|
|
270
276
|
// modules
|
|
271
277
|
security: {
|
|
272
278
|
headers: {
|
|
273
|
-
crossOriginEmbedderPolicy: 'unsafe-none',
|
|
274
279
|
strictTransportSecurity: false, // prevent endless reload in Chrome
|
|
275
280
|
},
|
|
276
281
|
},
|
|
@@ -295,6 +300,7 @@ export default defineNuxtConfig(
|
|
|
295
300
|
},
|
|
296
301
|
security: {
|
|
297
302
|
headers: {
|
|
303
|
+
crossOriginEmbedderPolicy: 'require-corp', // breaks nuxt devtools
|
|
298
304
|
strictTransportSecurity: {
|
|
299
305
|
maxAge: 31536000,
|
|
300
306
|
preload: true,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"@dargmuesli/nuxt-cookie-control": "9.1.
|
|
4
|
-
"@eslint/compat": "
|
|
3
|
+
"@dargmuesli/nuxt-cookie-control": "9.1.8",
|
|
4
|
+
"@eslint/compat": "2.0.0",
|
|
5
5
|
"@heroicons/vue": "2.2.0",
|
|
6
6
|
"@http-util/status-i18n": "0.9.0",
|
|
7
7
|
"@intlify/eslint-plugin-vue-i18n": "4.1.0",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"@nuxt/image": "2.0.0",
|
|
11
11
|
"@nuxtjs/color-mode": "4.0.0",
|
|
12
12
|
"@nuxtjs/html-validator": "2.1.0",
|
|
13
|
-
"@nuxtjs/i18n": "10.2.
|
|
13
|
+
"@nuxtjs/i18n": "10.2.1",
|
|
14
14
|
"@nuxtjs/seo": "3.2.2",
|
|
15
15
|
"@pinia/nuxt": "0.11.3",
|
|
16
16
|
"@tailwindcss/forms": "0.5.10",
|
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
"@urql/vue": "2.0.0",
|
|
22
22
|
"@vuelidate/core": "2.0.3",
|
|
23
23
|
"@vuelidate/validators": "2.0.4",
|
|
24
|
-
"
|
|
24
|
+
"@vueuse/core": "14.0.0",
|
|
25
|
+
"class-variance-authority": "0.7.1",
|
|
26
|
+
"clipboardy": "5.0.1",
|
|
27
|
+
"clsx": "2.1.1",
|
|
25
28
|
"eslint": "9.39.1",
|
|
26
29
|
"eslint-config-prettier": "10.1.8",
|
|
27
30
|
"eslint-plugin-compat": "6.0.2",
|
|
@@ -29,14 +32,18 @@
|
|
|
29
32
|
"eslint-plugin-yml": "1.19.0",
|
|
30
33
|
"globals": "16.5.0",
|
|
31
34
|
"jiti": "2.6.1",
|
|
32
|
-
"jose": "6.1.
|
|
35
|
+
"jose": "6.1.2",
|
|
36
|
+
"lucide-vue-next": "0.554.0",
|
|
33
37
|
"nuxt-gtag": "4.1.0",
|
|
34
|
-
"nuxt-security": "2.
|
|
35
|
-
"
|
|
36
|
-
"
|
|
38
|
+
"nuxt-security": "2.5.0",
|
|
39
|
+
"shadcn-nuxt": "2.3.3",
|
|
40
|
+
"tailwind-merge": "3.4.0",
|
|
41
|
+
"tw-animate-css": "1.4.0",
|
|
42
|
+
"vue-sonner": "2.0.9",
|
|
43
|
+
"vue-tsc": "3.1.5"
|
|
37
44
|
},
|
|
38
45
|
"devDependencies": {
|
|
39
|
-
"@types/node": "24.10.
|
|
46
|
+
"@types/node": "24.10.1",
|
|
40
47
|
"@urql/devtools": "2.0.3",
|
|
41
48
|
"@urql/exchange-graphcache": "8.1.0",
|
|
42
49
|
"@vueuse/core": "14.0.0",
|
|
@@ -51,12 +58,12 @@
|
|
|
51
58
|
"reka-ui": "2.6.0",
|
|
52
59
|
"serve": "14.2.5",
|
|
53
60
|
"sharp": "0.34.5",
|
|
54
|
-
"stylelint": "16.
|
|
61
|
+
"stylelint": "16.26.0",
|
|
55
62
|
"stylelint-config-recommended-vue": "1.6.1",
|
|
56
63
|
"stylelint-config-standard": "39.0.1",
|
|
57
64
|
"stylelint-no-unsupported-browser-features": "8.0.5",
|
|
58
65
|
"tailwindcss": "4.1.17",
|
|
59
|
-
"vue": "3.5.
|
|
66
|
+
"vue": "3.5.25",
|
|
60
67
|
"vue-router": "4.6.3"
|
|
61
68
|
},
|
|
62
69
|
"engines": {
|
|
@@ -107,5 +114,5 @@
|
|
|
107
114
|
"start:static": "serve playground/.output/public --ssl-cert ./.config/certificates/ssl.crt --ssl-key ./.config/certificates/ssl.key"
|
|
108
115
|
},
|
|
109
116
|
"type": "module",
|
|
110
|
-
"version": "20.0.0-beta.
|
|
117
|
+
"version": "20.0.0-beta.2"
|
|
111
118
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { defu } from 'defu'
|
|
2
|
-
import type {
|
|
2
|
+
import type { ModuleOptions } from 'nuxt-security'
|
|
3
3
|
|
|
4
4
|
// remove invalid `'none'`s and duplicates
|
|
5
5
|
export const cleanupCsp = (
|
|
6
|
-
|
|
7
|
-
nuxtSecurityConfiguration: Partial<NuxtOptions['security']>,
|
|
6
|
+
nuxtSecurityConfiguration: Partial<ModuleOptions>,
|
|
8
7
|
) => {
|
|
9
8
|
if (
|
|
10
9
|
nuxtSecurityConfiguration.headers &&
|
|
@@ -28,7 +27,6 @@ export const cleanupCsp = (
|
|
|
28
27
|
}
|
|
29
28
|
|
|
30
29
|
export default defineNitroPlugin((nitroApp) => {
|
|
31
|
-
// @ts-expect-error https://github.com/Baroshem/nuxt-security/pull/661
|
|
32
30
|
nitroApp.hooks.hook('nuxt-security:routeRules', async (routeRules) => {
|
|
33
31
|
const { siteUrlTyped: siteUrl } = useSiteUrl()
|
|
34
32
|
|
package/server/utils/timezone.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { H3Event } from 'h3'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
export const getTimezoneServer = async (event: H3Event) => {
|
|
3
|
+
export const getTimezone = async (event: H3Event) => {
|
|
5
4
|
const timezoneBySsr = event.context.$timezone
|
|
6
5
|
|
|
7
6
|
if (timezoneBySsr) return timezoneBySsr
|
|
@@ -113,14 +113,6 @@ export const VIO_GET_CSP = ({ siteUrl }: { siteUrl: URL }) =>
|
|
|
113
113
|
: {}),
|
|
114
114
|
'connect-src': [
|
|
115
115
|
"'self'", // e.g. `/_nuxt/builds/meta/`, `/_payload.json`, `/privacy-policy/_payload.json`
|
|
116
|
-
// ...(process.env.NODE_ENV === 'development'
|
|
117
|
-
// ? [
|
|
118
|
-
// 'http://localhost:3000/_nuxt/', // hot reload
|
|
119
|
-
// 'https://localhost:3000/_nuxt/', // hot reload
|
|
120
|
-
// 'ws://localhost:3000/_nuxt/', // hot reload
|
|
121
|
-
// 'wss://localhost:3000/_nuxt/', // hot reload
|
|
122
|
-
// ] // TODO: generalize for different ports
|
|
123
|
-
// : []),
|
|
124
116
|
],
|
|
125
117
|
'img-src': [
|
|
126
118
|
"'self'", // e.g. favicon
|
|
@@ -131,7 +123,7 @@ export const VIO_GET_CSP = ({ siteUrl }: { siteUrl: URL }) =>
|
|
|
131
123
|
`${siteUrl}_nuxt/`, // bundle
|
|
132
124
|
],
|
|
133
125
|
'style-src': [
|
|
134
|
-
"'
|
|
126
|
+
"'nonce-{{nonce}}'",
|
|
135
127
|
"'self'", // TODO: `${siteUrl}_nuxt/`, // bundle
|
|
136
128
|
], // TODO: use `style-src-elem` once Playwright WebKit supports it
|
|
137
129
|
},
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the duration between two dates in terms of years, months, weeks, days, hours, and minutes.
|
|
3
|
+
*
|
|
4
|
+
* This utility is useful for displaying human-readable differences between two timestamps.
|
|
5
|
+
* The returned object represents the absolute difference, regardless of the order of `from` and `to`.
|
|
6
|
+
*
|
|
7
|
+
* @param {Date} from - The starting date of the interval.
|
|
8
|
+
* @param {Date} to - The ending date of the interval.
|
|
9
|
+
* @returns {{ years: number, months: number, weeks: number, days: number, hours: number, minutes: number }}
|
|
10
|
+
* An object representing the time difference between the two dates:
|
|
11
|
+
* - `years`: Number of full years
|
|
12
|
+
* - `months`: Remaining full months after accounting for years
|
|
13
|
+
* - `weeks`: Remaining full weeks after accounting for months
|
|
14
|
+
* - `days`: Remaining days after accounting for weeks
|
|
15
|
+
* - `hours`: Remaining hours
|
|
16
|
+
* - `minutes`: Remaining minutes
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* getDuration(new Date('2020-01-01'), new Date('2023-04-10'))
|
|
20
|
+
* // returns: { years: 3, months: 3, weeks: 1, days: 2, hours: 0, minutes: 0 }
|
|
21
|
+
*/
|
|
22
|
+
export const getDuration = (
|
|
23
|
+
from: Date,
|
|
24
|
+
to: Date,
|
|
25
|
+
): {
|
|
26
|
+
days: number
|
|
27
|
+
hours: number
|
|
28
|
+
minutes: number
|
|
29
|
+
months: number
|
|
30
|
+
weeks: number
|
|
31
|
+
years: number
|
|
32
|
+
} => {
|
|
33
|
+
const start = from > to ? to : from
|
|
34
|
+
const end = from > to ? from : to
|
|
35
|
+
|
|
36
|
+
let years = end.getUTCFullYear() - start.getUTCFullYear()
|
|
37
|
+
let months = end.getUTCMonth() - start.getUTCMonth()
|
|
38
|
+
let days = end.getUTCDate() - start.getUTCDate()
|
|
39
|
+
let hours = end.getUTCHours() - start.getUTCHours()
|
|
40
|
+
let minutes = end.getUTCMinutes() - start.getUTCMinutes()
|
|
41
|
+
|
|
42
|
+
if (minutes < 0) {
|
|
43
|
+
minutes += 60
|
|
44
|
+
hours--
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (hours < 0) {
|
|
48
|
+
hours += 24
|
|
49
|
+
days--
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (days < 0) {
|
|
53
|
+
const prevMonth = new Date(
|
|
54
|
+
Date.UTC(end.getUTCFullYear(), end.getUTCMonth(), 0),
|
|
55
|
+
)
|
|
56
|
+
days += prevMonth.getUTCDate()
|
|
57
|
+
months--
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (months < 0) {
|
|
61
|
+
months += 12
|
|
62
|
+
years--
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const weeks = Math.floor(days / 7)
|
|
66
|
+
days = days % 7
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
days,
|
|
70
|
+
hours,
|
|
71
|
+
minutes,
|
|
72
|
+
months,
|
|
73
|
+
weeks,
|
|
74
|
+
years,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Default date-time formatting options used for displaying timestamps in a concise and readable format.
|
|
80
|
+
*
|
|
81
|
+
* Format includes:
|
|
82
|
+
* - Day (numeric, e.g., "15")
|
|
83
|
+
* - Month (short name, e.g., "Jul")
|
|
84
|
+
* - Year (numeric, e.g., "2025")
|
|
85
|
+
* - Hour and minute (numeric, 12/24-hour based on locale, e.g., "6:00 PM")
|
|
86
|
+
* - Time zone name (short, e.g., "UTC", "PST")
|
|
87
|
+
*
|
|
88
|
+
* These options are intended to be used with `Intl.DateTimeFormat`.
|
|
89
|
+
*/
|
|
90
|
+
export const dateTimeFormatOptions: Intl.DateTimeFormatOptions = {
|
|
91
|
+
day: 'numeric',
|
|
92
|
+
hour: 'numeric',
|
|
93
|
+
minute: 'numeric',
|
|
94
|
+
month: 'short',
|
|
95
|
+
timeZoneName: 'short',
|
|
96
|
+
year: 'numeric',
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Returns a date-time formatter function for use in emails which don't support targeted locales yet.
|
|
101
|
+
*
|
|
102
|
+
* The formatter uses the specified locale and a fixed UTC time zone.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} locale - A BCP 47 language tag (e.g., "en-US", "fr-FR") used to format the date/time.
|
|
105
|
+
* @returns {Intl.DateTimeFormat} A formatter instance that formats dates in the specified locale and UTC time zone.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* const format = getEmailDateTimeFormatter('en-US')
|
|
109
|
+
* format.format(new Date('2025-07-15T18:00:00Z'))
|
|
110
|
+
* // → "Jul 15, 2025, 6:00 PM UTC"
|
|
111
|
+
*/
|
|
112
|
+
export const getEmailDateTimeFormatter = (
|
|
113
|
+
locale: string,
|
|
114
|
+
): Intl.DateTimeFormat =>
|
|
115
|
+
Intl.DateTimeFormat(locale, {
|
|
116
|
+
...dateTimeFormatOptions,
|
|
117
|
+
timeZone: 'UTC',
|
|
118
|
+
})
|
|
@@ -103,9 +103,7 @@ export const getServiceHref = ({
|
|
|
103
103
|
|
|
104
104
|
if (stagingHost) {
|
|
105
105
|
return `https://${nameSubdomainString}${stagingHost}`
|
|
106
|
-
|
|
107
|
-
// eslint-disable-next-line nuxt/prefer-import-meta
|
|
108
|
-
} else if (isSsr && process.server) {
|
|
106
|
+
} else if (isSsr && import.meta.server) {
|
|
109
107
|
return `http://${name}${portString}`
|
|
110
108
|
} else {
|
|
111
109
|
return `https://${nameSubdomainString}${getRootHost(host)}`
|
package/shared/utils/modal.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import Swal from 'sweetalert2'
|
|
2
|
-
|
|
3
|
-
export const showToast = ({ title }: { title: string }) =>
|
|
4
|
-
Swal.fire({
|
|
5
|
-
didOpen: (toast) => {
|
|
6
|
-
toast.addEventListener('mouseenter', Swal.stopTimer)
|
|
7
|
-
toast.addEventListener('mouseleave', Swal.resumeTimer)
|
|
8
|
-
},
|
|
9
|
-
icon: 'success',
|
|
10
|
-
position: 'bottom-right',
|
|
11
|
-
showConfirmButton: false,
|
|
12
|
-
timer: 3000,
|
|
13
|
-
timerProgressBar: true,
|
|
14
|
-
title,
|
|
15
|
-
toast: true,
|
|
16
|
-
})
|