@frigear-nu/cookie-consent 0.0.0-stage → 0.0.1

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.
Files changed (37) hide show
  1. package/dist/module.d.mts +128 -0
  2. package/dist/module.json +9 -0
  3. package/dist/module.mjs +61 -0
  4. package/dist/runtime/app/components/CookieConsentBanner.d.vue.ts +3 -0
  5. package/dist/runtime/app/components/CookieConsentBanner.vue +98 -0
  6. package/dist/runtime/app/components/CookieConsentBanner.vue.d.ts +3 -0
  7. package/dist/runtime/app/components/CookieConsentManage.d.vue.ts +13 -0
  8. package/dist/runtime/app/components/CookieConsentManage.vue +19 -0
  9. package/dist/runtime/app/components/CookieConsentManage.vue.d.ts +13 -0
  10. package/dist/runtime/app/components/CookieConsentPreferences.d.vue.ts +3 -0
  11. package/dist/runtime/app/components/CookieConsentPreferences.vue +102 -0
  12. package/dist/runtime/app/components/CookieConsentPreferences.vue.d.ts +3 -0
  13. package/dist/runtime/app/composables/index.d.ts +2 -0
  14. package/dist/runtime/app/composables/index.js +2 -0
  15. package/dist/runtime/app/composables/useConsentT.d.ts +8 -0
  16. package/dist/runtime/app/composables/useConsentT.js +37 -0
  17. package/dist/runtime/app/composables/useCookieConsent.d.ts +22 -0
  18. package/dist/runtime/app/composables/useCookieConsent.js +85 -0
  19. package/dist/runtime/i18n/lang/da.json +30 -0
  20. package/dist/runtime/i18n/lang/en.json +30 -0
  21. package/dist/runtime/server/db/schema.sqlite.d.ts +62 -0
  22. package/dist/runtime/server/db/schema.sqlite.js +9 -0
  23. package/dist/runtime/server/routes/cookie-consent/index.get.d.ts +2 -0
  24. package/dist/runtime/server/routes/cookie-consent/index.get.js +12 -0
  25. package/dist/runtime/server/routes/cookie-consent/index.post.d.ts +2 -0
  26. package/dist/runtime/server/routes/cookie-consent/index.post.js +30 -0
  27. package/dist/runtime/server/utils/consent.d.ts +8 -0
  28. package/dist/runtime/server/utils/consent.js +19 -0
  29. package/dist/runtime/utils/consent.d.ts +18 -0
  30. package/dist/runtime/utils/consent.js +61 -0
  31. package/dist/runtime/utils/extend.d.ts +11 -0
  32. package/dist/runtime/utils/extend.js +7 -0
  33. package/dist/runtime/utils/index.d.ts +3 -0
  34. package/dist/runtime/utils/index.js +11 -0
  35. package/dist/types.d.mts +3 -0
  36. package/package.json +89 -3
  37. package/README.md +0 -3
@@ -0,0 +1,128 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+
3
+ /** A string, or a map of locale code -> string (`{ da: '...', en: '...' }`). */
4
+ type Localized = string | Record<string, string>;
5
+ /** A cookie (or similar storage entry) set under a category - listed in the preferences dialog for GDPR transparency. */
6
+ interface ConsentCookie {
7
+ name: string;
8
+ provider?: string;
9
+ purpose?: Localized;
10
+ /** Human readable, e.g. `'1 year'` or `'Session'`. */
11
+ duration?: string;
12
+ }
13
+ interface ConsentCategory {
14
+ key: string;
15
+ /** Always on and not switchable (strictly necessary cookies). */
16
+ required?: boolean;
17
+ /** Pre-selected state in the preferences dialog. Never applied without the user saving. Default `false`. */
18
+ defaultEnabled?: boolean;
19
+ /** Defaults to the i18n key `cookieConsent.categories.<key>.label`. */
20
+ label?: Localized;
21
+ /** Defaults to the i18n key `cookieConsent.categories.<key>.description`. */
22
+ description?: Localized;
23
+ cookies?: ConsentCookie[];
24
+ }
25
+ interface ConsentMessages {
26
+ description: string;
27
+ accept: string;
28
+ reject: string;
29
+ preferences: string;
30
+ expand: string;
31
+ manage: string;
32
+ save: string;
33
+ preferencesTitle: string;
34
+ preferencesDescription: string;
35
+ required: string;
36
+ policy: string;
37
+ cookies: string;
38
+ }
39
+ interface ModuleOptions {
40
+ /** Name of the consent cookie. Default `cookie-consent`. */
41
+ cookieName?: string;
42
+ /** Cookie lifetime in seconds. Default 180 days. */
43
+ maxAge?: number;
44
+ /** Manual version. Changing it asks every visitor again. The category set is hashed into the version automatically. */
45
+ version?: string;
46
+ /** Categories to show. Defaults to `necessary` (required), `analytics` and `marketing`. Other modules add more via `extendCookieConsent`. */
47
+ categories?: ConsentCategory[];
48
+ /** Routes where the banner is hidden. Default `['/authorize']`. */
49
+ excludeRoutes?: string[];
50
+ /** Collapse the banner into a round button once scrolled this many px (`true` = 80, `false` = never). Default `true`. */
51
+ collapseOnScroll?: boolean | number;
52
+ /** Link to the cookie/privacy policy shown in the banner. */
53
+ policyUrl?: string;
54
+ /**
55
+ * Store the consent on the signed-in user (`@frigear-nu/users` + `@nuxthub/core` required).
56
+ * Default: `true` when `@frigear-nu/users` is installed.
57
+ */
58
+ persistToUser?: boolean;
59
+ }
60
+ interface PublicModuleOptions {
61
+ cookieName: string;
62
+ maxAge: number;
63
+ version: string;
64
+ categories: ConsentCategory[];
65
+ excludeRoutes: string[];
66
+ collapseThreshold: number | false;
67
+ policyUrl?: string;
68
+ persistToUser: boolean;
69
+ }
70
+ /** What is stored in the consent cookie and (for signed-in users) in the database. */
71
+ interface ConsentState {
72
+ /** Consent version the choice was made against. */
73
+ v: string;
74
+ /** Epoch milliseconds when the choice was made. */
75
+ ts: number;
76
+ categories: Record<string, boolean>;
77
+ }
78
+
79
+ declare module '@nuxt/schema' {
80
+ interface NuxtConfig {
81
+ cookieConsent?: ModuleOptions
82
+ }
83
+
84
+ interface NuxtOptions {
85
+ cookieConsent?: ModuleOptions & {
86
+ _categories?: ConsentCategory[]
87
+ }
88
+ }
89
+
90
+ interface PublicRuntimeConfig {
91
+ cookieConsent?: PublicModuleOptions
92
+ }
93
+
94
+ interface NuxtHooks {
95
+ /** Called every time `extendCookieConsent` registers categories. */
96
+ 'cookie-consent:extend': (categories: ConsentCategory[]) => void
97
+ /**
98
+ * Called once after every module has set up, with the final merged category list. Mutate the
99
+ * array to adjust it; the consent version is derived from it afterwards.
100
+ */
101
+ 'cookie-consent:resolved': (categories: ConsentCategory[]) => void
102
+ }
103
+ }
104
+
105
+ declare module '#app' {
106
+ interface RuntimeNuxtHooks {
107
+ /** Called on the client when the visitor saves a choice (accept, reject, preferences or withdraw). */
108
+ 'cookie-consent:change': (state: ConsentState | null, previous: ConsentState | null) => void
109
+ /** Called once on the client with the stored consent (or `null` if still undecided). */
110
+ 'cookie-consent:ready': (state: ConsentState | null) => void
111
+ }
112
+ }
113
+
114
+ declare module 'nitropack' {
115
+ interface NitroRuntimeHooks {
116
+ /** Called on the server after a signed-in user's consent was stored. */
117
+ 'cookie-consent:saved': (payload: {
118
+ userId: number | string
119
+ state: ConsentState
120
+ acceptedAt: number
121
+ }) => void | Promise<void>
122
+ }
123
+ }
124
+
125
+ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
126
+
127
+ export { _default as default };
128
+ export type { ConsentCategory, ConsentCookie, ConsentMessages, ConsentState, Localized, ModuleOptions, PublicModuleOptions };
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "@frigear-nu/cookie-consent",
3
+ "configKey": "cookieConsent",
4
+ "version": "0.0.1",
5
+ "builder": {
6
+ "@nuxt/module-builder": "1.0.3",
7
+ "unbuild": "unknown"
8
+ }
9
+ }
@@ -0,0 +1,61 @@
1
+ import { defineNuxtModule, createResolver, hasNuxtModule, addImports, addServerImports, addComponentsDir, addServerHandler } from '@nuxt/kit';
2
+ import { registerModule, registerLocales, extendDatabaseTable } from '@frigear-nu/module-kit/utils';
3
+ import { mergeCategories, DEFAULT_CATEGORIES, computeVersion } from '../dist/runtime/utils/consent.js';
4
+
5
+ const DEFAULT_MAX_AGE = 60 * 60 * 24 * 180;
6
+ const module$1 = defineNuxtModule({
7
+ meta: { name: "@frigear-nu/cookie-consent", configKey: "cookieConsent" },
8
+ defaults: {
9
+ cookieName: "cookie-consent",
10
+ maxAge: DEFAULT_MAX_AGE,
11
+ excludeRoutes: ["/authorize"],
12
+ collapseOnScroll: true
13
+ },
14
+ moduleDependencies: {
15
+ "@frigear-nu/module-kit": { version: ">=0.0.1" }
16
+ },
17
+ async setup(options, nuxt) {
18
+ registerModule("@frigear-nu/cookie-consent", nuxt);
19
+ const resolver = createResolver(import.meta.url);
20
+ registerLocales(nuxt, resolver.resolve("./runtime/i18n/lang"));
21
+ const persistToUser = (options.persistToUser ?? true) && hasNuxtModule("@frigear-nu/users");
22
+ addImports([
23
+ { name: "useCookieConsent", from: resolver.resolve("./runtime/app/composables/useCookieConsent") },
24
+ { name: "onCookieConsent", from: resolver.resolve("./runtime/app/composables/useCookieConsent") },
25
+ { name: "useConsentT", from: resolver.resolve("./runtime/app/composables/useConsentT") }
26
+ ]);
27
+ addServerImports([
28
+ { name: "getCookieConsent", from: resolver.resolve("./runtime/server/utils/consent") },
29
+ { name: "hasCookieConsent", from: resolver.resolve("./runtime/server/utils/consent") }
30
+ ]);
31
+ addComponentsDir({ path: resolver.resolve("./runtime/app/components") });
32
+ if (persistToUser) {
33
+ addServerHandler({ route: "/api/cookie-consent", method: "get", handler: resolver.resolve("./runtime/server/routes/cookie-consent/index.get") });
34
+ addServerHandler({ route: "/api/cookie-consent", method: "post", handler: resolver.resolve("./runtime/server/routes/cookie-consent/index.post") });
35
+ extendDatabaseTable(nuxt, {
36
+ name: "users",
37
+ resolver,
38
+ file: (dialect) => `./runtime/server/db/schema.${dialect}`
39
+ });
40
+ }
41
+ nuxt.hook("modules:done", async () => {
42
+ const categories = mergeCategories(
43
+ options.categories ?? DEFAULT_CATEGORIES,
44
+ nuxt.options.cookieConsent?._categories ?? []
45
+ );
46
+ await nuxt.callHook("cookie-consent:resolved", categories);
47
+ nuxt.options.runtimeConfig.public.cookieConsent = {
48
+ cookieName: options.cookieName,
49
+ maxAge: options.maxAge,
50
+ version: computeVersion(options.version, categories),
51
+ categories,
52
+ excludeRoutes: options.excludeRoutes ?? [],
53
+ collapseThreshold: options.collapseOnScroll === false ? false : typeof options.collapseOnScroll === "number" ? options.collapseOnScroll : 80,
54
+ policyUrl: options.policyUrl,
55
+ persistToUser
56
+ };
57
+ });
58
+ }
59
+ });
60
+
61
+ export { module$1 as default };
@@ -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,98 @@
1
+ <script setup>
2
+ import { useWindowScroll } from "@vueuse/core";
3
+ const { config, hasResponded, acceptAll, rejectAll, openPreferences, hydrateFromUser } = useCookieConsent();
4
+ const { t } = useConsentT();
5
+ const route = useRoute();
6
+ const excluded = computed(() => config.excludeRoutes.includes(route.path));
7
+ const open = computed(() => !hasResponded.value && !excluded.value);
8
+ const { y } = useWindowScroll();
9
+ const scrolledDown = computed(() => config.collapseThreshold !== false && y.value > config.collapseThreshold);
10
+ const isManuallyExpanded = ref(false);
11
+ const collapsed = computed(() => scrolledDown.value && !isManuallyExpanded.value);
12
+ watch(scrolledDown, (isDown) => {
13
+ if (!isDown) isManuallyExpanded.value = false;
14
+ });
15
+ onMounted(() => {
16
+ hydrateFromUser();
17
+ if (config.persistToUser) watch(useUserSession().loggedIn, hydrateFromUser);
18
+ useNuxtApp().callHook("cookie-consent:ready", useCookieConsent().consent.value);
19
+ });
20
+ </script>
21
+
22
+ <template>
23
+ <ClientOnly>
24
+ <Transition name="cookie-fab">
25
+ <button
26
+ v-if="open && collapsed"
27
+ class="cc-fab"
28
+ :aria-label="t('expand')"
29
+ data-testid="cookie-consent-fab"
30
+ @click="isManuallyExpanded = true"
31
+ >
32
+ <UIcon
33
+ name="i-lucide-cookie"
34
+ class="cc-fab-icon"
35
+ />
36
+ </button>
37
+ </Transition>
38
+
39
+ <Transition name="cookie-slide">
40
+ <div
41
+ v-if="open && !collapsed"
42
+ class="cc-wrap"
43
+ role="dialog"
44
+ :aria-label="t('preferencesTitle')"
45
+ data-testid="cookie-consent-banner"
46
+ >
47
+ <div class="cc-banner">
48
+ <div class="cc-badge">
49
+ <UIcon
50
+ name="i-lucide-cookie"
51
+ class="cc-badge-icon"
52
+ />
53
+ </div>
54
+ <div class="cc-content">
55
+ <p class="cc-text">
56
+ {{ t("description") }}
57
+ <NuxtLink
58
+ v-if="config.policyUrl"
59
+ :to="config.policyUrl"
60
+ class="cc-link"
61
+ >
62
+ {{ t("policy") }}
63
+ </NuxtLink>
64
+ </p>
65
+ <div class="cc-actions">
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>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ </Transition>
91
+
92
+ <CookieConsentPreferences />
93
+ </ClientOnly>
94
+ </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,13 @@
1
+ declare var __VLS_1: {};
2
+ type __VLS_Slots = {} & {
3
+ default?: (props: typeof __VLS_1) => any;
4
+ };
5
+ declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
9
+ type __VLS_WithSlots<T, S> = T & {
10
+ new (): {
11
+ $slots: S;
12
+ };
13
+ };
@@ -0,0 +1,19 @@
1
+ <script setup>
2
+ const { openPreferences } = useCookieConsent();
3
+ const { t } = useConsentT();
4
+ </script>
5
+
6
+ <template>
7
+ <button
8
+ type="button"
9
+ :class="['cc-manage', { 'cc-manage-text': !$slots.default }]"
10
+ data-testid="cookie-consent-manage"
11
+ @click="openPreferences"
12
+ >
13
+ <slot>{{ t("manage") }}</slot>
14
+ </button>
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>
@@ -0,0 +1,13 @@
1
+ declare var __VLS_1: {};
2
+ type __VLS_Slots = {} & {
3
+ default?: (props: typeof __VLS_1) => any;
4
+ };
5
+ declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
6
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
7
+ declare const _default: typeof __VLS_export;
8
+ export default _default;
9
+ type __VLS_WithSlots<T, S> = T & {
10
+ new (): {
11
+ $slots: S;
12
+ };
13
+ };
@@ -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,102 @@
1
+ <script setup>
2
+ const { categories, consent, isPreferencesOpen, save, acceptAll, rejectAll } = useCookieConsent();
3
+ const { t, label, description, localize } = useConsentT();
4
+ const choices = ref({});
5
+ watch(isPreferencesOpen, (isOpen) => {
6
+ if (!isOpen) return;
7
+ choices.value = Object.fromEntries(categories.map((category) => [
8
+ category.key,
9
+ category.required ? true : consent.value?.categories[category.key] ?? category.defaultEnabled ?? false
10
+ ]));
11
+ }, { immediate: true });
12
+ </script>
13
+
14
+ <template>
15
+ <UModal
16
+ v-model:open="isPreferencesOpen"
17
+ :title="t('preferencesTitle')"
18
+ :description="t('preferencesDescription')"
19
+ >
20
+ <template #body>
21
+ <div
22
+ class="cc-list"
23
+ data-testid="cookie-consent-preferences-dialog"
24
+ >
25
+ <div
26
+ v-for="category in categories"
27
+ :key="category.key"
28
+ class="cc-item"
29
+ >
30
+ <div class="cc-row">
31
+ <div>
32
+ <p class="cc-label">
33
+ {{ label(category) }}
34
+ </p>
35
+ <p class="cc-desc">
36
+ {{ description(category) }}
37
+ </p>
38
+ </div>
39
+ <span
40
+ v-if="category.required"
41
+ class="cc-required"
42
+ >{{ t("required") }}</span>
43
+ <USwitch
44
+ v-else
45
+ v-model="choices[category.key]"
46
+ :aria-label="label(category)"
47
+ :data-testid="`cookie-consent-toggle-${category.key}`"
48
+ />
49
+ </div>
50
+ <details
51
+ v-if="category.cookies?.length"
52
+ class="cc-details"
53
+ >
54
+ <summary class="cc-summary">
55
+ {{ t("cookies") }} ({{ category.cookies.length }})
56
+ </summary>
57
+ <ul class="cc-cookies">
58
+ <li
59
+ v-for="cookie in category.cookies"
60
+ :key="cookie.name"
61
+ >
62
+ <code>{{ cookie.name }}</code>
63
+ <span v-if="cookie.provider"> · {{ cookie.provider }}</span>
64
+ <span v-if="cookie.duration"> · {{ cookie.duration }}</span>
65
+ <span
66
+ v-if="cookie.purpose"
67
+ class="cc-purpose"
68
+ >{{ localize(cookie.purpose) }}</span>
69
+ </li>
70
+ </ul>
71
+ </details>
72
+ </div>
73
+ </div>
74
+ </template>
75
+ <template #footer>
76
+ <div class="cc-footer">
77
+ <UButton
78
+ color="neutral"
79
+ variant="outline"
80
+ :label="t('reject')"
81
+ @click="rejectAll"
82
+ />
83
+ <UButton
84
+ color="neutral"
85
+ variant="outline"
86
+ :label="t('accept')"
87
+ @click="acceptAll"
88
+ />
89
+ <UButton
90
+ color="primary"
91
+ :label="t('save')"
92
+ data-testid="cookie-consent-save"
93
+ @click="save(choices)"
94
+ />
95
+ </div>
96
+ </template>
97
+ </UModal>
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>
@@ -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,2 @@
1
+ export { useCookieConsent, onCookieConsent } from './useCookieConsent.js';
2
+ export { useConsentT } from './useConsentT.js';
@@ -0,0 +1,2 @@
1
+ export { useCookieConsent, onCookieConsent } from "./useCookieConsent.js";
2
+ export { useConsentT } from "./useConsentT.js";
@@ -0,0 +1,8 @@
1
+ import type { ConsentCategory, ConsentMessages, Localized } from '../../../types.js';
2
+ export declare function useConsentT(): {
3
+ locale: any;
4
+ t: (key: keyof ConsentMessages) => any;
5
+ label: (category: ConsentCategory) => string;
6
+ description: (category: ConsentCategory) => string;
7
+ localize: (value: Localized | undefined, fallback?: string) => string;
8
+ };
@@ -0,0 +1,37 @@
1
+ import { localize } from "../../utils/consent.js";
2
+ const EN = {
3
+ description: "We use cookies to improve your experience. Choose which categories you allow.",
4
+ accept: "Accept all",
5
+ reject: "Reject all",
6
+ preferences: "Preferences",
7
+ expand: "Show cookie consent",
8
+ manage: "Manage cookies",
9
+ save: "Save choices",
10
+ preferencesTitle: "Cookie preferences",
11
+ preferencesDescription: "Choose which cookies you allow. You can change this at any time.",
12
+ required: "Always on",
13
+ policy: "Read more",
14
+ cookies: "Cookies"
15
+ };
16
+ const EN_CATEGORIES = {
17
+ necessary: { label: "Necessary", description: "Required for the site to work, such as keeping you signed in. Cannot be disabled." },
18
+ analytics: { label: "Analytics", description: "Helps us understand how the site is used so we can improve it." },
19
+ marketing: { label: "Marketing", description: "Used for social media integrations and relevant content." }
20
+ };
21
+ export function useConsentT() {
22
+ const tm = useModuleT("cookieConsent");
23
+ const nuxtApp = useNuxtApp();
24
+ const locale = computed(() => nuxtApp.$i18n?.locale?.value ?? "en");
25
+ function categoryText(category, field) {
26
+ const explicit = category[field];
27
+ if (explicit) return localize(explicit, locale.value);
28
+ return tm(`categories.${category.key}.${field}`, EN_CATEGORIES[category.key]?.[field] ?? (field === "label" ? category.key : ""));
29
+ }
30
+ return {
31
+ locale,
32
+ t: (key) => tm(key, EN[key]),
33
+ label: (category) => categoryText(category, "label"),
34
+ description: (category) => categoryText(category, "description"),
35
+ localize: (value, fallback = "") => localize(value, locale.value, fallback)
36
+ };
37
+ }
@@ -0,0 +1,22 @@
1
+ import type { ConsentState } from '../../../types.js';
2
+ export declare function useCookieConsent(): {
3
+ config: any;
4
+ categories: any;
5
+ consent: any;
6
+ hasResponded: any;
7
+ isAllowed: (key: string) => boolean;
8
+ acceptAll: () => Promise<ConsentState>;
9
+ rejectAll: () => Promise<ConsentState>;
10
+ save: (choices: Record<string, boolean>) => Promise<ConsentState>;
11
+ reset: () => Promise<void>;
12
+ hydrateFromUser: () => Promise<void>;
13
+ isPreferencesOpen: any;
14
+ openPreferences: () => void;
15
+ } | undefined;
16
+ /**
17
+ * Runs `callback` once the visitor has granted `category` - immediately if they already did, or as
18
+ * soon as they do. The optional return value is called when consent is withdrawn.
19
+ *
20
+ * onCookieConsent("analytics", () => { startTracking(); return () => stopTracking() })
21
+ */
22
+ export declare function onCookieConsent(category: string, callback: () => (() => void) | undefined): any;
@@ -0,0 +1,85 @@
1
+ import { acceptAllChoices, buildConsent, normalizeConsent, rejectAllChoices } from "../../utils/consent.js";
2
+ const createCookieConsent = (nuxtApp) => {
3
+ const config = useRuntimeConfig().public.cookieConsent;
4
+ const cookie = useCookie(config.cookieName, {
5
+ maxAge: config.maxAge,
6
+ path: "/",
7
+ sameSite: "lax",
8
+ secure: !import.meta.dev,
9
+ default: () => null
10
+ });
11
+ const consent = computed(() => normalizeConsent(cookie.value, config.categories, config.version));
12
+ const hasResponded = computed(() => !!consent.value);
13
+ const categories = config.categories;
14
+ const isPreferencesOpen = useState("cookie-consent:preferences", () => false);
15
+ const isAllowed = (key) => consent.value?.categories[key] === true;
16
+ async function persistToUser(state) {
17
+ if (!config.persistToUser) return;
18
+ const { loggedIn } = useUserSession();
19
+ if (!loggedIn.value) return;
20
+ try {
21
+ await $fetch("/api/cookie-consent", { method: "POST", body: { categories: state.categories, version: state.v } });
22
+ } catch (error) {
23
+ console.warn("[cookie-consent] Failed to store consent on the user.", error);
24
+ }
25
+ }
26
+ async function save(choices) {
27
+ const previous = consent.value;
28
+ const state = buildConsent(categories, config.version, choices);
29
+ cookie.value = state;
30
+ isPreferencesOpen.value = false;
31
+ await nuxtApp.callHook("cookie-consent:change", state, previous);
32
+ await persistToUser(state);
33
+ return state;
34
+ }
35
+ const acceptAll = () => save(acceptAllChoices(categories));
36
+ const rejectAll = () => save(rejectAllChoices(categories));
37
+ async function reset() {
38
+ const previous = consent.value;
39
+ const state = buildConsent(categories, config.version, rejectAllChoices(categories));
40
+ cookie.value = null;
41
+ await nuxtApp.callHook("cookie-consent:change", null, previous);
42
+ await persistToUser(state);
43
+ }
44
+ async function hydrateFromUser() {
45
+ if (!config.persistToUser || hasResponded.value) return;
46
+ const { loggedIn } = useUserSession();
47
+ if (!loggedIn.value) return;
48
+ try {
49
+ const { consent: stored } = await $fetch("/api/cookie-consent");
50
+ const state = normalizeConsent(stored, config.categories, config.version, stored?.ts);
51
+ if (state) {
52
+ cookie.value = state;
53
+ await nuxtApp.callHook("cookie-consent:change", state, null);
54
+ }
55
+ } catch {
56
+ }
57
+ }
58
+ const openPreferences = () => {
59
+ isPreferencesOpen.value = true;
60
+ };
61
+ return { config, categories, consent, hasResponded, isAllowed, acceptAll, rejectAll, save, reset, hydrateFromUser, isPreferencesOpen, openPreferences };
62
+ };
63
+ const instances = /* @__PURE__ */ new WeakMap();
64
+ export function useCookieConsent() {
65
+ const nuxtApp = useNuxtApp();
66
+ let instance = instances.get(nuxtApp);
67
+ if (!instance) {
68
+ instance = nuxtApp.runWithContext(() => effectScope(true).run(() => createCookieConsent(nuxtApp)));
69
+ instances.set(nuxtApp, instance);
70
+ }
71
+ return instance;
72
+ }
73
+ export function onCookieConsent(category, callback) {
74
+ const { isAllowed } = useCookieConsent();
75
+ let cleanup;
76
+ const stop = watch(() => isAllowed(category), (allowed) => {
77
+ if (allowed && !cleanup) cleanup = callback();
78
+ else if (!allowed && cleanup) {
79
+ cleanup();
80
+ cleanup = void 0;
81
+ }
82
+ }, { immediate: true, flush: "sync" });
83
+ if (getCurrentScope()) onScopeDispose(stop);
84
+ return stop;
85
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "cookieConsent": {
3
+ "description": "Vi bruger cookies for at forbedre din oplevelse. Vælg hvilke kategorier du tillader.",
4
+ "accept": "Acceptér alle",
5
+ "reject": "Afvis alle",
6
+ "preferences": "Indstillinger",
7
+ "expand": "Vis cookie-samtykke",
8
+ "manage": "Administrér cookies",
9
+ "save": "Gem valg",
10
+ "preferencesTitle": "Cookie-indstillinger",
11
+ "preferencesDescription": "Vælg hvilke cookies du tillader. Du kan til enhver tid ændre det.",
12
+ "required": "Altid aktiv",
13
+ "policy": "Læs mere",
14
+ "cookies": "Cookies",
15
+ "categories": {
16
+ "necessary": {
17
+ "label": "Nødvendige",
18
+ "description": "Påkrævet for at siden fungerer, f.eks. at holde dig logget ind. Kan ikke slås fra."
19
+ },
20
+ "analytics": {
21
+ "label": "Statistik",
22
+ "description": "Hjælper os med at forstå, hvordan siden bruges, så vi kan forbedre den."
23
+ },
24
+ "marketing": {
25
+ "label": "Markedsføring",
26
+ "description": "Bruges til integration med sociale medier og relevant indhold."
27
+ }
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "cookieConsent": {
3
+ "description": "We use cookies to improve your experience. Choose which categories you allow.",
4
+ "accept": "Accept all",
5
+ "reject": "Reject all",
6
+ "preferences": "Preferences",
7
+ "expand": "Show cookie consent",
8
+ "manage": "Manage cookies",
9
+ "save": "Save choices",
10
+ "preferencesTitle": "Cookie preferences",
11
+ "preferencesDescription": "Choose which cookies you allow. You can change this at any time.",
12
+ "required": "Always on",
13
+ "policy": "Read more",
14
+ "cookies": "Cookies",
15
+ "categories": {
16
+ "necessary": {
17
+ "label": "Necessary",
18
+ "description": "Required for the site to work, such as keeping you signed in. Cannot be disabled."
19
+ },
20
+ "analytics": {
21
+ "label": "Analytics",
22
+ "description": "Helps us understand how the site is used so we can improve it."
23
+ },
24
+ "marketing": {
25
+ "label": "Marketing",
26
+ "description": "Used for social media integrations and relevant content."
27
+ }
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,62 @@
1
+ export declare const users: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
2
+ name: "users";
3
+ schema: undefined;
4
+ columns: {
5
+ cookieConsentAcceptedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
6
+ name: "cookie_consent_accepted_at";
7
+ tableName: "users";
8
+ dataType: "number";
9
+ columnType: "SQLiteInteger";
10
+ data: number;
11
+ driverParam: number;
12
+ notNull: false;
13
+ hasDefault: false;
14
+ isPrimaryKey: false;
15
+ isAutoincrement: false;
16
+ hasRuntimeDefault: false;
17
+ enumValues: undefined;
18
+ baseColumn: never;
19
+ identity: undefined;
20
+ generated: undefined;
21
+ }, {}, {}>;
22
+ cookieConsentVersion: import("drizzle-orm/sqlite-core").SQLiteColumn<{
23
+ name: "cookie_consent_version";
24
+ tableName: "users";
25
+ dataType: "string";
26
+ columnType: "SQLiteText";
27
+ data: string;
28
+ driverParam: string;
29
+ notNull: false;
30
+ hasDefault: false;
31
+ isPrimaryKey: false;
32
+ isAutoincrement: false;
33
+ hasRuntimeDefault: false;
34
+ enumValues: [string, ...string[]];
35
+ baseColumn: never;
36
+ identity: undefined;
37
+ generated: undefined;
38
+ }, {}, {
39
+ length: number | undefined;
40
+ }>;
41
+ cookieConsentCategories: import("drizzle-orm/sqlite-core").SQLiteColumn<{
42
+ name: "cookie_consent_categories";
43
+ tableName: "users";
44
+ dataType: "string";
45
+ columnType: "SQLiteText";
46
+ data: string;
47
+ driverParam: string;
48
+ notNull: false;
49
+ hasDefault: false;
50
+ isPrimaryKey: false;
51
+ isAutoincrement: false;
52
+ hasRuntimeDefault: false;
53
+ enumValues: [string, ...string[]];
54
+ baseColumn: never;
55
+ identity: undefined;
56
+ generated: undefined;
57
+ }, {}, {
58
+ length: number | undefined;
59
+ }>;
60
+ };
61
+ dialect: "sqlite";
62
+ }>;
@@ -0,0 +1,9 @@
1
+ import { sqliteTable, integer, text } from "drizzle-orm/sqlite-core";
2
+ export const users = sqliteTable("users", {
3
+ // When the user last responded to the cookie banner (epoch ms) - proof of consent for GDPR.
4
+ cookieConsentAcceptedAt: integer("cookie_consent_accepted_at"),
5
+ // The consent version (manual version + category-set hash) the choice was made against.
6
+ cookieConsentVersion: text("cookie_consent_version"),
7
+ // JSON of exactly what was granted, e.g. {"necessary":true,"analytics":false}.
8
+ cookieConsentCategories: text("cookie_consent_categories")
9
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import { db, schema } from "@nuxthub/db";
2
+ import { eq } from "drizzle-orm";
3
+ export default defineEventHandler(async (event) => {
4
+ const { user } = await requireUserSession(event);
5
+ const [row] = await db.select({
6
+ acceptedAt: schema.users.cookieConsentAcceptedAt,
7
+ version: schema.users.cookieConsentVersion,
8
+ categories: schema.users.cookieConsentCategories
9
+ }).from(schema.users).where(eq(schema.users.id, user.id));
10
+ if (!row?.acceptedAt || !row.version || !row.categories) return { consent: null };
11
+ return { consent: { v: row.version, ts: row.acceptedAt, categories: JSON.parse(row.categories) } };
12
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,30 @@
1
+ import { db, schema } from "@nuxthub/db";
2
+ import { eq } from "drizzle-orm";
3
+ import { z } from "zod";
4
+ import { buildConsent } from "../../../utils/consent.js";
5
+ import { getCookieConsentConfig } from "../../utils/consent.js";
6
+ const bodySchema = z.object({
7
+ categories: z.record(z.string(), z.boolean()),
8
+ version: z.string()
9
+ });
10
+ export default defineEventHandler(async (event) => {
11
+ const { user } = await requireUserSession(event);
12
+ const config = getCookieConsentConfig(event);
13
+ const body = await readValidatedBody(event, bodySchema.parse);
14
+ if (body.version !== config.version) {
15
+ throw createError({ statusCode: 409, statusMessage: "Outdated cookie consent version." });
16
+ }
17
+ const known = new Set(config.categories.map((category) => category.key));
18
+ const unknown = Object.keys(body.categories).filter((key) => !known.has(key));
19
+ if (unknown.length) {
20
+ throw createError({ statusCode: 400, statusMessage: `Unknown categories: ${unknown.join(", ")}` });
21
+ }
22
+ const state = buildConsent(config.categories, config.version, body.categories);
23
+ await db.update(schema.users).set({
24
+ cookieConsentAcceptedAt: state.ts,
25
+ cookieConsentVersion: state.v,
26
+ cookieConsentCategories: JSON.stringify(state.categories)
27
+ }).where(eq(schema.users.id, user.id));
28
+ await useNitroApp().hooks.callHook("cookie-consent:saved", { userId: user.id, state, acceptedAt: state.ts });
29
+ return state;
30
+ });
@@ -0,0 +1,8 @@
1
+ import type { H3Event } from 'h3';
2
+ import type { ConsentState, PublicModuleOptions } from '../../../types.js';
3
+ /** The resolved public module options (categories, version, cookie name, ...). */
4
+ export declare function getCookieConsentConfig(event: H3Event): PublicModuleOptions | undefined;
5
+ /** Reads the visitor's current consent from the request cookie (`null` = undecided / outdated). */
6
+ export declare function getCookieConsent(event: H3Event): ConsentState | null;
7
+ /** Whether the visitor granted `category` - use to gate tracking in SSR / API code. */
8
+ export declare function hasCookieConsent(event: H3Event, category: string): boolean;
@@ -0,0 +1,19 @@
1
+ import { normalizeConsent } from "../../utils/consent.js";
2
+ export function getCookieConsentConfig(event) {
3
+ return useRuntimeConfig(event).public.cookieConsent;
4
+ }
5
+ export function getCookieConsent(event) {
6
+ const config = getCookieConsentConfig(event);
7
+ if (!config) return null;
8
+ const raw = getCookie(event, config.cookieName);
9
+ if (!raw) return null;
10
+ let parsed = raw;
11
+ try {
12
+ parsed = JSON.parse(raw);
13
+ } catch {
14
+ }
15
+ return normalizeConsent(parsed, config.categories, config.version);
16
+ }
17
+ export function hasCookieConsent(event, category) {
18
+ return getCookieConsent(event)?.categories[category] === true;
19
+ }
@@ -0,0 +1,18 @@
1
+ import type { ConsentCategory, ConsentState } from '../../types.js';
2
+ export declare const DEFAULT_CATEGORIES: ConsentCategory[];
3
+ /** Merges categories by key: earlier labels win, cookies are concatenated, `required` is sticky. */
4
+ export declare function mergeCategories(...lists: ConsentCategory[][]): ConsentCategory[];
5
+ /** Small stable string hash (djb2) - used to derive the consent version from the category set. */
6
+ export declare function hashString(input: string): string;
7
+ export declare function computeVersion(manualVersion: string | undefined, categories: ConsentCategory[]): string;
8
+ /** Builds a state: required categories are always granted, unknown keys are dropped. */
9
+ export declare function buildConsent(categories: ConsentCategory[], version: string, choices: Record<string, boolean>, now?: number): ConsentState;
10
+ export declare function acceptAllChoices(categories: ConsentCategory[]): Record<string, boolean>;
11
+ export declare function rejectAllChoices(categories: ConsentCategory[]): Record<string, boolean>;
12
+ /**
13
+ * Turns whatever the cookie holds into a valid state for the *current* version, or `null` when the
14
+ * visitor must be asked again (nothing stored, malformed, or made against another version).
15
+ * The old app's `accepted` / `rejected` strings are migrated.
16
+ */
17
+ export declare function normalizeConsent(raw: unknown, categories: ConsentCategory[], version: string, now?: number): ConsentState | null;
18
+ export declare function localize(value: string | Record<string, string> | undefined, locale: string, fallback?: string): string;
@@ -0,0 +1,61 @@
1
+ export const DEFAULT_CATEGORIES = [
2
+ { key: "necessary", required: true },
3
+ { key: "analytics" },
4
+ { key: "marketing" }
5
+ ];
6
+ export function mergeCategories(...lists) {
7
+ const byKey = /* @__PURE__ */ new Map();
8
+ for (const category of lists.flat()) {
9
+ const existing = byKey.get(category.key);
10
+ if (!existing) {
11
+ byKey.set(category.key, { ...category, cookies: [...category.cookies ?? []] });
12
+ continue;
13
+ }
14
+ existing.required = existing.required || category.required;
15
+ existing.defaultEnabled = existing.defaultEnabled ?? category.defaultEnabled;
16
+ existing.label = existing.label ?? category.label;
17
+ existing.description = existing.description ?? category.description;
18
+ const known = new Set((existing.cookies ?? []).map((cookie) => cookie.name));
19
+ existing.cookies = [...existing.cookies ?? [], ...(category.cookies ?? []).filter((cookie) => !known.has(cookie.name))];
20
+ }
21
+ return [...byKey.values()];
22
+ }
23
+ export function hashString(input) {
24
+ let hash = 5381;
25
+ for (let index = 0; index < input.length; index++) {
26
+ hash = (hash << 5) + hash + input.charCodeAt(index) | 0;
27
+ }
28
+ return (hash >>> 0).toString(36);
29
+ }
30
+ export function computeVersion(manualVersion, categories) {
31
+ const fingerprint = categories.map((category) => `${category.key}:${category.required ? 1 : 0}:${(category.cookies ?? []).map((cookie) => cookie.name).sort().join(",")}`).sort().join("|");
32
+ return `${manualVersion ?? "1"}-${hashString(fingerprint)}`;
33
+ }
34
+ export function buildConsent(categories, version, choices, now = Date.now()) {
35
+ return {
36
+ v: version,
37
+ ts: now,
38
+ categories: Object.fromEntries(
39
+ categories.map((category) => [category.key, category.required ? true : choices[category.key] === true])
40
+ )
41
+ };
42
+ }
43
+ export function acceptAllChoices(categories) {
44
+ return Object.fromEntries(categories.map((category) => [category.key, true]));
45
+ }
46
+ export function rejectAllChoices(categories) {
47
+ return Object.fromEntries(categories.map((category) => [category.key, !!category.required]));
48
+ }
49
+ export function normalizeConsent(raw, categories, version, now = Date.now()) {
50
+ if (raw === "accepted") return buildConsent(categories, version, acceptAllChoices(categories), now);
51
+ if (raw === "rejected") return buildConsent(categories, version, rejectAllChoices(categories), now);
52
+ if (!raw || typeof raw !== "object") return null;
53
+ const state = raw;
54
+ if (state.v !== version || typeof state.ts !== "number" || !state.categories || typeof state.categories !== "object") return null;
55
+ return buildConsent(categories, version, state.categories, state.ts);
56
+ }
57
+ export function localize(value, locale, fallback = "") {
58
+ if (!value) return fallback;
59
+ if (typeof value === "string") return value;
60
+ return value[locale] ?? value[locale.split("-")[0]] ?? value.en ?? Object.values(value)[0] ?? fallback;
61
+ }
@@ -0,0 +1,11 @@
1
+ import type { Nuxt } from '@nuxt/schema';
2
+ import type { ConsentCategory } from '../../types.js';
3
+ /**
4
+ * Lets another module contribute consent categories (or cookies to an existing one) during its own
5
+ * `setup()`. Registrations are merged by `key` once every module has set up, so order doesn't
6
+ * matter. Adding a category or cookie changes the consent version and re-prompts visitors.
7
+ */
8
+ export declare function extendCookieConsent(nuxt: Nuxt, extension: {
9
+ categories: ConsentCategory[];
10
+ }): void;
11
+ export declare const defineConsentCategory: <T extends ConsentCategory>(category: T) => T;
@@ -0,0 +1,7 @@
1
+ export function extendCookieConsent(nuxt, extension) {
2
+ nuxt.options.cookieConsent ||= {};
3
+ nuxt.options.cookieConsent._categories ||= [];
4
+ nuxt.options.cookieConsent._categories.push(...extension.categories);
5
+ void nuxt.callHook("cookie-consent:extend", extension.categories);
6
+ }
7
+ export const defineConsentCategory = (category) => category;
@@ -0,0 +1,3 @@
1
+ export { extendCookieConsent, defineConsentCategory } from './extend.js';
2
+ export { DEFAULT_CATEGORIES, mergeCategories, computeVersion, buildConsent, normalizeConsent, acceptAllChoices, rejectAllChoices, localize, } from './consent.js';
3
+ export type { ConsentCategory, ConsentCookie, ConsentState, Localized } from '../../types.js';
@@ -0,0 +1,11 @@
1
+ export { extendCookieConsent, defineConsentCategory } from "./extend.js";
2
+ export {
3
+ DEFAULT_CATEGORIES,
4
+ mergeCategories,
5
+ computeVersion,
6
+ buildConsent,
7
+ normalizeConsent,
8
+ acceptAllChoices,
9
+ rejectAllChoices,
10
+ localize
11
+ } from "./consent.js";
@@ -0,0 +1,3 @@
1
+ export { default } from './module.mjs'
2
+
3
+ export { type ConsentCategory, type ConsentCookie, type ConsentMessages, type ConsentState, type Localized, type ModuleOptions, type PublicModuleOptions } from './module.mjs'
package/package.json CHANGED
@@ -1,6 +1,92 @@
1
1
  {
2
2
  "name": "@frigear-nu/cookie-consent",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.1",
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
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/frigear-nu/frigear-nu.git",
8
+ "directory": "packages/cookie-consent"
9
+ },
10
+ "license": "MIT",
11
+ "type": "module",
12
+ "main": "./dist/module.mjs",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/types.d.mts",
16
+ "import": "./dist/module.mjs",
17
+ "default": "./dist/module.mjs"
18
+ },
19
+ "./utils": {
20
+ "types": "./dist/runtime/utils/index.d.ts",
21
+ "import": "./dist/runtime/utils/index.js",
22
+ "default": "./dist/runtime/utils/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "workspaces": [
29
+ "playground"
30
+ ],
31
+ "dependencies": {
32
+ "@frigear-nu/module-kit": "0.1.0",
33
+ "@libsql/client": "^0.18.0",
34
+ "@nuxt/kit": "^4.6.0",
35
+ "@vueuse/core": "^15.0.0",
36
+ "drizzle-kit": "^0.31.11",
37
+ "drizzle-orm": "^0.45.3",
38
+ "zod": "^4.6.5"
39
+ },
40
+ "peerDependencies": {
41
+ "@frigear-nu/users": ">=0.0.1",
42
+ "@nuxthub/core": "^0.10.8",
43
+ "@nuxt/ui": "^4.10.0"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "@nuxt/ui": {
47
+ "optional": true
48
+ },
49
+ "@frigear-nu/users": {
50
+ "optional": true
51
+ },
52
+ "@nuxthub/core": {
53
+ "optional": true
54
+ }
55
+ },
56
+ "devDependencies": {
57
+ "@frigear-nu/users": "1.0.2",
58
+ "@nuxt/devtools": "^3.4.2",
59
+ "@nuxt/eslint-config": "^1.17.0",
60
+ "@nuxt/module-builder": "^1.0.3",
61
+ "@nuxt/schema": "^4.6.0",
62
+ "@nuxt/test-utils": "^4.3.2",
63
+ "@nuxt/ui": "^4.11.0",
64
+ "@nuxthub/core": "^0.10.8",
65
+ "@types/node": "latest",
66
+ "changelogen": "^0.6.2",
67
+ "eslint": "^10.10.0",
68
+ "nuxt": "^4.6.0",
69
+ "typescript": "^6.0.3",
70
+ "vitest": "^5.0.1",
71
+ "vue-tsc": "^3.3.11"
72
+ },
73
+ "scripts": {
74
+ "postinstall": "nuxt-module-build build --stub && nuxt prepare",
75
+ "build": "nuxt-module-build build",
76
+ "dev": "npm run dev:prepare && nuxt dev playground",
77
+ "dev:build": "nuxt build playground",
78
+ "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
79
+ "lint": "eslint .",
80
+ "lint:fix": "eslint . --fix",
81
+ "test": "vitest run",
82
+ "test:watch": "vitest watch",
83
+ "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
84
+ },
85
+ "typesVersions": {
86
+ "*": {
87
+ ".": [
88
+ "./dist/types.d.mts"
89
+ ]
90
+ }
91
+ }
6
92
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.