UNPKG

@tantainnovative/ndpr-toolkit

Version:

Nigeria Data Protection Toolkit — enterprise-grade compliance components for the Nigeria Data Protection Act (NDPA) 2023

729 lines (696 loc) 23.7 kB
import * as React_2 from 'react'; import React__default from 'react'; export declare const DSR: { Provider: React_2.FC<DSRProviderProps>; Form: React_2.FC<DSRRequestFormProps>; Dashboard: React_2.FC<DSRDashboardProps>; Tracker: React_2.FC<DSRTrackerProps>; }; declare interface DSRContextValue extends UseDSRReturn { requestTypes: RequestType[]; } /** * Data Subject Request dashboard component. Supports compliance with NDPA Part IV, * providing tools to track, manage, and respond to data subject requests within required timeframes. */ export declare const DSRDashboard: React__default.FC<DSRDashboardProps>; export declare interface DSRDashboardClassNames { root?: string; header?: string; title?: string; filters?: string; requestList?: string; requestItem?: string; statusBadge?: string; detailPanel?: string; } declare interface DSRDashboardProps { /** * List of DSR requests to display */ requests: DSRRequest[]; /** * Callback function called when a request is selected */ onSelectRequest?: (requestId: string) => void; /** * Callback function called when a request status is updated */ onUpdateStatus?: (requestId: string, status: DSRStatus) => void; /** * Callback function called when a request is assigned */ onAssignRequest?: (requestId: string, assignee: string) => void; /** * Title displayed on the dashboard * @default "Data Subject Request Dashboard" */ title?: string; /** * Description text displayed on the dashboard * @default "Track and manage data subject requests in compliance with NDPA Part IV requirements." */ description?: string; /** * Custom CSS class for the dashboard */ className?: string; /** * Custom CSS class for the buttons */ buttonClassName?: string; /** * Whether to show the request details * @default true */ showRequestDetails?: boolean; /** * Whether to show the request timeline * @default true */ showRequestTimeline?: boolean; /** * Whether to show the deadline alerts * @default true */ showDeadlineAlerts?: boolean; /** * List of possible assignees */ assignees?: string[]; /** * Object of CSS class overrides keyed by semantic section name. */ classNames?: DSRDashboardClassNames; /** * When true, all default Tailwind classes are removed so consumers * can style from scratch using classNames. */ unstyled?: boolean; } /** * Represents the data submitted by the DSR request form. */ export declare interface DSRFormSubmission { /** The selected request type identifier */ requestType: string; /** Data subject personal information */ dataSubject: { fullName: string; email: string; phone?: string; identifierType: string; identifierValue: string; }; /** Additional information provided for the selected request type */ additionalInfo?: Record<string, string | number | boolean | null>; /** Timestamp (ms) when the form was submitted */ submittedAt: number; } export declare const DSRProvider: React__default.FC<DSRProviderProps>; export declare interface DSRProviderProps { requestTypes: RequestType[]; adapter?: StorageAdapter<DSRRequest[]>; storageKey?: string; useLocalStorage?: boolean; onSubmit?: (request: DSRRequest) => void; onUpdate?: (request: DSRRequest) => void; children: React__default.ReactNode; } /** * Represents a data subject request */ export declare interface DSRRequest { /** Unique identifier for the request */ id: string; /** Type of request */ type: DSRType; /** Current status of the request */ status: DSRStatus; /** Timestamp when the request was submitted */ createdAt: number; /** Timestamp when the request was last updated */ updatedAt: number; /** Timestamp when the request was completed (if applicable) */ completedAt?: number; /** Timestamp when the identity was verified (if applicable) */ verifiedAt?: number; /** * Due date for responding to the request (timestamp) * NDPA requires response within 30 days of receipt */ dueDate?: number; /** Description or details of the request */ description?: string; /** * The lawful basis under which the data was originally processed * Relevant for evaluating objection and erasure requests */ lawfulBasis?: string; /** Data subject information */ subject: { name: string; email: string; phone?: string; identifierValue?: string; identifierType?: string; }; /** Additional information provided by the data subject */ additionalInfo?: Record<string, string | number | boolean | null>; /** Notes added by staff processing the request */ internalNotes?: Array<{ timestamp: number; author: string; note: string; }>; /** Verification status */ verification?: { verified: boolean; method?: string; verifiedAt?: number; verifiedBy?: string; }; /** Reason for rejection (if status is 'rejected') */ rejectionReason?: string; /** Files attached to the request */ attachments?: Array<{ id: string; name: string; type: string; url: string; addedAt: number; }>; /** * Whether an extension was requested for this DSR * NDPA allows a one-time extension of 30 days with justification */ extensionRequested?: boolean; /** Reason for the extension, if requested */ extensionReason?: string; } /** * Data Subject Request form component. Implements NDPA Part VI (Sections 34-38) * covering data subject rights including access, rectification, erasure, and portability. */ export declare const DSRRequestForm: React__default.FC<DSRRequestFormProps>; export declare interface DSRRequestFormClassNames { root?: string; title?: string; description?: string; form?: string; fieldGroup?: string; label?: string; input?: string; select?: string; textarea?: string; submitButton?: string; /** Alias for submitButton */ primaryButton?: string; successMessage?: string; /** Custom class applied when isSubmitting is true (e.g. a loading overlay) */ loadingOverlay?: string; } declare interface DSRRequestFormProps { /** * Array of request types that can be submitted */ requestTypes: RequestType[]; /** * Callback function called when form is submitted */ onSubmit: (data: DSRFormSubmission) => void; /** * Callback function called when form validation fails */ onValidationError?: (errors: Record<string, string>) => void; /** * Title displayed on the form * @default "Submit a Data Subject Request" */ title?: string; /** * Description text displayed on the form * @default "Use this form to exercise your rights under the Nigeria Data Protection Act (NDPA), Part VI, Sections 34-38." */ description?: string; /** * Text for the submit button * @default "Submit Request" */ submitButtonText?: string; /** * Custom CSS class for the form */ className?: string; /** * Custom CSS class for the submit button */ buttonClassName?: string; /** * Whether to show a confirmation message after submission * @default true */ showConfirmation?: boolean; /** * Confirmation message to display after submission * @default "Your request has been submitted successfully. You will receive a confirmation email shortly." */ confirmationMessage?: string; /** * Whether to require identity verification * @default true */ requireIdentityVerification?: boolean; /** * Types of identifiers accepted for verification * @default ["email", "account", "customer_id"] */ identifierTypes?: Array<{ id: string; label: string; }>; /** * Whether to collect additional contact information * @default true */ collectAdditionalContact?: boolean; /** * Custom labels for form fields */ labels?: { name?: string; email?: string; requestType?: string; description?: string; submit?: string; }; /** * Object of CSS class overrides keyed by semantic section name. */ classNames?: DSRRequestFormClassNames; /** * When true, all default Tailwind classes are removed so consumers * can style from scratch using classNames. */ unstyled?: boolean; /** * Whether the form is currently submitting. * When true, the submit button is disabled and shows "Submitting..." text. */ isSubmitting?: boolean; /** * Default values to pre-fill form fields. * Useful for editing existing requests or pre-populating known data. */ defaultValues?: Partial<DSRFormSubmission>; /** * Callback fired when the form is reset via the Reset button. * To fully remount the component (clearing all internal state), * change the `key` prop from the parent. */ onReset?: () => void; } /** * Status of a data subject request */ export declare type DSRStatus = 'pending' | 'awaitingVerification' | 'inProgress' | 'completed' | 'rejected'; /** * Validated DSR submission shape — matches what `<DSRRequestForm onSubmit>` * emits client-side. Use this as the typed parameter for your server-side * handler after `validateDsrSubmissionStructured` returns `valid: true`. */ export declare interface DsrSubmissionPayload { requestType: string; dataSubject: { fullName: string; email: string; phone?: string; identifierType: string; identifierValue: string; }; additionalInfo?: Record<string, string | number | boolean | null>; submittedAt: number; } /** * DSR tracking and analytics component. Supports compliance with NDPA Part IV, * providing summary statistics, deadline tracking, and compliance metrics for data subject requests. */ export declare const DSRTracker: React__default.FC<DSRTrackerProps>; export declare interface DSRTrackerClassNames { root?: string; header?: string; title?: string; stats?: string; statCard?: string; table?: string; tableHeader?: string; tableRow?: string; statusBadge?: string; } declare interface DSRTrackerProps { /** * List of DSR requests to track */ requests: DSRRequest[]; /** * Callback function called when a request is selected */ onSelectRequest?: (requestId: string) => void; /** * Title displayed on the tracker * @default "DSR Request Tracker" */ title?: string; /** * Description text displayed on the tracker * @default "Track the status and progress of data subject requests as required by NDPA Part IV." */ description?: string; /** * Custom CSS class for the tracker */ className?: string; /** * Custom CSS class for the buttons */ buttonClassName?: string; /** * Whether to show the summary statistics * @default true */ showSummaryStats?: boolean; /** * Whether to show the request type breakdown * @default true */ showTypeBreakdown?: boolean; /** * Whether to show the status breakdown * @default true */ showStatusBreakdown?: boolean; /** * Whether to show the timeline chart * @default true */ showTimelineChart?: boolean; /** * Whether to show the overdue requests * @default true */ showOverdueRequests?: boolean; /** * Object of CSS class overrides keyed by semantic section name. */ classNames?: DSRTrackerClassNames; /** * When true, all default Tailwind classes are removed so consumers * can style from scratch using classNames. */ unstyled?: boolean; } /** * Data Subject Rights types aligned with NDPA 2023 Part VI (Sections 34-38) * and the related provisions in Part V (Section 27 — information to the data subject) * and Part X (Section 46 — complaint to the Commission). * * Note: These are guidance labels — not legal advice. Verify with your DPO or counsel. */ /** * Types of data subject requests per NDPA Part VI * - 'information': Right to be informed (Section 27 — provision of information; Section 34(1)(a)) * - 'access': Right of access / confirmation + data copy (Section 34(1)(a)–(b)) * - 'rectification': Right to rectification (Section 34(1)(c)) * - 'erasure': Right to erasure (Section 34(1)(d), Section 34(2)) * - 'restriction': Right to restrict processing (Section 34(1)(e)) * - 'portability': Right to data portability (Section 38) * - 'objection': Right to object (Section 36) * - 'automated_decision_making': Rights re. automated decisions / profiling (Section 37) * - 'withdraw_consent': Right to withdraw consent (Section 35) */ export declare type DSRType = 'information' | 'access' | 'rectification' | 'erasure' | 'restriction' | 'portability' | 'objection' | 'automated_decision_making' | 'withdraw_consent'; /** * Format a DSR request for display or submission. Returns the formatted * payload plus a typed `errors` array of `{ field, code, message }` for any * required fields missing from the source request. * * Codes emitted: * - `request_id_required` * - `request_type_required` * - `request_status_required` * - `created_at_required` * - `subject_name_required` * - `subject_email_required` */ export declare function formatDSRRequestStructured(request: DSRRequest): FormatDSRRequestStructuredResult; /** Result of {@link formatDSRRequestStructured}. */ export declare interface FormatDSRRequestStructuredResult { valid: boolean; errors: StructuredValidationError[]; /** Formatted request payload — always populated regardless of `valid`. */ formattedRequest: Record<string, unknown>; /** Narrowed input — populated only on `valid: true`. */ data?: DSRRequest; } /** * Legacy status of a data subject request * @deprecated Use DSRStatus instead */ export declare type RequestStatus = 'pending' | 'verifying' | 'processing' | 'completed' | 'rejected'; /** * Represents a type of data subject request (detailed configuration) */ export declare interface RequestType { /** Unique identifier for the request type */ id: string; /** Display name for the request type */ name: string; /** Description of what this request type entails */ description: string; /** * NDPA 2023 section reference for this right * (e.g., "Section 34(1)(a)" for access, "Section 38" for portability). * Used for display purposes only — verify the exact subsection with counsel. */ ndpaSection?: string; /** * Estimated time to fulfill this type of request (in days) * NDPA requires response within 30 days */ estimatedCompletionTime: number; /** Whether additional information is required for this request type */ requiresAdditionalInfo: boolean; /** Custom fields required for this request type */ additionalFields?: Array<{ id: string; label: string; type: 'text' | 'textarea' | 'select' | 'checkbox' | 'file'; options?: string[]; required: boolean; placeholder?: string; }>; } export declare interface StorageAdapter<T = unknown> { /** Load persisted data. Called once on hook mount. */ load(): T | null | Promise<T | null>; /** Persist data. Called on every state change. */ save(data: T): void | Promise<void>; /** Clear persisted data. Called on reset. */ remove(): void | Promise<void>; } /** * Single structured validation error with a stable, locale-independent * `code` consumers can switch on programmatically. */ declare interface StructuredValidationError { /** Dot-path of the offending field (e.g. `'timestamp'`, `'dataSubject.email'`, `'options[0].purpose'`). */ field: string; /** Stable, snake_case error code — safe to switch on across locales. */ code: string; /** Human-readable English message — informational only; do not regex-match. */ message: string; } /** * Result of a structured validator. `errors` is an array (one entry per * failed rule). `data` is the narrowed, typed payload, only populated on * `valid: true`. */ declare interface StructuredValidationResult<T> { valid: boolean; errors: StructuredValidationError[]; data?: T; } /** * Hook for managing Data Subject Requests in compliance with the NDPA. * * @example * ```tsx * import { useDSR } from '@tantainnovative/ndpr-toolkit/hooks'; * * function DSRPanel() { * const { requests, submitRequest } = useDSR({ * requestTypes: [ * { id: 'access', name: 'Access', description: 'Request access', estimatedCompletionTime: 30 }, * ], * }); * return ( * <ul> * {requests.map((r) => ( * <li key={r.id}>{r.type} — {r.status}</li> * ))} * </ul> * ); * } * ``` */ export declare function useDSR({ initialRequests, requestTypes, adapter, storageKey, useLocalStorage, onSubmit, onUpdate, }: UseDSROptions): UseDSRReturn; export declare function useDSRCompound(): DSRContextValue; declare interface UseDSROptions { /** * Initial requests to load */ initialRequests?: DSRRequest[]; /** * Available request types */ requestTypes: RequestType[]; /** * Pluggable storage adapter. When provided, takes precedence over storageKey/useLocalStorage. */ adapter?: StorageAdapter<DSRRequest[]>; /** * Storage key for requests * @default "ndpr_dsr_requests" * @deprecated Use adapter instead */ storageKey?: string; /** * Whether to use local storage to persist requests. * * **Changed in 4.0:** the default is now `false`. `useDSR` is the admin * tracker hook and its state contains data subjects' PII; the previous * default (true) stored that PII in the admin's browser localStorage, * which is rarely appropriate. Opt in by passing `useLocalStorage: true` * if you specifically want the old behaviour. * * For production deployments, pass an explicit `adapter` instead. * * @default false (as of 4.0; was `true` in 3.x) * @deprecated Pass an explicit `adapter` instead of toggling this flag. */ useLocalStorage?: boolean; /** * Callback function called when a request is submitted */ onSubmit?: (request: DSRRequest) => void; /** * Callback function called when a request is updated */ onUpdate?: (request: DSRRequest) => void; } declare interface UseDSRReturn { /** * All requests */ requests: DSRRequest[]; /** * Submit a new request. The hook assigns `id`, `status`, `createdAt`, * `updatedAt`, and `dueDate` — pass everything else. */ submitRequest: (requestData: Omit<DSRRequest, 'id' | 'status' | 'createdAt' | 'updatedAt' | 'dueDate'>) => DSRRequest; /** * Update an existing request */ updateRequest: (id: string, updates: Partial<DSRRequest>) => DSRRequest | null; /** * Get a request by ID */ getRequest: (id: string) => DSRRequest | null; /** * Get requests by status */ /** * Filter requests by status. Accepts both the modern `DSRStatus` union and * the deprecated `RequestStatus` for backward compatibility — pass the * modern values (`'pending' | 'awaitingVerification' | 'inProgress' | ...`). */ getRequestsByStatus: (status: DSRStatus | RequestStatus) => DSRRequest[]; /** * Get requests by type */ getRequestsByType: (type: string) => DSRRequest[]; /** * Get the request type definition by ID */ getRequestType: (typeId: string) => RequestType | undefined; /** * Format a request for display or submission */ formatRequest: (request: DSRRequest) => Record<string, unknown>; /** * Clear all requests */ clearRequests: () => void; /** * Whether the adapter is still loading data (relevant for async adapters) */ isLoading: boolean; } /** Options for {@link validateDsrSubmissionStructured}. */ export declare interface ValidateDsrSubmissionOptions { /** * Whether the data subject is required to provide an identifier * (NDPC's recommended verification step). Mirror whatever you set on * the client-side `<DSRRequestForm requireIdentityVerification>`. * @default true */ requireIdentityVerification?: boolean; /** * Allowed request types. When provided, the payload's `requestType` * must be one of these — useful for locking the server to a specific * set of supported NDPA Part VI §34-38 (plus §35, §36, §37) data-subject rights. */ allowedRequestTypes?: string[]; } /** * Validate a raw DSR submission payload against the same rules * `<DSRRequestForm />` enforces client-side. Designed to be called from a * server-side handler (Next.js Route Handler, NestJS controller, Express * middleware, Cloudflare Worker) so client and server stay in sync without * the consumer hand-rolling zod / class-validator schemas. * * Defensive — accepts `unknown` and narrows. Safe to call directly on * `await request.json()`. Returns `{ field, code, message }[]` errors so * callers can switch on `code` programmatically across locales. * * Codes emitted: * - `payload_not_object` * - `request_type_required` * - `request_type_not_allowed` * - `data_subject_required` * - `full_name_required` * - `email_required` * - `email_invalid_format` * - `phone_invalid_type` * - `identifier_type_required` * - `identifier_value_required` * - `submitted_at_invalid` * - `additional_info_invalid_type` * - `payload_final_narrowing_failed` * * @example **Next.js Route Handler** * ```ts * import { validateDsrSubmissionStructured } from '@tantainnovative/ndpr-toolkit/server'; * * export async function POST(req: Request) { * const { valid, errors, data } = validateDsrSubmissionStructured(await req.json()); * if (!valid) { * return Response.json({ errors }, { status: 422 }); * } * await dsrStore.create(data); * return Response.json({ ok: true }, { status: 201 }); * } * ``` */ export declare function validateDsrSubmissionStructured(payload: unknown, options?: ValidateDsrSubmissionOptions): StructuredValidationResult<DsrSubmissionPayload>; export { }