@frigear-nu/cookie-consent 0.0.5 → 0.0.7
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/module.d.mts +21 -9
- package/dist/module.json +1 -1
- package/dist/module.mjs +18 -5
- package/dist/runtime/app/components/CookieConsentBanner.vue +28 -43
- package/dist/runtime/app/components/CookieConsentBannerActions.d.vue.ts +3 -0
- package/dist/runtime/app/components/CookieConsentBannerActions.vue +29 -0
- package/dist/runtime/app/components/CookieConsentBannerActions.vue.d.ts +3 -0
- package/dist/runtime/app/components/CookieConsentManage.vue +1 -5
- package/dist/runtime/app/components/CookieConsentPreferences.vue +11 -15
- package/dist/runtime/app/composables/useConsentT.d.ts +5 -2
- package/dist/runtime/app/composables/useConsentT.js +5 -1
- package/dist/runtime/server/routes/cookie-consent/index.get.js +1 -1
- package/dist/runtime/server/routes/cookie-consent/index.post.js +1 -1
- package/dist/runtime/utils/consent.d.ts +2 -2
- package/dist/runtime/utils/index.d.ts +1 -1
- package/dist/types.d.mts +3 -1
- package/package.json +10 -3
package/dist/module.d.mts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
import { Translated } from '@frigear-nu/ui/translated';
|
|
3
|
+
export { Translated } from '@frigear-nu/ui/translated';
|
|
2
4
|
|
|
3
|
-
/** A string, or a map of locale code -> string (`{ da: '...', en: '...' }`). */
|
|
4
|
-
type Localized = string | Record<string, string>;
|
|
5
5
|
/** A cookie (or similar storage entry) set under a category - listed in the preferences dialog for GDPR transparency. */
|
|
6
6
|
interface ConsentCookie {
|
|
7
7
|
name: string;
|
|
8
8
|
provider?: string;
|
|
9
|
-
purpose?:
|
|
9
|
+
purpose?: Translated;
|
|
10
10
|
/** Human readable, e.g. `'1 year'` or `'Session'`. */
|
|
11
11
|
duration?: string;
|
|
12
12
|
}
|
|
@@ -17,9 +17,9 @@ interface ConsentCategory {
|
|
|
17
17
|
/** Pre-selected state in the preferences dialog. Never applied without the user saving. Default `false`. */
|
|
18
18
|
defaultEnabled?: boolean;
|
|
19
19
|
/** Defaults to the i18n key `cookieConsent.categories.<key>.label`. */
|
|
20
|
-
label?:
|
|
20
|
+
label?: Translated;
|
|
21
21
|
/** Defaults to the i18n key `cookieConsent.categories.<key>.description`. */
|
|
22
|
-
description?:
|
|
22
|
+
description?: Translated;
|
|
23
23
|
cookies?: ConsentCookie[];
|
|
24
24
|
}
|
|
25
25
|
interface ConsentMessages {
|
|
@@ -49,13 +49,23 @@ interface ModuleOptions {
|
|
|
49
49
|
excludeRoutes?: string[];
|
|
50
50
|
/** Collapse the banner into a round button once scrolled this many px (`true` = 80, `false` = never). Default `true`. */
|
|
51
51
|
collapseOnScroll?: boolean | number;
|
|
52
|
-
/** Link to the cookie/privacy policy shown in the banner. */
|
|
53
|
-
policyUrl?:
|
|
52
|
+
/** Link to the cookie/privacy policy shown in the banner. A string, or a map of locale code -> URL (`{ da: '/da/gdpr', en: '/gdpr' }`). */
|
|
53
|
+
policyUrl?: Translated;
|
|
54
|
+
/**
|
|
55
|
+
* Text of the policy link in the banner (the "Read more" link). A string or a `{ da, en }` map.
|
|
56
|
+
* Defaults to the i18n key `cookieConsent.policy`.
|
|
57
|
+
*/
|
|
58
|
+
policyLabel?: Translated;
|
|
54
59
|
/**
|
|
55
60
|
* Store the consent on the signed-in user (`@frigear-nu/users` + `@nuxthub/core` required).
|
|
56
61
|
* Default: `true` when `@frigear-nu/users` is installed.
|
|
57
62
|
*/
|
|
58
63
|
persistToUser?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Iconify name (`collection:name`) of the icon on the banner badge and the collapsed button. Default `lucide:cookie`.
|
|
66
|
+
* The module adds it to Nuxt Icon's client bundle, because Nuxt Icon does not scan `node_modules` for icons.
|
|
67
|
+
*/
|
|
68
|
+
icon?: string;
|
|
59
69
|
}
|
|
60
70
|
interface PublicModuleOptions {
|
|
61
71
|
cookieName: string;
|
|
@@ -64,8 +74,10 @@ interface PublicModuleOptions {
|
|
|
64
74
|
categories: ConsentCategory[];
|
|
65
75
|
excludeRoutes: string[];
|
|
66
76
|
collapseThreshold: number | false;
|
|
67
|
-
policyUrl?:
|
|
77
|
+
policyUrl?: Translated;
|
|
78
|
+
policyLabel?: Translated;
|
|
68
79
|
persistToUser: boolean;
|
|
80
|
+
icon: string;
|
|
69
81
|
}
|
|
70
82
|
/** What is stored in the consent cookie and (for signed-in users) in the database. */
|
|
71
83
|
interface ConsentState {
|
|
@@ -125,4 +137,4 @@ declare module 'nitropack' {
|
|
|
125
137
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
126
138
|
|
|
127
139
|
export { _default as default };
|
|
128
|
-
export type { ConsentCategory, ConsentCookie, ConsentMessages, ConsentState,
|
|
140
|
+
export type { ConsentCategory, ConsentCookie, ConsentMessages, ConsentState, ModuleOptions, PublicModuleOptions };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, hasNuxtModule, addImports, addServerImports, addComponentsDir, addServerHandler } from '@nuxt/kit';
|
|
2
|
-
import { registerModule, registerLocales, extendDatabaseTable } from '@frigear-nu/module-kit/utils';
|
|
2
|
+
import { registerModule, registerTailwindSource, registerLocales, extendDatabaseTable } from '@frigear-nu/module-kit/utils';
|
|
3
3
|
import { mergeCategories, DEFAULT_CATEGORIES, computeVersion } from '../dist/runtime/utils/consent.js';
|
|
4
4
|
|
|
5
5
|
const DEFAULT_MAX_AGE = 60 * 60 * 24 * 180;
|
|
6
|
+
const DEFAULT_ICON = "lucide:cookie";
|
|
6
7
|
const module$1 = defineNuxtModule({
|
|
7
8
|
meta: { name: "@frigear-nu/cookie-consent", configKey: "cookieConsent" },
|
|
8
9
|
defaults: {
|
|
9
10
|
cookieName: "cookie-consent",
|
|
10
11
|
maxAge: DEFAULT_MAX_AGE,
|
|
11
12
|
excludeRoutes: ["/authorize"],
|
|
12
|
-
collapseOnScroll: true
|
|
13
|
+
collapseOnScroll: true,
|
|
14
|
+
icon: DEFAULT_ICON
|
|
13
15
|
},
|
|
14
16
|
moduleDependencies: {
|
|
15
|
-
"@frigear-nu/module-kit": { version: ">=0.0.1" }
|
|
17
|
+
"@frigear-nu/module-kit": { version: ">=0.0.1" },
|
|
18
|
+
// Declared (optional) so they are set up first and `hasNuxtModule` below does not depend on the module order:
|
|
19
|
+
// with `users` the consent is also stored on the signed-in user.
|
|
20
|
+
"@frigear-nu/auth": { version: ">=0.0.1", optional: true },
|
|
21
|
+
"@frigear-nu/users": { version: ">=0.0.1", optional: true }
|
|
16
22
|
},
|
|
17
23
|
async setup(options, nuxt) {
|
|
18
24
|
registerModule("@frigear-nu/cookie-consent", nuxt);
|
|
19
25
|
const resolver = createResolver(import.meta.url);
|
|
26
|
+
registerTailwindSource(nuxt, resolver.resolve("./runtime/app"));
|
|
20
27
|
registerLocales(nuxt, resolver.resolve("./runtime/i18n/lang"));
|
|
21
28
|
const persistToUser = (options.persistToUser ?? true) && hasNuxtModule("@frigear-nu/users");
|
|
22
29
|
addImports([
|
|
@@ -28,7 +35,7 @@ const module$1 = defineNuxtModule({
|
|
|
28
35
|
{ name: "getCookieConsent", from: resolver.resolve("./runtime/server/utils/consent") },
|
|
29
36
|
{ name: "hasCookieConsent", from: resolver.resolve("./runtime/server/utils/consent") }
|
|
30
37
|
]);
|
|
31
|
-
addComponentsDir({ path: resolver.resolve("./runtime/app/components") });
|
|
38
|
+
addComponentsDir({ path: resolver.resolve("./runtime/app/components"), priority: -1 });
|
|
32
39
|
if (persistToUser) {
|
|
33
40
|
addServerHandler({ route: "/api/cookie-consent", method: "get", handler: resolver.resolve("./runtime/server/routes/cookie-consent/index.get") });
|
|
34
41
|
addServerHandler({ route: "/api/cookie-consent", method: "post", handler: resolver.resolve("./runtime/server/routes/cookie-consent/index.post") });
|
|
@@ -38,6 +45,10 @@ const module$1 = defineNuxtModule({
|
|
|
38
45
|
file: (dialect) => `./runtime/server/db/schema.${dialect}`
|
|
39
46
|
});
|
|
40
47
|
}
|
|
48
|
+
const icon = options.icon || DEFAULT_ICON;
|
|
49
|
+
nuxt.hook("icon:clientBundleIcons", ((icons) => {
|
|
50
|
+
icons.add(icon.replace(/^i[-:]/, ""));
|
|
51
|
+
}));
|
|
41
52
|
nuxt.hook("modules:done", async () => {
|
|
42
53
|
const categories = mergeCategories(
|
|
43
54
|
options.categories ?? DEFAULT_CATEGORIES,
|
|
@@ -52,7 +63,9 @@ const module$1 = defineNuxtModule({
|
|
|
52
63
|
excludeRoutes: options.excludeRoutes ?? [],
|
|
53
64
|
collapseThreshold: options.collapseOnScroll === false ? false : typeof options.collapseOnScroll === "number" ? options.collapseOnScroll : 80,
|
|
54
65
|
policyUrl: options.policyUrl,
|
|
55
|
-
|
|
66
|
+
policyLabel: options.policyLabel,
|
|
67
|
+
persistToUser,
|
|
68
|
+
icon
|
|
56
69
|
};
|
|
57
70
|
});
|
|
58
71
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { useWindowScroll } from "@vueuse/core";
|
|
3
|
-
const { config, hasResponded,
|
|
4
|
-
const { t } = useConsentT();
|
|
3
|
+
const { config, hasResponded, hydrateFromUser } = useCookieConsent();
|
|
4
|
+
const { t, policyUrl } = useConsentT();
|
|
5
5
|
const route = useRoute();
|
|
6
6
|
const excluded = computed(() => config.excludeRoutes.includes(route.path));
|
|
7
7
|
const open = computed(() => !hasResponded.value && !excluded.value);
|
|
@@ -21,69 +21,58 @@ onMounted(() => {
|
|
|
21
21
|
|
|
22
22
|
<template>
|
|
23
23
|
<ClientOnly>
|
|
24
|
-
<Transition
|
|
24
|
+
<Transition
|
|
25
|
+
enter-active-class="transition duration-200 ease-out"
|
|
26
|
+
leave-active-class="transition duration-200 ease-out"
|
|
27
|
+
enter-from-class="scale-50 opacity-0"
|
|
28
|
+
leave-to-class="scale-50 opacity-0"
|
|
29
|
+
>
|
|
25
30
|
<button
|
|
26
31
|
v-if="open && collapsed"
|
|
27
|
-
class="
|
|
32
|
+
class="fixed bottom-4 left-4 z-50 flex size-14 cursor-pointer items-center justify-center rounded-full border border-default bg-default shadow-[0_10px_25px_rgb(0_0_0/0.3)] hover:opacity-90"
|
|
28
33
|
:aria-label="t('expand')"
|
|
29
34
|
data-testid="cookie-consent-fab"
|
|
30
35
|
@click="isManuallyExpanded = true"
|
|
31
36
|
>
|
|
32
37
|
<UIcon
|
|
33
|
-
name="
|
|
34
|
-
class="
|
|
38
|
+
:name="config.icon"
|
|
39
|
+
class="size-7 text-primary"
|
|
35
40
|
/>
|
|
36
41
|
</button>
|
|
37
42
|
</Transition>
|
|
38
43
|
|
|
39
|
-
<Transition
|
|
44
|
+
<Transition
|
|
45
|
+
enter-active-class="transition duration-300 ease-out"
|
|
46
|
+
leave-active-class="transition duration-300 ease-out"
|
|
47
|
+
enter-from-class="translate-y-full opacity-0"
|
|
48
|
+
leave-to-class="translate-y-full opacity-0"
|
|
49
|
+
>
|
|
40
50
|
<div
|
|
41
51
|
v-if="open && !collapsed"
|
|
42
|
-
class="
|
|
52
|
+
class="pointer-events-none fixed inset-x-0 bottom-0 z-50 flex justify-center"
|
|
43
53
|
role="dialog"
|
|
44
54
|
:aria-label="t('preferencesTitle')"
|
|
45
55
|
data-testid="cookie-consent-banner"
|
|
46
56
|
>
|
|
47
|
-
<div class="
|
|
48
|
-
<div class="
|
|
57
|
+
<div class="pointer-events-auto relative w-[92vw] rounded-t-2xl border border-primary/70 bg-default text-default shadow-[0_-8px_30px_rgb(0_0_0/0.25)]">
|
|
58
|
+
<div class="absolute -top-3.5 left-4 flex items-center justify-center rounded-full border border-primary/70 bg-default p-1">
|
|
49
59
|
<UIcon
|
|
50
|
-
name="
|
|
51
|
-
class="
|
|
60
|
+
:name="config.icon"
|
|
61
|
+
class="size-5 text-primary"
|
|
52
62
|
/>
|
|
53
63
|
</div>
|
|
54
|
-
<div class="
|
|
55
|
-
<p class="
|
|
64
|
+
<div class="flex flex-col items-center justify-between gap-4 px-4 pb-4 pt-5 sm:flex-row">
|
|
65
|
+
<p class="text-sm text-muted">
|
|
56
66
|
{{ t("description") }}
|
|
57
67
|
<NuxtLink
|
|
58
|
-
v-if="
|
|
59
|
-
:to="
|
|
60
|
-
class="
|
|
68
|
+
v-if="policyUrl"
|
|
69
|
+
:to="policyUrl"
|
|
70
|
+
class="underline"
|
|
61
71
|
>
|
|
62
72
|
{{ t("policy") }}
|
|
63
73
|
</NuxtLink>
|
|
64
74
|
</p>
|
|
65
|
-
<
|
|
66
|
-
<UButton
|
|
67
|
-
color="neutral"
|
|
68
|
-
variant="ghost"
|
|
69
|
-
:label="t('preferences')"
|
|
70
|
-
data-testid="cookie-consent-preferences"
|
|
71
|
-
@click="openPreferences"
|
|
72
|
-
/>
|
|
73
|
-
<UButton
|
|
74
|
-
color="neutral"
|
|
75
|
-
variant="outline"
|
|
76
|
-
:label="t('reject')"
|
|
77
|
-
data-testid="cookie-consent-reject"
|
|
78
|
-
@click="rejectAll"
|
|
79
|
-
/>
|
|
80
|
-
<UButton
|
|
81
|
-
color="primary"
|
|
82
|
-
:label="t('accept')"
|
|
83
|
-
data-testid="cookie-consent-accept"
|
|
84
|
-
@click="acceptAll"
|
|
85
|
-
/>
|
|
86
|
-
</div>
|
|
75
|
+
<CookieConsentBannerActions />
|
|
87
76
|
</div>
|
|
88
77
|
</div>
|
|
89
78
|
</div>
|
|
@@ -92,7 +81,3 @@ onMounted(() => {
|
|
|
92
81
|
<CookieConsentPreferences />
|
|
93
82
|
</ClientOnly>
|
|
94
83
|
</template>
|
|
95
|
-
|
|
96
|
-
<style scoped>
|
|
97
|
-
.cc-wrap{display:flex;inset:auto 0 0 0;justify-content:center;pointer-events:none;position:fixed;z-index:50}.cc-banner{border-radius:1rem 1rem 0 0;box-shadow:0 -8px 30px rgba(0,0,0,.25);color:var(--ui-text);pointer-events:auto;position:relative;width:92vw}.cc-badge,.cc-banner{background:var(--ui-bg);border:1px solid color-mix(in oklab,var(--ui-primary) 70%,transparent)}.cc-badge{align-items:center;border-radius:9999px;display:flex;justify-content:center;left:1rem;padding:.25rem;position:absolute;top:-.875rem}.cc-badge-icon{color:var(--ui-primary);height:1.25rem;width:1.25rem}.cc-content{align-items:center;display:flex;flex-direction:column;gap:1rem;justify-content:space-between;padding:1.25rem 1rem 1rem}.cc-text{color:var(--ui-text-muted);font-size:.875rem;margin:0}.cc-link{text-decoration:underline}.cc-actions{display:flex;flex-shrink:0;flex-wrap:wrap;gap:.5rem}@media (min-width:640px){.cc-content{flex-direction:row}}.cc-fab{align-items:center;background:var(--ui-bg);border:1px solid var(--ui-border);border-radius:9999px;bottom:1rem;box-shadow:0 10px 25px rgba(0,0,0,.3);cursor:pointer;display:flex;height:3.5rem;justify-content:center;left:1rem;position:fixed;width:3.5rem;z-index:50}.cc-fab:hover{opacity:.9}.cc-fab-icon{color:var(--ui-primary);height:1.75rem;width:1.75rem}.cookie-slide-enter-active,.cookie-slide-leave-active{transition:transform .3s ease,opacity .3s ease}.cookie-slide-enter-from,.cookie-slide-leave-to{opacity:0;transform:translateY(100%)}.cookie-fab-enter-active,.cookie-fab-leave-active{transition:opacity .2s ease,transform .2s ease}.cookie-fab-enter-from,.cookie-fab-leave-to{opacity:0;transform:scale(.5)}
|
|
98
|
-
</style>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const { acceptAll, rejectAll, openPreferences } = useCookieConsent();
|
|
3
|
+
const { t } = useConsentT();
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div class="flex shrink-0 flex-wrap gap-2">
|
|
8
|
+
<UButton
|
|
9
|
+
color="neutral"
|
|
10
|
+
variant="ghost"
|
|
11
|
+
:label="t('preferences')"
|
|
12
|
+
data-testid="cookie-consent-preferences"
|
|
13
|
+
@click="openPreferences"
|
|
14
|
+
/>
|
|
15
|
+
<UButton
|
|
16
|
+
color="neutral"
|
|
17
|
+
variant="outline"
|
|
18
|
+
:label="t('reject')"
|
|
19
|
+
data-testid="cookie-consent-reject"
|
|
20
|
+
@click="rejectAll"
|
|
21
|
+
/>
|
|
22
|
+
<UButton
|
|
23
|
+
color="primary"
|
|
24
|
+
:label="t('accept')"
|
|
25
|
+
data-testid="cookie-consent-accept"
|
|
26
|
+
@click="acceptAll"
|
|
27
|
+
/>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -6,14 +6,10 @@ const { t } = useConsentT();
|
|
|
6
6
|
<template>
|
|
7
7
|
<button
|
|
8
8
|
type="button"
|
|
9
|
-
:class="['
|
|
9
|
+
:class="['cursor-pointer', { underline: !$slots.default }]"
|
|
10
10
|
data-testid="cookie-consent-manage"
|
|
11
11
|
@click="openPreferences"
|
|
12
12
|
>
|
|
13
13
|
<slot>{{ t("manage") }}</slot>
|
|
14
14
|
</button>
|
|
15
15
|
</template>
|
|
16
|
-
|
|
17
|
-
<style scoped>
|
|
18
|
-
.cc-manage{background:none;border:0;color:inherit;cursor:pointer;padding:0}.cc-manage-text{text-decoration:underline}
|
|
19
|
-
</style>
|
|
@@ -19,26 +19,26 @@ watch(isPreferencesOpen, (isOpen) => {
|
|
|
19
19
|
>
|
|
20
20
|
<template #body>
|
|
21
21
|
<div
|
|
22
|
-
class="
|
|
22
|
+
class="flex flex-col gap-4"
|
|
23
23
|
data-testid="cookie-consent-preferences-dialog"
|
|
24
24
|
>
|
|
25
25
|
<div
|
|
26
26
|
v-for="category in categories"
|
|
27
27
|
:key="category.key"
|
|
28
|
-
class="
|
|
28
|
+
class="rounded-lg border border-default p-3"
|
|
29
29
|
>
|
|
30
|
-
<div class="
|
|
30
|
+
<div class="flex items-start justify-between gap-4">
|
|
31
31
|
<div>
|
|
32
|
-
<p class="
|
|
32
|
+
<p class="font-medium">
|
|
33
33
|
{{ label(category) }}
|
|
34
34
|
</p>
|
|
35
|
-
<p class="
|
|
35
|
+
<p class="text-sm text-muted">
|
|
36
36
|
{{ description(category) }}
|
|
37
37
|
</p>
|
|
38
38
|
</div>
|
|
39
39
|
<span
|
|
40
40
|
v-if="category.required"
|
|
41
|
-
class="
|
|
41
|
+
class="shrink-0 text-xs text-muted"
|
|
42
42
|
>{{ t("required") }}</span>
|
|
43
43
|
<USwitch
|
|
44
44
|
v-else
|
|
@@ -49,12 +49,12 @@ watch(isPreferencesOpen, (isOpen) => {
|
|
|
49
49
|
</div>
|
|
50
50
|
<details
|
|
51
51
|
v-if="category.cookies?.length"
|
|
52
|
-
class="
|
|
52
|
+
class="mt-2 text-sm"
|
|
53
53
|
>
|
|
54
|
-
<summary class="
|
|
54
|
+
<summary class="cursor-pointer text-muted">
|
|
55
55
|
{{ t("cookies") }} ({{ category.cookies.length }})
|
|
56
56
|
</summary>
|
|
57
|
-
<ul class="
|
|
57
|
+
<ul class="mt-1 flex list-none flex-col gap-1 p-0">
|
|
58
58
|
<li
|
|
59
59
|
v-for="cookie in category.cookies"
|
|
60
60
|
:key="cookie.name"
|
|
@@ -64,7 +64,7 @@ watch(isPreferencesOpen, (isOpen) => {
|
|
|
64
64
|
<span v-if="cookie.duration"> · {{ cookie.duration }}</span>
|
|
65
65
|
<span
|
|
66
66
|
v-if="cookie.purpose"
|
|
67
|
-
class="
|
|
67
|
+
class="block text-muted"
|
|
68
68
|
>{{ localize(cookie.purpose) }}</span>
|
|
69
69
|
</li>
|
|
70
70
|
</ul>
|
|
@@ -73,7 +73,7 @@ watch(isPreferencesOpen, (isOpen) => {
|
|
|
73
73
|
</div>
|
|
74
74
|
</template>
|
|
75
75
|
<template #footer>
|
|
76
|
-
<div class="
|
|
76
|
+
<div class="flex w-full flex-wrap justify-end gap-2">
|
|
77
77
|
<UButton
|
|
78
78
|
color="neutral"
|
|
79
79
|
variant="outline"
|
|
@@ -96,7 +96,3 @@ watch(isPreferencesOpen, (isOpen) => {
|
|
|
96
96
|
</template>
|
|
97
97
|
</UModal>
|
|
98
98
|
</template>
|
|
99
|
-
|
|
100
|
-
<style scoped>
|
|
101
|
-
.cc-list{display:flex;flex-direction:column;gap:1rem}.cc-item{border:1px solid var(--ui-border);border-radius:.5rem;padding:.75rem}.cc-row{align-items:flex-start;display:flex;gap:1rem;justify-content:space-between}.cc-label{font-weight:500;margin:0}.cc-desc{font-size:.875rem;margin:0}.cc-desc,.cc-purpose,.cc-required,.cc-summary{color:var(--ui-text-muted)}.cc-required{flex-shrink:0;font-size:.75rem}.cc-details{font-size:.875rem;margin-top:.5rem}.cc-summary{cursor:pointer}.cc-cookies{display:flex;flex-direction:column;gap:.25rem;list-style:none;margin:.25rem 0 0;padding:0}.cc-purpose{display:block}.cc-footer{display:flex;flex-wrap:wrap;gap:.5rem;justify-content:flex-end;width:100%}
|
|
102
|
-
</style>
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type { ConsentCategory, ConsentMessages,
|
|
1
|
+
import type { ConsentCategory, ConsentMessages, Translated } from '../../../types.js';
|
|
2
2
|
export declare function useConsentT(): {
|
|
3
3
|
locale: any;
|
|
4
|
+
/** The policy link target for the current locale, if `policyUrl` is set. */
|
|
5
|
+
policyUrl: any;
|
|
6
|
+
/** The `policyLabel` option wins over the translated `policy` text. */
|
|
4
7
|
t: (key: keyof ConsentMessages) => any;
|
|
5
8
|
label: (category: ConsentCategory) => string;
|
|
6
9
|
description: (category: ConsentCategory) => string;
|
|
7
|
-
localize: (value:
|
|
10
|
+
localize: (value: Translated | undefined, fallback?: string) => string;
|
|
8
11
|
};
|
|
@@ -21,6 +21,7 @@ const EN_CATEGORIES = {
|
|
|
21
21
|
export function useConsentT() {
|
|
22
22
|
const tm = useModuleT("cookieConsent");
|
|
23
23
|
const nuxtApp = useNuxtApp();
|
|
24
|
+
const { policyLabel, policyUrl } = useRuntimeConfig().public.cookieConsent ?? {};
|
|
24
25
|
const locale = computed(() => nuxtApp.$i18n?.locale?.value ?? "en");
|
|
25
26
|
function categoryText(category, field) {
|
|
26
27
|
const explicit = category[field];
|
|
@@ -29,7 +30,10 @@ export function useConsentT() {
|
|
|
29
30
|
}
|
|
30
31
|
return {
|
|
31
32
|
locale,
|
|
32
|
-
|
|
33
|
+
/** The policy link target for the current locale, if `policyUrl` is set. */
|
|
34
|
+
policyUrl: computed(() => localize(policyUrl, locale.value) || void 0),
|
|
35
|
+
/** The `policyLabel` option wins over the translated `policy` text. */
|
|
36
|
+
t: (key) => key === "policy" && policyLabel ? localize(policyLabel, locale.value) : tm(key, EN[key]),
|
|
33
37
|
label: (category) => categoryText(category, "label"),
|
|
34
38
|
description: (category) => categoryText(category, "description"),
|
|
35
39
|
localize: (value, fallback = "") => localize(value, locale.value, fallback)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { db, schema } from "@nuxthub/db";
|
|
2
2
|
import { eq } from "drizzle-orm";
|
|
3
3
|
export default defineEventHandler(async (event) => {
|
|
4
|
-
const
|
|
4
|
+
const user = await requireUser(event, { sessionOnly: true });
|
|
5
5
|
const [row] = await db.select({
|
|
6
6
|
acceptedAt: schema.users.cookieConsentAcceptedAt,
|
|
7
7
|
version: schema.users.cookieConsentVersion,
|
|
@@ -8,7 +8,7 @@ const bodySchema = z.object({
|
|
|
8
8
|
version: z.string()
|
|
9
9
|
});
|
|
10
10
|
export default defineEventHandler(async (event) => {
|
|
11
|
-
const
|
|
11
|
+
const user = await requireUser(event, { sessionOnly: true });
|
|
12
12
|
const config = getCookieConsentConfig(event);
|
|
13
13
|
const body = await readValidatedBody(event, bodySchema.parse);
|
|
14
14
|
if (body.version !== config.version) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConsentCategory, ConsentState } from '../../types.js';
|
|
1
|
+
import type { ConsentCategory, ConsentState, Translated } from '../../types.js';
|
|
2
2
|
export declare const DEFAULT_CATEGORIES: ConsentCategory[];
|
|
3
3
|
/** Merges categories by key: earlier labels win, cookies are concatenated, `required` is sticky. */
|
|
4
4
|
export declare function mergeCategories(...lists: ConsentCategory[][]): ConsentCategory[];
|
|
@@ -15,4 +15,4 @@ export declare function rejectAllChoices(categories: ConsentCategory[]): Record<
|
|
|
15
15
|
* The old app's `accepted` / `rejected` strings are migrated.
|
|
16
16
|
*/
|
|
17
17
|
export declare function normalizeConsent(raw: unknown, categories: ConsentCategory[], version: string, now?: number): ConsentState | null;
|
|
18
|
-
export declare function localize(value:
|
|
18
|
+
export declare function localize(value: Translated | undefined, locale: string, fallback?: string): string;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { extendCookieConsent, defineConsentCategory } from './extend.js';
|
|
2
2
|
export { DEFAULT_CATEGORIES, mergeCategories, computeVersion, buildConsent, normalizeConsent, acceptAllChoices, rejectAllChoices, localize, } from './consent.js';
|
|
3
|
-
export type { ConsentCategory, ConsentCookie, ConsentState,
|
|
3
|
+
export type { ConsentCategory, ConsentCookie, ConsentState, Translated } from '../../types.js';
|
package/dist/types.d.mts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { type Translated } from '@frigear-nu/ui/translated'
|
|
2
|
+
|
|
1
3
|
export { default } from './module.mjs'
|
|
2
4
|
|
|
3
|
-
export { type ConsentCategory, type ConsentCookie, type ConsentMessages, type ConsentState, type
|
|
5
|
+
export { type ConsentCategory, type ConsentCookie, type ConsentMessages, type ConsentState, type ModuleOptions, type PublicModuleOptions } from './module.mjs'
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frigear-nu/cookie-consent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "GDPR cookie consent banner with categories - modules contribute categories (extendCookieConsent), apps gate scripts on consent, and the choice is stored as proof on the user.",
|
|
5
|
+
"homepage": "https://docs.frigear.dev/modules/cookie-consent",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
7
8
|
"url": "git+https://github.com/frigear-nu/frigear-nu.git",
|
|
@@ -29,7 +30,8 @@
|
|
|
29
30
|
"playground"
|
|
30
31
|
],
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@frigear-nu/module-kit": "0.1.
|
|
33
|
+
"@frigear-nu/module-kit": "0.1.7",
|
|
34
|
+
"@frigear-nu/ui": "0.0.7",
|
|
33
35
|
"@libsql/client": "^0.18.0",
|
|
34
36
|
"@nuxt/kit": "^4.6.0",
|
|
35
37
|
"@vueuse/core": "^15.0.0",
|
|
@@ -38,6 +40,7 @@
|
|
|
38
40
|
"zod": "^4.6.5"
|
|
39
41
|
},
|
|
40
42
|
"peerDependencies": {
|
|
43
|
+
"@frigear-nu/auth": ">=0.0.1",
|
|
41
44
|
"@frigear-nu/users": ">=0.0.1",
|
|
42
45
|
"@nuxthub/core": "^0.10.8",
|
|
43
46
|
"@nuxt/ui": "^4.10.0"
|
|
@@ -49,12 +52,16 @@
|
|
|
49
52
|
"@frigear-nu/users": {
|
|
50
53
|
"optional": true
|
|
51
54
|
},
|
|
55
|
+
"@frigear-nu/auth": {
|
|
56
|
+
"optional": true
|
|
57
|
+
},
|
|
52
58
|
"@nuxthub/core": {
|
|
53
59
|
"optional": true
|
|
54
60
|
}
|
|
55
61
|
},
|
|
56
62
|
"devDependencies": {
|
|
57
|
-
"@frigear-nu/
|
|
63
|
+
"@frigear-nu/auth": "0.0.3",
|
|
64
|
+
"@frigear-nu/users": "1.0.9",
|
|
58
65
|
"@nuxt/devtools": "^3.4.2",
|
|
59
66
|
"@nuxt/eslint-config": "^1.17.0",
|
|
60
67
|
"@nuxt/module-builder": "^1.0.3",
|