Data Subject Rights Portal

NDPA 2023-compliant portal for managing data subject rights requests

Overview

The Data Subject Rights Portal provides a complete solution for handling data subject access requests (DSARs) and other rights requests in compliance with the Nigeria Data Protection Act 2023 (NDPA). It includes a request submission form, admin dashboard for managing requests, and a tracking system for data subjects.

NDPA Data Subject Rights

Under the NDPA 2023, data subjects have several rights, including the right to access their personal data, right to rectification, right to erasure, right to restrict processing, right to data portability, and right to object to processing. Organizations must respond to these requests within 30 days.

Quick Start

The toolkit ships zero-config presets, compound components for custom layouts, and a StorageAdapter pattern so you can plug in any persistence backend without touching component internals.

Zero-config preset

// Drop in a fully-working DSR portal with NDPA defaults
import { NDPRSubjectRights } from '@tantainnovative/ndpr-toolkit/presets';

<NDPRSubjectRights />

Compound components for custom layouts

import { DSR } from '@tantainnovative/ndpr-toolkit/dsr';

<DSR.Provider requestTypes={types}>
  <DSR.Form />
  <DSR.Dashboard />
</DSR.Provider>

Hook with adapter

import { useDSR } from '@tantainnovative/ndpr-toolkit/hooks';
import { apiAdapter } from '@tantainnovative/ndpr-toolkit/adapters';

// The adapter prop accepts any StorageAdapter for custom persistence
// (server-side DB, REST API, localStorage, etc.)
const dsr = useDSR({ requestTypes: types, adapter: apiAdapter('/api/dsr') });

Installation

Install the NDPR Toolkit package which includes the Data Subject Rights components:

pnpm add @tantainnovative/ndpr-toolkit

Components

The Data Subject Rights system includes several components that work together:

DSRRequestForm

A form for data subjects to submit rights requests, with support for different request types.

import { DSRRequestForm } from '@tantainnovative/ndpr-toolkit';

<DSRRequestForm
  onSubmit={handleSubmitRequest}
  requestTypes={[
    { id: 'access', name: 'Access my data', description: 'Request a copy of your personal data', estimatedCompletionTime: 30, requiresAdditionalInfo: false },
    { id: 'rectification', name: 'Correct my data', description: 'Request correction of inaccurate data', estimatedCompletionTime: 30, requiresAdditionalInfo: false },
    { id: 'erasure', name: 'Delete my data', description: 'Request deletion of your data', estimatedCompletionTime: 30, requiresAdditionalInfo: false },
    { id: 'restriction', name: 'Restrict processing of my data', description: 'Request restriction of processing', estimatedCompletionTime: 30, requiresAdditionalInfo: false },
    { id: 'portability', name: 'Data portability', description: 'Request your data in a portable format', estimatedCompletionTime: 30, requiresAdditionalInfo: false },
    { id: 'objection', name: 'Object to processing', description: 'Object to processing of your data', estimatedCompletionTime: 30, requiresAdditionalInfo: false }
  ]}
/>

DSRDashboard

An admin dashboard for managing and responding to data subject rights requests.

import { DSRDashboard } from '@tantainnovative/ndpr-toolkit';

<DSRDashboard
  requests={dsrRequests}
  onSelectRequest={handleSelectRequest}
  onUpdateStatus={(id, status) => updateStatus(id, status)}
  onAssignRequest={(id, assignee) => assign(id, assignee)}
/>

DSRTracker

A component for data subjects to track the status of their requests.

import { DSRTracker } from '@tantainnovative/ndpr-toolkit';

<DSRTracker
  requests={dsrRequests}
  onSelectRequest={handleSelectRequest}
/>

BreachReportForm

A form for reporting data breaches, which is a requirement under the NDPA. See also the dedicated Breach Notification documentation.

import { BreachReportForm } from '@tantainnovative/ndpr-toolkit';

<BreachReportForm
  onSubmit={handleSubmitBreachReport}
  formDescription="Report a data breach that has occurred within your organization."
  recipientEmail="dpo@example.com"
/>

Usage

Here's a complete example of how to implement the Data Subject Rights system in your application:

import { useState } from 'react';
import {
  DSRRequestForm,
  DSRDashboard,
  DSRTracker,
  useDSR
} from '@tantainnovative/ndpr-toolkit';

const requestTypes = [
  { id: 'access', name: 'Access my data', description: 'Request a copy of your personal data', estimatedCompletionTime: 30, requiresAdditionalInfo: false },
  { id: 'rectification', name: 'Correct my data', description: 'Request correction of inaccurate data', estimatedCompletionTime: 30, requiresAdditionalInfo: false },
  { id: 'erasure', name: 'Delete my data', description: 'Request deletion of your data', estimatedCompletionTime: 30, requiresAdditionalInfo: false },
  { id: 'restriction', name: 'Restrict processing of my data', description: 'Request restriction of processing', estimatedCompletionTime: 30, requiresAdditionalInfo: false },
  { id: 'portability', name: 'Data portability', description: 'Request your data in a portable format', estimatedCompletionTime: 30, requiresAdditionalInfo: false },
  { id: 'objection', name: 'Object to processing', description: 'Object to processing of your data', estimatedCompletionTime: 30, requiresAdditionalInfo: false }
];

function DSRPortal() {
  const [activeTab, setActiveTab] = useState('submit');
  const [selectedId, setSelectedId] = useState('');

  const {
    requests,
    submitRequest,
    updateRequest,
    getRequest,
  } = useDSR({ requestTypes });

  // DSRRequestForm calls onSubmit with a DSRFormSubmission
  const handleSubmitRequest = (submission) => {
    const newRequest = submitRequest({
      type: submission.requestType,
      subject: {
        name: submission.dataSubject.fullName,
        email: submission.dataSubject.email,
        phone: submission.dataSubject.phone,
        identifierType: submission.dataSubject.identifierType,
        identifierValue: submission.dataSubject.identifierValue
      },
      additionalInfo: submission.additionalInfo
    });
    alert(`Your request has been submitted. Your tracking ID is: ${newRequest.id}`);
  };

  const handleSelectRequest = (id) => {
    setSelectedId(id);
    const request = getRequest(id);
    console.log('Selected request:', request);
  };

  return (
    <div>
      <nav>
        <button onClick={() => setActiveTab('submit')}>Submit Request</button>
        <button onClick={() => setActiveTab('track')}>Track Requests</button>
        <button onClick={() => setActiveTab('admin')}>Admin Dashboard</button>
      </nav>

      {activeTab === 'submit' && (
        <DSRRequestForm onSubmit={handleSubmitRequest} requestTypes={requestTypes} />
      )}

      {activeTab === 'track' && (
        <DSRTracker requests={requests} onSelectRequest={handleSelectRequest} />
      )}

      {activeTab === 'admin' && (
        <DSRDashboard
          requests={requests}
          onUpdateStatus={(id, status) => updateRequest(id, { status })}
        />
      )}
    </div>
  );
}

Submission payload contract

When you pass a submitTo URL to NDPRSubjectRights, the toolkit POSTs the form data to your backend as JSON. This section is the canonical request and response shape so backend implementers don't have to read the component source.

Request

  • Method: POST
  • Content-Type: application/json
  • Credentials: same-origin by default
  • Body: JSON.stringify(DSRFormSubmission)

Use submitOptions on the component to override credentials or add custom headers (e.g. X-CSRF-Token). For full control over retries, error hooks, or non-fetch transports, build an apiAdapter and pass it via adapter instead.

Request body

/**
 * Represents the data submitted by the DSR request form.
 */
export 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;
}

Example payload

{
  "requestType": "access",
  "dataSubject": {
    "fullName": "Adaeze Okafor",
    "email": "adaeze.okafor@example.ng",
    "phone": "+2348012345678",
    "identifierType": "nin",
    "identifierValue": "12345678901"
  },
  "additionalInfo": {
    "dataCategories": "account, billing, marketing",
    "preferredFormat": "json",
    "verifiedOwner": true
  },
  "submittedAt": 1748131200000
}

Recommended response shape

The toolkit doesn't mandate a specific response, but the convention below lets the client show a confirmation page, email a receipt, or poll for status. onSubmitSuccess parses the body as JSON and passes it through as body: unknown, so any shape works — this is the one the rest of the toolkit expects.

interface DSRSubmissionResponse {
  /** Server-generated tracking ID returned to the data subject. */
  referenceId: string;
  /** Lifecycle stage of the request immediately after acceptance. */
  status?: 'received' | 'processing' | 'completed';
  /** ISO-8601 timestamp the data subject can expect a final response by. */
  estimatedCompletionAt?: string;
}

Example backend handler (Next.js App Router)

// app/api/dsr/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  let body: unknown;
  try {
    body = await request.json();
  } catch {
    return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 });
  }

  const submission = body as {
    requestType?: string;
    dataSubject?: { fullName?: string; email?: string; identifierValue?: string };
    submittedAt?: number;
  };

  if (
    !submission?.requestType ||
    !submission.dataSubject?.fullName ||
    !submission.dataSubject?.email ||
    !submission.dataSubject?.identifierValue ||
    typeof submission.submittedAt !== 'number'
  ) {
    return NextResponse.json({ error: 'Missing required fields' }, { status: 422 });
  }

  const referenceId = 'DSR-' + Date.now().toString(36);
  // await persistDSR(referenceId, submission);

  return NextResponse.json({ referenceId, status: 'received' }, { status: 201 });
}

Client-side handling

Use the onSubmitSuccess and onSubmitError props on NDPRSubjectRights to react to the response on the client. onSubmitSuccess parses the response body as JSON before invoking your callback, so you can read referenceId straight off body (typed unknown — narrow it before use). See the API Reference below for the full prop list.

API Reference

DSRRequestForm Props

PropTypeDefaultDescription
onSubmit(submission: DSRFormSubmission) => voidRequiredCallback function when form is submitted
requestTypesRequestType[]RequiredArray of request types ({ id, name, description, estimatedCompletionTime, requiresAdditionalInfo, additionalFields? })
titlestring'Submit a Data Subject Request'Form title
descriptionstring'Use this form to exercise your rights under the NDPA...'Form description
submitButtonTextstring'Submit Request'Text for the submit button

useDSR Hook

import { useDSR } from '@tantainnovative/ndpr-toolkit/hooks';

// requestTypes is required: RequestType[] ({ id, name, description, estimatedCompletionTime, requiresAdditionalInfo })
const {
  requests,             // Array of all DSR requests
  submitRequest,        // Submit a new request
  updateRequest,        // Update an existing request
  getRequest,           // Get a request by id
  getRequestsByStatus,  // Filter requests by status
  getRequestsByType,    // Filter requests by type
  getRequestType,       // Get a request type definition by id
  formatRequest,        // Format a request for display/submission
  clearRequests,        // Clear all requests
  isLoading,            // Loading state for async adapters
} = useDSR({ requestTypes });

// Submit a new request. The hook assigns id, status, createdAt, updatedAt, dueDate.
const newRequest = submitRequest({
  type: 'access',
  subject: {
    name: 'John Doe',
    email: 'john@example.com',
    phone: '1234567890'
  },
  description: 'I would like to access all my personal data.'
});

// Update a request
updateRequest('request-id', {
  status: 'inProgress'
});

// Look up and filter requests
const request = getRequest('request-id');
const pendingRequests = getRequestsByStatus('pending');
const accessRequests = getRequestsByType('access');

DSRType Enum

export type DSRType =
  | 'information'
  | 'access'
  | 'rectification'
  | 'erasure'
  | 'restriction'
  | 'portability'
  | 'objection'
  | 'automated_decision_making'
  | 'withdraw_consent';

DSRStatus Enum

export type DSRStatus = 'pending' | 'awaitingVerification' | 'inProgress' | 'completed' | 'rejected';

DSRRequest Interface

export interface DSRRequest {
  id: string;
  type: DSRType;
  status: DSRStatus;
  createdAt: number;
  updatedAt: number;
  completedAt?: number;
  verifiedAt?: number;
  dueDate?: number;
  description?: string;
  subject: {
    name: string;
    email: string;
    phone?: string;
    identifierValue?: string;
    identifierType?: string;
  };
  additionalInfo?: Record<string, string | number | boolean | null>;
  internalNotes?: Array<{
    timestamp: number;
    author: string;
    note: string;
  }>;
}

Best Practices

  • Verification Process: Implement a verification process to confirm the identity of the data subject making the request.
  • Response Timeframe: The NDPA requires organizations to respond to DSARs within 30 days. Ensure your process allows for timely responses.
  • Complete Responses:Provide complete information in response to access requests, including what data you hold, how it's used, who it's shared with, and its source.
  • Record Keeping: Maintain records of all DSARs and your responses to them. The DSRDashboard component helps with this.
  • Staff Training: Ensure staff handling DSARs are trained on the requirements of the NDPA and your internal processes.

Accessibility

The Data Subject Rights components are built with accessibility in mind:

  • All form elements have proper labels and ARIA attributes
  • Focus states are clearly visible
  • Color contrast meets WCAG 2.1 AA standards
  • Keyboard navigation is fully supported
  • Error messages are announced to screen readers

Need Help?

If you have questions about implementing the Data Subject Rights system or need assistance with NDPA compliance, check out these resources:

GitHub Issues

Report bugs or request features on our GitHub repository.

View Issues

NDPA Resources

Learn more about NDPA 2023 compliance requirements.

NDPA Framework