port-nimara-client-portal/docs/website-implementation-guid...

9.3 KiB

Website Implementation Guide for Embedded Documenso Signing

Task Summary

Implement embedded Documenso signing on the Port Nimara website to provide a seamless, branded signing experience for Expression of Interest (EOI) documents.

Context

The Port Nimara client portal (separate repository) generates EOI documents via a self-hosted Documenso instance and now creates embedded signing URLs that should be handled by the main website at portnimara.com.

Technical Specifications

Website Stack

  • Framework: Nuxt 3 (v3.15.4) with Vue 3
  • Styling: Tailwind CSS
  • TypeScript: Enabled
  • Package to Install: @documenso/embed-vue

Documenso Configuration

  • Host: https://signatures.portnimara.dev
  • Self-hosted community edition (full embedding features available)
  • Authentication: Handled via tokens (no API keys needed on website)

Brand Assets

  • Logos: /images/logo-full.png, /images/logo-no-text.png, /images/logo-no-text-white.png
  • Primary Color: #0056b3
  • Background: #ffffff, #f8f9fa
  • Fonts: Custom fonts in /public/fonts/ (Bill Corporate, Garamond, Runalto)

URL Structure to Implement

Route Pattern

/sign/[type]/[token]

URL Examples

Client signing: https://portnimara.com/sign/client/ABC123
CC approval: https://portnimara.com/sign/cc/DEF456
Developer signing: https://portnimara.com/sign/developer/GHI789

Route Parameters

  • type: 'client' | 'cc' | 'developer'
  • token: Alphanumeric string from Documenso (e.g., ABC123, mK9N2pL5vX8)

Required Implementation

1. Install Dependencies

npm install @documenso/embed-vue

2. Create Main Signing Page

File: pages/sign/[type]/[token].vue

Key Requirements:

  • Validate route parameters (type and token)
  • Extract token and signer type from route
  • Display appropriate messaging per signer type
  • Embed Documenso signing interface
  • Handle successful signing with redirect
  • Apply Port Nimara branding throughout

Signer Type Messages:

const signerMessages = {
  client: {
    title: 'Sign Your Expression of Interest',
    subtitle: 'Please review and sign your Port Nimara berth EOI'
  },
  cc: {
    title: 'Approve Expression of Interest',
    subtitle: 'Please review and approve this Port Nimara berth EOI'
  },
  developer: {
    title: 'Review and Sign Expression of Interest',
    subtitle: 'Please review and sign this Port Nimara berth EOI'
  }
};

Embed Configuration:

// Use these exact values
const documensoHost = 'https://signatures.portnimara.dev';

// CSS variables for brand consistency
const cssVars = {
  primary: '#0056b3',
  primaryForeground: '#ffffff',
  background: '#ffffff',
  foreground: '#1a1a1a',
  secondary: '#f8f9fa',
  secondaryForeground: '#1a1a1a',
  accent: '#e9ecef',
  accentForeground: '#1a1a1a',
  destructive: '#dc3545',
  destructiveForeground: '#ffffff',
  border: '#dee2e6',
  input: '#ffffff',
  ring: '#0056b3',
  radius: '0.5rem'
};

3. Create Success Page

File: pages/sign/success.vue

Requirements:

  • Thank you message
  • Confirmation that document was signed
  • Link back to main Port Nimara website
  • Professional, clean design

4. Create Error Page

File: pages/sign/error.vue

Requirements:

  • Handle invalid/expired tokens
  • Provide contact information
  • Link to support or main website

5. Add Environment Configuration

Add to your .env file:

WEBHOOK_SECRET_SIGNING=96BQQRiKkTIN2w0rHbqo7yHggV/sT8702HtHih3uNSY=

Implementation Template

Main Signing Page Structure

<template>
  <div class="min-h-screen bg-gray-50">
    <!-- Header with Port Nimara branding -->
    <header class="bg-white shadow-sm border-b">
      <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
        <div class="flex items-center justify-between">
          <img src="/images/logo-full.png" alt="Port Nimara" class="h-12" />
          <div class="text-right">
            <h1 class="text-2xl font-bold text-gray-900">{{ pageTitle }}</h1>
            <p class="text-sm text-gray-600">{{ pageSubtitle }}</p>
          </div>
        </div>
      </div>
    </header>

    <!-- Main signing interface -->
    <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
      <div class="bg-white rounded-lg shadow-lg overflow-hidden">
        <!-- Loading state -->
        <div v-if="loading" class="flex items-center justify-center h-96">
          <div class="text-center">
            <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
            <p class="mt-4 text-gray-600">Loading document...</p>
          </div>
        </div>
        
        <!-- Embedded Documenso interface -->
        <EmbedSignDocument
          v-else
          :token="token"
          :host="documensoHost"
          :on-document-signed="handleDocumentSigned"
          :on-document-viewed="handleDocumentViewed"
          :dark-mode-disabled="false"
          class="w-full h-[800px]"
          :css-vars="cssVars"
        />
      </div>
    </main>
  </div>
</template>

<script setup>
import { EmbedSignDocument } from '@documenso/embed-vue';
// ... implementation details in full guide
</script>

Validation Requirements

Route Parameter Validation

// Validate signer type
const validSignerTypes = ['client', 'cc', 'developer'];
if (!validSignerTypes.includes(signerType.value)) {
  router.push('/sign/error');
}

// Validate token format (basic)
const tokenPattern = /^[a-zA-Z0-9_-]+$/;
if (!tokenPattern.test(token.value)) {
  router.push('/sign/error');
}

Event Handling

Document Signed Handler

const handleDocumentSigned = async () => {
  console.log('Document signed successfully');
  
  // Optional: Webhook notification (Phase 2)
  // [Webhook code - initially commented out]
  
  // Always redirect to success
  router.push('/sign/success');
};

Document Viewed Handler

const handleDocumentViewed = () => {
  console.log('Document viewed:', { token: token.value, type: signerType.value });
  // Optional: Analytics tracking
};

Mobile Responsiveness

Ensure the implementation works well on mobile devices:

/* Example responsive adjustments */
@media (max-width: 768px) {
  .signing-header h1 {
    font-size: 1.25rem;
  }
  
  .documenso-embed {
    height: 600px; /* Reduced height for mobile */
  }
}

Testing Approach

1. Manual Testing

Once implemented, you can test with these example URLs:

  • http://localhost:3000/sign/client/test123
  • http://localhost:3000/sign/cc/test456
  • http://localhost:3000/sign/developer/test789

2. Real Token Testing

After the client portal generates a real EOI, you'll receive actual tokens to test with.

CORS Requirements

The Documenso instance needs CORS headers to allow embedding. This is handled manually via nginx configuration on the server.

Expected CORS headers from signatures.portnimara.dev:

Access-Control-Allow-Origin: https://portnimara.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Credentials: true

Error Handling

Common Scenarios

  1. Invalid token: Redirect to error page
  2. Invalid signer type: Redirect to error page
  3. Expired token: Documenso will handle this within the embed
  4. Network issues: Show loading state, let Documenso handle retries

Success Criteria

Functionality:

  • Signing interface loads correctly
  • Different messaging for each signer type
  • Successful signing redirects to success page
  • Invalid URLs redirect to error page

User Experience:

  • Port Nimara branding throughout
  • Mobile responsive design
  • Fast loading times
  • Clear error messages

Technical:

  • Clean URL structure
  • Proper error handling
  • Console logging for debugging
  • Ready for webhook integration

Webhook Integration (Phase 2)

Initially implement with webhook code commented out

// Future webhook endpoint: 
// https://client-portal.portnimara.com/api/webhook/document-signed

const notifyPortal = async () => {
  try {
    await $fetch('https://client-portal.portnimara.com/api/webhook/document-signed', {
      method: 'POST',
      headers: {
        'x-webhook-secret': process.env.WEBHOOK_SECRET_SIGNING
      },
      body: {
        token: token.value,
        signerType: signerType.value,
        signedAt: new Date().toISOString()
      }
    });
  } catch (error) {
    console.error('Webhook failed:', error);
    // Don't block user flow
  }
};

Questions & Support

If you encounter any issues during implementation:

  1. CORS errors: The server-side CORS configuration may need adjustment
  2. Token format issues: Check that tokens are being extracted correctly
  3. Embedding not loading: Verify the Documenso host URL and package installation
  4. Styling issues: Refer to the CSS variables provided for brand consistency

Next Steps After Implementation

  1. Test with real tokens from generated EOIs
  2. Verify mobile experience on actual devices
  3. Coordinate with client portal for email template updates
  4. Monitor for any CORS issues in production
  5. Prepare for webhook integration in Phase 2

This guide provides everything needed to implement embedded signing on the Port Nimara website. The client portal changes are already complete and ready to generate the embedded URLs this implementation will handle.