@consenta/vue

Consenta Data Consent SDK — Vue 3 binding (Vue thuần trên headless core).
331
Install
npm install @consenta/vue
Latest Version:0.2.0
Vue:3
License:MIT
Last Updated:Sep 25, 2026
Links: npm
Maintainer: tungph7

@consenta/vue

Vue 3 binding cho Consenta Data Consent SDK — Vue thuần dựng trên headless core (@consenta/core). Không bọc web component: mọi nghiệp vụ (tải config, state, validate, build payload, submit) nằm ở core; package này chỉ nối vòng đời Vue (an toàn SSR/Nuxt).

Cài đặt

npm install @consenta/vue @consenta/core

vue là peerDependency (^3.3.0) — dùng bản Vue sẵn có của ứng dụng.

Dùng nhanh — <ConsentForm/>

<script setup lang="ts">
import { ConsentForm } from '@consenta/vue';
import type { ConsentPayload } from '@consenta/vue';

function onSubmit(payload: ConsentPayload) {
  console.log('consentId:', payload.consentId);
}
</script>

<template>
  <ConsentForm
    code-config="cp_...::t_..."
    language="vi"
    show-reject
    @submit="onSubmit"
    @error="(e) => console.error(e)"
  />
</template>

Style mặc định được chèn 1 lần vào <head> (idempotent, an toàn SSR). Tắt bằng unstyled và tự style qua class-names.

Sau khi gửi thành công, component thay form bằng panel xác nhận; khi lỗi, hiện banner kèm message do server trả. Muốn tự hiển thị thông báo → dùng no-success-ui.

Giao diện

Cùng bố cục với <cst-consent-form> của @consenta/web:

  • Header: tiêu đề + "Hiện danh sách / Ẩn danh sách", "Chi tiết / Thu gọn" (mở/đóng trường dữ liệu của mọi mục đích — chỉ hiện khi danh sách đang mở) và công tắc "Đồng ý tất cả" (bật/tắt mọi mục đích đang hiển thị, giữ nguyên mục đích bắt buộc; bị khoá khi mọi mục đích đều bắt buộc).
  • Dưới tiêu đề là mô tả của mẫu consent (config.description).
  • Danh sách mục đích mặc định ẩn; bật default-list-expanded để hiện sẵn. Mỗi mục đích: tên (+ * nếu bắt buộc) kèm nút "Chi tiết" ngay cạnh, switch bên phải, mô tả bên dưới (dài hơn 120 ký tự thì thu gọn 2 dòng + "Xem thêm").
  • "Chi tiết" mở các trường dữ liệu: mỗi trường một switch nhỏ (chỉ đổi được khi mục đích đang bật) + tag bên thứ ba. Trường CMP trả display: false không hiển thị và không tính là bắt buộc, nhưng vẫn được gửi theo trạng thái của mục đích.
  • Collection point tạm ngừng (Config API trả 410): component không render gì, status = 'suspended', sự kiện @suspended được gọi, isValid = true và submit() trả { ok: false, error: 'suspended' } (CONSENT_SUSPENDED_ERROR) — host nên cho luồng của mình đi tiếp.

Gộp consent vào form của bạn (manual-submit + template ref)

Khi muốn 1 nút duy nhất gửi cả form của bạn lẫn consent: ẩn nút của SDK bằng manual-submit, rồi gọi submitConsent() qua template ref.

<script setup lang="ts">
import { reactive, useTemplateRef } from 'vue';
import { CONSENT_SUSPENDED_ERROR, ConsentForm } from '@consenta/vue';
import type { ConsentFormHandle } from '@consenta/vue';

const consent = useTemplateRef<ConsentFormHandle>('consent');
const form = reactive({ name: '', email: '' });

async function handleSubmit() {
  // Giá trị field: key theo field id CMP HOẶC nhãn field ("Họ và tên")
  const result = await consent.value?.submitConsent({
    'Họ và tên': form.name,
    Email: form.email,
  });

  // 'suspended' = tenant đã tạm ngừng collection point → không cần consent, gửi form như thường.
  if (!result?.ok && result?.error !== CONSENT_SUSPENDED_ERROR) return; // lỗi → không gửi form
  await saveContact(form); // consent OK → gửi form của bạn
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="form.name" />
    <input v-model="form.email" type="email" />

    <ConsentForm
      ref="consent"
      code-config="cp_...::t_..."
      variant="embedded"
      manual-submit
      no-success-ui
    />

    <button type="submit">Gửi</button>
  </form>
</template>

ref cung cấp: submitConsent(fieldValues?) → { ok, consentId?, error? }, isValid(), getState(), isSuspended(). error có thể là message server, CONSENT_NOT_READY_ERROR ('not-ready' — chưa tải xong config) hoặc CONSENT_SUSPENDED_ERROR ('suspended' — collection point tạm ngừng).

Vue < 3.5 chưa có useTemplateRef → dùng const consent = ref<ConsentFormHandle | null>(null) với <ConsentForm ref="consent" …/> (Options API: this.$refs.consent).

Giá trị field (fieldValues)

  • Key theo field id CMP hoặc nhãn field (SDK khớp id → name → nhãn chuẩn hoá ho_va_ten).
  • value chỉ đính khi người dùng đã đồng ý field đó — không gửi dữ liệu cho mục đích/field bị tắt.
  • Có thể khai sẵn qua prop field-values; giá trị truyền vào submitConsent()/submit() sẽ đè lên.

Visitor ID (cmp_vid)

Mỗi lần gửi, SDK đọc trước khoá cmp_vid trong localStorage; chưa có thì sinh UUID mới và lưu lại, rồi gửi kèm payload dưới trường visitorId. Nhờ đó CMP nối được Data Consent với Cookie Consent của cùng một trình duyệt — khoá và định dạng dùng chung với SDK cookie banner, bên nào chạy trước cũng được. Mặc định bật, không cần cấu hình.

<ConsentForm code-config="…" visitor-id="7f75efa3-…" />
<!-- dùng cmp_vid có sẵn (phải là UUID) -->
<ConsentForm code-config="…" :visitor-id="null" />
<!-- TẮT: không đọc, không tạo, không gửi -->

visitor-id phải là UUID (định dạng cmp_vid dùng chung với SDK cookie banner). Giá trị sai định dạng bị bỏ qua (có cảnh báo trên console) và SDK tự đọc/sinh cmp_vid như khi bỏ trống.

Headless — useConsent()

Tự dựng UI riêng, vẫn tái dùng toàn bộ logic core:

<script setup lang="ts">
import { useConsent } from '@consenta/vue';

// Destructure được vì composable trả object chứa các ref → template tự unwrap.
const { status, config, state, engine, error, errorMessage, submitting, setPurpose, submit } =
  useConsent({ codeConfig: 'cp_...::t_...' });
</script>

<template>
  <p v-if="status !== 'ready'">Đang tải…</p>
  <template v-else>
    <label v-for="p in config?.items" :key="p.id">
      <input
        type="checkbox"
        :checked="state?.purposes[p.id]?.accepted ?? false"
        :disabled="engine?.isPurposeRequired(p.id)"
        @change="setPurpose(p.id, ($event.target as HTMLInputElement).checked)"
      />
      {{ p.label }}
    </label>
    <button :disabled="submitting" @click="submit()">Đồng ý</button>
    <p v-if="error != null" role="alert">{{ errorMessage ?? 'Gửi consent thất bại.' }}</p>
  </template>
</template>

submit(fieldValues?) không ném lỗi — trả { ok, consentId?, error? }; composable cũng expose errorMessage (ưu tiên message server), submitting, submitted, isValid, reload. status là 'loading' | 'ready' | 'error' | 'suspended'. Engine core có sẵn engine.getVisiblePurposes(), engine.getVisibleFields(id), engine.isAllAccepted(), engine.hasOptionalPurpose() để dựng header "Đồng ý tất cả" và bỏ mục/trường display: false.

Mọi option nhận giá trị, ref hoặc getter (MaybeRefOrGetter): đổi codeConfig/baseUrl sẽ tự tải lại config.

Nuxt 3 / SSR

Không cần <ClientOnly>: khi SSR, component render trạng thái "đang tải" và không gọi API (request chỉ chạy trong onMounted), nên không có hydration mismatch.

<!-- pages/dang-ky.vue -->
<script setup lang="ts">
import { ConsentForm } from '@consenta/vue';
</script>

<template>
  <ConsentForm code-config="cp_...::t_..." />
</template>

Props chính của <ConsentForm/>

Prop (template) Kiểu Mặc định Ý nghĩa
code-config string — Integration key (bắt buộc)
base-url string prod CMP Override CMP (vd dev localhost:8082)
language 'vi' | 'en' 'vi' Ngôn ngữ nhãn UI
variant 'card' | 'embedded' 'card' embedded = khung phẳng để nhúng trong form
show-reject boolean false Hiện nút "Từ chối tất cả"
default-list-expanded boolean false Hiện sẵn danh sách mục đích
default-expanded boolean false Mở sẵn phần trường dữ liệu
manual-submit boolean false Ẩn thanh nút; host gửi qua ref
no-success-ui boolean false Không thay form bằng panel thành công
field-values object — Giá trị field đính vào payload
visitor-id string | null tự đọc Ghi đè cmp_vid (UUID); null = tắt hẳn
unstyled / class-names — — Tùy biến style

Sự kiện: @ready (config), @change (state), @submit (payload), @error (error), @suspended (collection point tạm ngừng).

Ngoài ra <ConsentForm/> expose template ref (ConsentFormHandle): submitConsent(fieldValues?), isValid(), getState(), isSuspended().

Theming

Giá trị mặc định của biến --cst-* được khai trong :where(.cst-consent) (độ ưu tiên 0), nên selector bất kỳ của bạn nhắm vào .cst-consent đều ghi đè được:

.my-form .cst-consent {
  --cst-accent: #0f766e;
  --cst-radius: 10px;
}

Biến có sẵn: --cst-accent, --cst-text, --cst-muted, --cst-required, --cst-border, --cst-surface, --cst-subtle, --cst-radius, --cst-radius-sm, --cst-gap, --cst-font, --cst-padding, --cst-shadow, --cst-container-border.

Xem thêm useConsent để build payload/submit thủ công.

Version History

Version Released Vue License
0.2.0 ^3.3.0 MIT
0.1.0 ^3.3.0 MIT