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: