{"_id":"@akku-work/consent-sdk-react","name":"@akku-work/consent-sdk-react","dist-tags":{"beta":"0.1.0-beta.1","latest":"0.1.0-beta.1"},"versions":{"0.1.0-beta.1":{"name":"@akku-work/consent-sdk-react","version":"0.1.0-beta.1","description":"Akku Consent - React bindings and consent surfaces for an authenticated host application.","keywords":["consent","gdpr","dpdp","privacy","cmp","preference-management","react","react-hooks"],"license":"UNLICENSED","author":{"name":"Akku"},"homepage":"https://bitbucket.org/cnw-bitbucket-ws/akku-consent","repository":{"type":"git","url":"git+https://bitbucket.org/cnw-bitbucket-ws/akku-consent.git","directory":"packages/consent-sdk-react"},"bugs":{"url":"https://bitbucket.org/cnw-bitbucket-ws/akku-consent/issues"},"//private":"PKG-006 pattern, third time: this is the THIRD workspace package (after @akku-work/consent-sdk and @akku-work/consent-sdk-auth) that is NOT private — every other app/package keeps `private: true`. Publishing is tag-triggered only (sdk-v*, bitbucket-pipelines.yml); do not run a bare `pnpm publish -r` from the repo root.","publishConfig":{"access":"public","tag":"beta"},"type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./styles.css":"./styles.css"},"//sideEffects":"The stylesheet is listed explicitly: `sideEffects: false` tells a bundler it may drop an import whose only effect is a side effect, and `import \"@akku-work/consent-sdk-react/styles.css\"` is exactly that. Dropping it ships an unstyled consent surface.","sideEffects":["**/*.css"],"//dependencies":"Empty on purpose, like both sibling SDKs. @akku-work/consent-sdk-auth is a PEER, not a dependency: a host installs it itself and must end up with exactly ONE ConsentManager — two copies would each hold their own onChange listener set and their own view of the subject's decisions, so a write through one would never notify the other.","dependencies":{},"//peerDependencies":"A real semver range, NOT `workspace:^`. The workspace protocol is pnpm-only: `pnpm publish` rewrites it, but `npm pack`/`npm publish` ship it verbatim and the tarball is then uninstallable — found by packing this package and installing it into a host app outside the monorepo. A plain range keeps the local workspace link working (pnpm links a workspace package that satisfies the range) and is correct in the tarball whichever tool publishes it.","peerDependencies":{"@akku-work/consent-sdk-auth":"^0.1.0-beta.1","react":"^18.0.0 || ^19.0.0"},"devDependencies":{"@testing-library/dom":"^10.4.0","@testing-library/react":"^16.1.0","@testing-library/user-event":"^14.5.2","@types/react":"^19.0.1","jsdom":"^25.0.1","react":"^19.0.0","react-dom":"^19.0.0","rimraf":"^6.0.1","tsup":"^8.3.5","typescript":"^5.6.3","vitest":"^3.2.6","@akku-work/consent-sdk-auth":"0.1.0-beta.1","@akku/contracts":"0.1.0"},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:coverage":"vitest run --coverage","lint":"eslint .","typecheck":"tsc -p tsconfig.json --noEmit","clean":"rimraf dist"},"_id":"@akku-work/consent-sdk-react@0.1.0-beta.1","_integrity":"sha512-6SrLd66t+AM1eORXK8R+Q1uBx99jgPI6dB+ltUzrAMUYAUgi4tCUv1lG2/+4R5VO0nIxAAReKf9BrqOJAI+kiw==","_resolved":"C:\\Users\\RAJAND~1\\AppData\\Local\\Temp\\93ce686ab2474753c222b73a115ddf24\\akku-work-consent-sdk-react-0.1.0-beta.1.tgz","_from":"file:akku-work-consent-sdk-react-0.1.0-beta.1.tgz","_nodeVersion":"22.22.2","_npmVersion":"10.9.7","dist":{"integrity":"sha512-6SrLd66t+AM1eORXK8R+Q1uBx99jgPI6dB+ltUzrAMUYAUgi4tCUv1lG2/+4R5VO0nIxAAReKf9BrqOJAI+kiw==","shasum":"87042b9bc4c18788b011d8da21b877d52a2c19ae","tarball":"https://registry.npmjs.org/@akku-work/consent-sdk-react/-/consent-sdk-react-0.1.0-beta.1.tgz","fileCount":10,"unpackedSize":183983,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCrrEzIveZoDP9BZnRxDI7y9nGpvQg6Lugf3JWB2PUf7QIgC/x8BApmQBqJEzETo9OdUcTffQmIQOwHroyHopEPU+4="}]},"_npmUser":{"name":"rajandavidt","email":"rajandavid.t@cloudnowtech.com"},"directories":{},"maintainers":[{"name":"cloudnowtech","email":"developer@cloudnowtech.com"},{"name":"rajandavidt","email":"rajandavid.t@cloudnowtech.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/consent-sdk-react_0.1.0-beta.1_1787299038191_0.5979336133307145"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-21T07:57:17.985Z","0.1.0-beta.1":"2026-08-21T07:57:18.325Z","modified":"2026-08-21T07:57:18.614Z"},"maintainers":[{"name":"cloudnowtech","email":"developer@cloudnowtech.com"},{"name":"rajandavidt","email":"rajandavid.t@cloudnowtech.com"}],"description":"Akku Consent - React bindings and consent surfaces for an authenticated host application.","homepage":"https://bitbucket.org/cnw-bitbucket-ws/akku-consent","keywords":["consent","gdpr","dpdp","privacy","cmp","preference-management","react","react-hooks"],"repository":{"type":"git","url":"git+https://bitbucket.org/cnw-bitbucket-ws/akku-consent.git","directory":"packages/consent-sdk-react"},"author":{"name":"Akku"},"bugs":{"url":"https://bitbucket.org/cnw-bitbucket-ws/akku-consent/issues"},"license":"UNLICENSED","readme":"# @akku-work/consent-sdk-react\r\n\r\nReact bindings and consent surfaces for an authenticated host application.\r\n\r\nThis package **renders**. [`@akku-work/consent-sdk-auth`](../consent-sdk-auth) **decides** — it resolves\r\neach published purpose to a legal basis, a validity window and an outcome, and this package draws\r\nthat. Nothing here evaluates expiry, derives a basis, or decides whether a purpose needs asking; if\r\nyou find yourself adding logic of that kind here, it belongs on the other side.\r\n\r\n## Install\r\n\r\n```bash\r\nnpm install @akku-work/consent-sdk-react @akku-work/consent-sdk-auth\r\n```\r\n\r\n`@akku-work/consent-sdk-auth` and `react` are **peer** dependencies. The auth SDK is a peer rather than a\r\ndependency on purpose: your app must end up with exactly **one** `ConsentManager`. Two copies each\r\nhold their own `onChange` listeners and their own view of the subject's decisions, so a write through\r\none would never notify the other.\r\n\r\n## Wiring\r\n\r\n```tsx\r\nimport { ConsentManager } from \"@akku-work/consent-sdk-auth\";\r\nimport { ConsentProvider } from \"@akku-work/consent-sdk-react\";\r\n\r\nconst manager = new ConsentManager({\r\n  apiHost: \"https://consent.nova.example\",\r\n  siteKey: \"sk_live_9f2c…\",\r\n  applicationId: \"nova-bank-web\",\r\n  getSubjectToken: () => session.fetchConsentToken(),\r\n});\r\n\r\n<ConsentProvider manager={manager}>\r\n  <App />\r\n</ConsentProvider>;\r\n```\r\n\r\nNo `userId` is ever passed. Identity travels only inside the host-signed subject token, and the\r\nbackend derives the subject by verifying it — a userId handed over from the browser is a claim, not a\r\ncredential.\r\n\r\n## Gating a feature\r\n\r\n```tsx\r\nconst { isGranted, loading } = useConsent();\r\n\r\nif (isGranted(\"device.analytics\")) trackDashboardView();\r\n```\r\n\r\n`isGranted` is **false while loading** and **false for a key the policy does not declare**. This is a\r\ngate, and a gate that opens because the answer has not arrived yet is not a gate. Read `loading`\r\nalongside it when you need to tell \"no\" from \"not yet\".\r\n\r\n## The four surfaces\r\n\r\nWhich one to use is a property of the **moment**, not of the purpose.\r\n\r\n| Surface | Use for |\r\n| --- | --- |\r\n| `AskModal` | One purpose, at the moment it matters. Blocks, so a user action must provoke it. |\r\n| `DisclosureModal` | A collection point, fired when a form opens. Discloses, then captures. |\r\n| `AskSnackbar` | A low-stakes ask that must not interrupt. |\r\n| `PreferenceSheet` | The standing entry point. Every toggle is an immediate write. |\r\n\r\n```tsx\r\n// Renders nothing at all when there is nothing to ask — already decided and still valid, a basis\r\n// that is never prompted, or a key the policy does not declare. Mount it where the question belongs\r\n// and let it decide whether to appear.\r\n<AskModal purposeKey=\"email.marketing\" source=\"profile\" />\r\n```\r\n\r\n`source` is required and is stamped on the ledger event. It records **where** consent was captured,\r\nwhich is what makes a consent record auditable after the fact.\r\n\r\n## Rules these components will not let you break\r\n\r\nThese are not style choices. Each one is a way a consent UI can misrepresent what a person chose.\r\n\r\n- **The legal basis decides the control.** `consent` gets a toggle; `necessary` and\r\n  `legal_obligation` render as \"Always on\" with no control at all. You cannot pass a prop to override\r\n  this — offering a switch for something that cannot be switched off is the commonest way a consent UI\r\n  lies.\r\n- **Nothing is pre-ticked.** `DisclosureModal` starts every choice unanswered and will not save until\r\n  something is answered. A pre-ticked box is not consent, and an empty save would log a consent event\r\n  for a decision nobody made.\r\n- **A dismissal is never an answer.** Escape, the backdrop and \"Later\" record nothing. Silence is not\r\n  consent, and it is not a refusal either.\r\n- **No auto-dismiss timer** on the snackbar. A surface that fades into a decision turns inaction into\r\n  one.\r\n- **Decline is never de-emphasised.** Accept and decline take the same button treatment; equal\r\n  prominence is a contract requirement.\r\n- **The preference sheet has no Save button.** Every toggle writes immediately, because a preference\r\n  centre with unsaved changes is one that loses them — and a withdrawal has to take effect when it is\r\n  asked for.\r\n- **A failed read never renders as \"you have no choices.\"** The error is shown and the last known\r\n  state is kept. An unreachable API must not blank the screen or break the page around it.\r\n\r\n## Styling\r\n\r\nImport the stylesheet. The package ships its own look:\r\n\r\n```ts\r\nimport \"@akku-work/consent-sdk-react/styles.css\";\r\n```\r\n\r\n**This is not optional, and the package deliberately does not leave it to you.** An earlier version\r\nshipped class names and no CSS, on the reasoning that a host app has its own design system. That was\r\nwrong: *decline must never be de-emphasised relative to accept*, and a requirement enforced only by a\r\ncomment in someone else's stylesheet is not enforced. Here both buttons take their height, size and\r\nweight from the same rule — only `background` differs — so there is no separate declaration to\r\noverride.\r\n\r\nRetheme with custom properties rather than by rewriting rules:\r\n\r\n```css\r\n:root {\r\n  --akku-iris-500: #0d9488;   /* accent */\r\n  --akku-iris-600: #0f766e;   /* accent, hover */\r\n  --akku-ink: #0f172a;\r\n  --akku-font-sans: \"Your Face\", system-ui, sans-serif;\r\n}\r\n```\r\n\r\nEvery class also carries `display` with `!important`. That is not defensiveness for its own sake: a\r\npage-level `* { display: none }` reset would otherwise hide consent controls, and a surface a visitor\r\ncannot see is a consent request that never happened. The same defence exists in `@akku-work/consent-sdk`,\r\nwhere the bug was made twice.\r\n\r\nThe primitives (`PurposeRow`, `LegalBasisPill`, `ConsentToggle`, `SurfaceHeader`, `SurfaceFooter`,\r\n`ReasonBanner`, `PurposeKeyMeta`) are exported so you can compose a surface of your own without\r\nre-deriving the rules above and getting one wrong.\r\n\r\n## Known limit\r\n\r\nExpiry does not currently fire on the authenticated plane. Its consent record carries one\r\n`updatedAt` for the whole record rather than a per-purpose timestamp, so a decision cannot be aged\r\nindividually — and deriving it from the record-level timestamp would lapse a purpose answered\r\nyesterday because a different one was answered a year ago. `validityDays` is published, displayed and\r\ncarried through; it simply does not yet cause a re-ask here. Tracked on AK-8494.\r\n\r\n## Licence\r\n\r\nProprietary. See [LICENSE](./LICENSE) — publication on npm grants no licence to use this software.\r\n","readmeFilename":"README.md","_rev":"1-284c4ae6796d51a6e9a334d4d9dc29a3"}