AgentSkillsCN

Create Online Contract

如何利用数字签名、PDF 生成与 Supabase 集成,打造合法有效的在线合同系统。

SKILL.md
--- frontmatter
description: How to create a legally binding online contract system with digital signature, PDF generation, and Supabase integration.

Online Contract Creation & Digital Signature Skill

This skill documents how to build an online contract signing flow using React, Supabase, and react-to-print. It is based on the implementation in the Wise Wolf project.

1. Architecture Overview

The system consists of three main parts:

  1. Data Collection (Form): Collects user data (name, CPF, address, plan details) before the contract is shown.
  2. Contract Rendering (Visual): A component that renders the contract text with dynamic data inserted. This component is what gets "printed" or converted to PDF.
  3. Signature Modal (Interaction): A modal that overlays the screen, shows the contract, and captures the user's intent to sign (checkbox + typing name).

2. Dependencies

Ensure you have the following installed:

  • react-to-print: For handling the printing/PDF generation logic.
  • lucide-react: For icons (ShieldCheck, Lock, etc.).
  • Google Fonts (optional): 'Dancing Script' for a realistic signature look.
bash
npm install react-to-print lucide-react

3. Implementation Steps

Step 1: Create the Contract Document Component

Create a component (e.g., ContractDocument.tsx) that acts as the "paper" contract. It should accept all contract data as props.

Key Features:

  • use ref to allow react-to-print to grab the content.
  • Use explicit inline styles or Tailwind classes for A4 dimensions (w-[210mm] min-h-[297mm]).
  • Include standard contract clauses.
  • Dynamic Signature Area: Render the signature based on whether it's signed (acceptedAt prop is present).
tsx
// Example Snippet from ContractDocument.tsx
export function ContractDocument({ studentName, acceptedAt, userIp, ...props }) {
  const componentRef = useRef(null);
  
  // React-to-print hook (optional here if controlled by parent)
  
  return (
    <div ref={componentRef} className="w-[210mm] bg-white p-[25mm] ...">
       {/* Contract Text */}
       <h1>CONTRACT OF SERVICES</h1>
       <p>I, {studentName}, ...</p>
       
       {/* Signature Section */}
       <div className="signature-area">
          {acceptedAt ? (
             <div className="digital-signature">
                <span className="cursive-font">{studentName}</span>
                <div className="metadata">
                   IP: {userIp} | Date: {acceptedAt}
                </div>
             </div>
          ) : (
             <span>Waiting for signature...</span>
          )}
       </div>
    </div>
  );
}

Step 2: Create the Signature Modal

Create a modal (e.g., ContractModal.tsx) that forces the user to review and sign.

Key Logic:

  • Validation: Force the user to type their name exactly as it appears in the contract. This acts as a conscious act of signing.
  • Consent: A checkbox "I have read and agree...".
  • Visual Feedback: Show a live preview of the "cursive" signature using a font like Dancing Script.
  • Submission: When confirmed, pass a signatureData object back to the parent.
tsx
// Example Snippet from ContractModal.tsx
const handleConfirm = () => {
    if (accepted && typedName.trim().toLowerCase() === studentName.trim().toLowerCase()) {
        onConfirm({ 
            type: 'DIGITAL', 
            typedName: typedName.trim(),
            timestamp: new Date().toISOString() 
        });
    }
};

Step 3: Main Flow Integration (Registration Page)

In your main form (e.g., PublicRegistration.tsx):

  1. State Management:

    • step: 'FORM' -> 'CONTRACT' -> 'SUCCESS'.
    • signatureData: Store IP, timestamp, and subscription ID after signing.
  2. Process:

    • User fills form -> Clicks "Next".
    • step becomes 'CONTRACT'. Modal opens.
    • User signs -> handleRegister is called.
    • Database Save: Create the user profile in Supabase with contract_accepted: true, accepted_at, and signature_ip.
    • Backend Validation: Ensure your backend/Edge Function validates that these fields are present.
  3. PDF Generation (Post-Signing):

    • After success, render the ContractDocument again (hidden or in a modal) with the acceptedAt and signatureData props populated.
    • Use react-to-print to allow the user to download the signed copy.

4. Database Schema (Supabase)

Ensure your profiles table (or contracts table) has columns for audit trails:

sql
alter table profiles 
add column contract_accepted boolean default false,
add column accepted_at timestamptz,
add column signature_ip text,
add column signed_document_url text; -- Optional if storing the PDF file itself

5. Security & Legal Validity (Brazil - MP 2.200-2/2001)

To ensure legal validity (binding non-repudiation), you must capture:

  1. Identity: Authentication (login) or strict personal data validation (CPF, Name match).
  2. Intent: The mechanics of checking "I Agree" and typing the name acting as the signature.
  3. Integrity: Generate a hash or store the exact data state at the time of signing (we use the subscriptionId or a generated UUID as a token).
  4. Traceability: Capture IP address and accurate Timestamp.

6. Edge Cases

  • User edits name: If the user changes their name in the form, the contract must re-render with the new name.
  • Mobile responsiveness: The ContractDocument is fixed A4 size 210mm. On mobile, scale it down using CSS transform: scale(0.6) in the preview modal so it fits the screen.

Example Usage:

See components/PublicRegistration.tsx for the orchestration and components/ContractDocument.tsx for the template.