Basic Integration Guide

A comprehensive guide to integrating DocCentral into your React application from start to finish.

Prerequisites

Before starting, ensure you have completed the installation and have your API key ready.

Integration Overview

This guide walks you through building a complete document signing flow with the following features:

  • Loading documents from your backend
  • Displaying the PDF with form fields
  • Capturing signatures
  • Submitting completed documents
  • Handling success and error states

Step 1: Set Up the Provider

Create a providers file that wraps your application with the DocCentral provider:

app/providers.tsxtypescript
1'use client';
2
3import { DocCentralProvider } from '@doccentral/react';
4import { ReactNode, useState, useEffect } from 'react';
5
6export function Providers({ children }: { children: ReactNode }) {
7  const [mounted, setMounted] = useState(false);
8
9  // Avoid hydration mismatch
10  useEffect(() => {
11    setMounted(true);
12  }, []);
13
14  if (!mounted) {
15    return null;
16  }
17
18  return (
19    <DocCentralProvider
20      apiKey={process.env.NEXT_PUBLIC_DOCCENTRAL_API_KEY!}
21      debug={process.env.NODE_ENV === 'development'}
22      onReady={() => {
23        console.log('DocCentral SDK ready');
24      }}
25      onError={(error) => {
26        console.error('DocCentral SDK error:', error);
27      }}
28    >
29      {children}
30    </DocCentralProvider>
31  );
32}

Step 2: Create the Document Service

Create a service to fetch document details from your backend:

lib/documents.tstypescript
export interface Document {
  id: string;
  title: string;
  url: string;
  fields: FieldDefinition[];
  status: 'draft' | 'pending' | 'completed';
}

export async function getDocument(documentId: string): Promise<Document> {
  const response = await fetch(`/api/documents/${documentId}`);
  
  if (!response.ok) {
    throw new Error('Failed to fetch document');
  }
  
  return response.json();
}

export async function completeDocument(
  documentId: string,
  submissionData: {
    submittedAt: string;
    checksum: string;
  }
): Promise<void> {
  const response = await fetch(`/api/documents/${documentId}/complete`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(submissionData),
  });
  
  if (!response.ok) {
    throw new Error('Failed to complete document');
  }
}

Step 3: Build the Document Viewer Component

components/document-viewer.tsxtypescript
1'use client';
2
3import { useState, useEffect } from 'react';
4import { PDFViewer, useSDK, type DocumentSubmissionPayload } from '@doccentral/react';
5import { getDocument, completeDocument, type Document } from '@/lib/documents';
6
7interface DocumentViewerProps {
8  documentId: string;
9  onComplete?: () => void;
10}
11
12export function DocumentViewer({ documentId, onComplete }: DocumentViewerProps) {
13  const { isInitialized, validationError } = useSDK();
14  const [document, setDocument] = useState<Document | null>(null);
15  const [loading, setLoading] = useState(true);
16  const [error, setError] = useState<string | null>(null);
17  const [submitted, setSubmitted] = useState(false);
18
19  // Fetch document on mount
20  useEffect(() => {
21    if (!isInitialized) return;
22
23    async function fetchDocument() {
24      try {
25        const doc = await getDocument(documentId);
26        setDocument(doc);
27      } catch (err) {
28        setError(err instanceof Error ? err.message : 'Failed to load document');
29      } finally {
30        setLoading(false);
31      }
32    }
33
34    fetchDocument();
35  }, [documentId, isInitialized]);
36
37  // Handle submission
38  const handleSubmit = async (payload: DocumentSubmissionPayload) => {
39    try {
40      // Notify your backend
41      await completeDocument(documentId, {
42        submittedAt: payload.submittedAt,
43        checksum: payload.checksum,
44      });
45      
46      setSubmitted(true);
47      onComplete?.();
48    } catch (err) {
49      throw err; // Let the SDK handle the error display
50    }
51  };
52
53  // Loading state
54  if (loading || !isInitialized) {
55    return (
56      <div className="flex items-center justify-center h-96">
57        <div className="animate-spin h-8 w-8 border-4 border-blue-500 border-t-transparent rounded-full" />
58      </div>
59    );
60  }
61
62  // Error state
63  if (error || validationError) {
64    return (
65      <div className="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
66        <h3 className="text-red-800 font-semibold">Error Loading Document</h3>
67        <p className="text-red-600 mt-2">{error || validationError}</p>
68        <button
69          onClick={() => window.location.reload()}
70          className="mt-4 px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700"
71        >
72          Try Again
73        </button>
74      </div>
75    );
76  }
77
78  // Success state
79  if (submitted) {
80    return (
81      <div className="bg-green-50 border border-green-200 rounded-lg p-6 text-center">
82        <div className="text-green-500 text-5xl mb-4">✓</div>
83        <h3 className="text-green-800 font-semibold text-xl">
84          Document Submitted Successfully!
85        </h3>
86        <p className="text-green-600 mt-2">
87          Your signed document has been processed and saved.
88        </p>
89      </div>
90    );
91  }
92
93  // Render viewer
94  if (!document) return null;
95
96  return (
97    <div className="h-[80vh] border rounded-lg overflow-hidden">
98      <PDFViewer
99        documentId={document.id}
100        documentUrl={document.url}
101        initialFields={document.fields}
102        onSubmit={handleSubmit}
103        onSubmitError={(err) => {
104          setError(err.message);
105        }}
106      />
107    </div>
108  );
109}

Step 4: Create the Page

app/documents/[id]/page.tsxtypescript
import { DocumentViewer } from '@/components/document-viewer';
import { Suspense } from 'react';

interface DocumentPageProps {
  params: { id: string };
}

export default function DocumentPage({ params }: DocumentPageProps) {
  return (
    <div className="container mx-auto py-8 px-4">
      <header className="mb-6">
        <h1 className="text-2xl font-bold">Review & Sign Document</h1>
        <p className="text-gray-600 mt-1">
          Please review the document and fill in all required fields.
        </p>
      </header>

      <Suspense fallback={<DocumentSkeleton />}>
        <DocumentViewer
          documentId={params.id}
          onComplete={() => {
            // Navigate to success page or show notification
            console.log('Document completed!');
          }}
        />
      </Suspense>
    </div>
  );
}

function DocumentSkeleton() {
  return (
    <div className="h-[80vh] border rounded-lg bg-gray-100 animate-pulse" />
  );
}

Step 5: Create the API Routes

app/api/documents/[id]/route.tstypescript
import { NextResponse } from 'next/server';

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  // Fetch document from your database
  const document = await prisma.document.findUnique({
    where: { id: params.id },
    include: { fields: true },
  });

  if (!document) {
    return NextResponse.json(
      { error: 'Document not found' },
      { status: 404 }
    );
  }

  // Generate signed URL for PDF
  const url = await generateSignedUrl(document.s3Key);

  return NextResponse.json({
    id: document.id,
    title: document.title,
    url,
    fields: document.fields,
    status: document.status,
  });
}
app/api/documents/[id]/complete/route.tstypescript
import { NextResponse } from 'next/server';

export async function POST(
  request: Request,
  { params }: { params: { id: string } }
) {
  const body = await request.json();

  // Update document status
  await prisma.document.update({
    where: { id: params.id },
    data: {
      status: 'completed',
      completedAt: new Date(body.submittedAt),
      checksum: body.checksum,
    },
  });

  // Trigger any post-completion workflows
  await sendCompletionNotification(params.id);

  return NextResponse.json({ success: true });
}

Complete Flow Diagram

┌─────────────────────────────────────────────────────────────┐
│                        User Flow                             │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  1. User navigates to /documents/[id]                       │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  2. DocCentralProvider validates API key                     │
│     POST /sdk/validate-key                                  │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  3. DocumentViewer fetches document details                 │
│     GET /api/documents/[id]                                 │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  4. PDFViewer loads and renders PDF                         │
│     - Displays form fields                                  │
│     - User fills fields and signs                           │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  5. User clicks Submit                                      │
│     - SDK validates all required fields                     │
│     - SDK sends submission to DocCentral API                │
│     POST /sdk/documents/submit                              │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  6. Your backend is notified                                │
│     POST /api/documents/[id]/complete                       │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  7. Success! Document is completed                          │
└─────────────────────────────────────────────────────────────┘

Next Steps

Now that you have a basic integration working, explore these advanced topics: