← back to Patty

app/petitions/[slug]/SignatureForm.tsx

286 lines

'use client';

import { useState, FormEvent } from 'react';

interface SignatureFormProps {
  petitionId: string;
  petitionTitle: string;
  isActive: boolean;
}

export default function SignatureForm({ petitionId, petitionTitle, isActive }: SignatureFormProps) {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [zip, setZip] = useState('');
  const [comment, setComment] = useState('');
  const [optedIn, setOptedIn] = useState(false);
  const [honeypot, setHoneypot] = useState('');
  const [submitting, setSubmitting] = useState(false);
  const [result, setResult] = useState<{ success: boolean; message: string; signatureCount?: number } | null>(null);

  if (!isActive) {
    return (
      <div style={{
        backgroundColor: 'var(--color-surface)',
        border: '1px solid var(--color-border)',
        borderRadius: 'var(--radius-lg)',
        padding: '2rem',
        textAlign: 'center',
      }}>
        <div style={{ fontSize: '2rem', marginBottom: '0.75rem' }}>
          {/* Lock icon via SVG */}
          <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="var(--color-text-muted)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ display: 'inline-block' }}>
            <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
            <path d="M7 11V7a5 5 0 0 1 10 0v4" />
          </svg>
        </div>
        <h3 style={{ color: 'var(--color-text)', fontSize: '1.125rem', fontWeight: 600, marginBottom: '0.5rem' }}>
          This petition is not currently accepting signatures
        </h3>
        <p style={{ color: 'var(--color-text-muted)', fontSize: '0.875rem' }}>
          The petition may be under review, paused, or has been closed. Check back later.
        </p>
      </div>
    );
  }

  async function handleSubmit(e: FormEvent) {
    e.preventDefault();
    if (honeypot) return; // bot detected

    if (!name.trim()) {
      setResult({ success: false, message: 'Please enter your name.' });
      return;
    }
    if (!email.trim() || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
      setResult({ success: false, message: 'Please enter a valid email address.' });
      return;
    }

    setSubmitting(true);
    setResult(null);

    try {
      const res = await fetch('/api/public/signatures', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          petition_id: petitionId,
          signer_name: name.trim(),
          signer_email: email.trim().toLowerCase(),
          signer_zip: zip.trim() || null,
          signer_comment: comment.trim() || null,
          opted_in_email: optedIn,
          _hp: honeypot, // honeypot field
        }),
      });

      const data = await res.json();

      if (res.ok) {
        setResult({
          success: true,
          message: `Thank you for signing! You are signature #${data.signatureCount}.`,
          signatureCount: data.signatureCount,
        });
        // Reset form
        setName('');
        setEmail('');
        setZip('');
        setComment('');
        setOptedIn(false);
      } else {
        setResult({
          success: false,
          message: data.error || 'Something went wrong. Please try again.',
        });
      }
    } catch {
      setResult({
        success: false,
        message: 'Network error. Please check your connection and try again.',
      });
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <div style={{
      backgroundColor: 'var(--color-surface)',
      border: '1px solid var(--color-border)',
      borderRadius: 'var(--radius-lg)',
      padding: '1.5rem',
    }}>
      <h3 style={{
        color: 'var(--color-text)',
        fontSize: '1.25rem',
        fontWeight: 700,
        marginBottom: '1.25rem',
      }}>
        Sign This Petition
      </h3>

      {result && (
        <div style={{
          padding: '0.75rem 1rem',
          borderRadius: 'var(--radius-md)',
          marginBottom: '1rem',
          backgroundColor: result.success ? 'rgba(34, 197, 94, 0.15)' : 'rgba(239, 68, 68, 0.15)',
          border: `1px solid ${result.success ? 'rgba(34, 197, 94, 0.3)' : 'rgba(239, 68, 68, 0.3)'}`,
          color: result.success ? 'var(--color-success)' : 'var(--color-error)',
          fontSize: '0.875rem',
        }}>
          {result.message}
        </div>
      )}

      {result?.success ? (
        <div style={{ textAlign: 'center', padding: '1rem 0' }}>
          <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--color-success)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ display: 'inline-block', marginBottom: '0.75rem' }}>
            <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
            <polyline points="22 4 12 14.01 9 11.01" />
          </svg>
          <p style={{ color: 'var(--color-text)', fontWeight: 600, fontSize: '1rem', marginBottom: '0.5rem' }}>
            Your signature has been recorded!
          </p>
          <p style={{ color: 'var(--color-text-secondary)', fontSize: '0.875rem' }}>
            Share this petition to help it reach its goal.
          </p>
        </div>
      ) : (
        <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
          {/* Honeypot - hidden from humans */}
          <div style={{ position: 'absolute', left: '-9999px', opacity: 0, height: 0, overflow: 'hidden' }} aria-hidden="true">
            <label htmlFor="website_url">Website</label>
            <input
              id="website_url"
              name="website_url"
              type="text"
              tabIndex={-1}
              autoComplete="off"
              value={honeypot}
              onChange={(e) => setHoneypot(e.target.value)}
            />
          </div>

          <div>
            <label htmlFor="signer_name" style={{ display: 'block', color: 'var(--color-text-secondary)', fontSize: '0.875rem', fontWeight: 500, marginBottom: '0.375rem' }}>
              Full Name <span style={{ color: 'var(--color-error)' }}>*</span>
            </label>
            <input
              id="signer_name"
              type="text"
              className="input"
              placeholder="Your full name"
              value={name}
              onChange={(e) => setName(e.target.value)}
              required
              disabled={submitting}
            />
          </div>

          <div>
            <label htmlFor="signer_email" style={{ display: 'block', color: 'var(--color-text-secondary)', fontSize: '0.875rem', fontWeight: 500, marginBottom: '0.375rem' }}>
              Email Address <span style={{ color: 'var(--color-error)' }}>*</span>
            </label>
            <input
              id="signer_email"
              type="email"
              className="input"
              placeholder="your@email.com"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              required
              disabled={submitting}
            />
            <p style={{ color: 'var(--color-text-muted)', fontSize: '0.75rem', marginTop: '0.25rem' }}>
              Your email is used for verification only and will not be displayed publicly.
            </p>
          </div>

          <div>
            <label htmlFor="signer_zip" style={{ display: 'block', color: 'var(--color-text-secondary)', fontSize: '0.875rem', fontWeight: 500, marginBottom: '0.375rem' }}>
              ZIP Code <span style={{ color: 'var(--color-text-muted)', fontWeight: 400 }}>(optional)</span>
            </label>
            <input
              id="signer_zip"
              type="text"
              className="input"
              placeholder="e.g. 90210"
              value={zip}
              onChange={(e) => setZip(e.target.value)}
              maxLength={10}
              disabled={submitting}
              style={{ maxWidth: '12rem' }}
            />
          </div>

          <div>
            <label htmlFor="signer_comment" style={{ display: 'block', color: 'var(--color-text-secondary)', fontSize: '0.875rem', fontWeight: 500, marginBottom: '0.375rem' }}>
              Comment <span style={{ color: 'var(--color-text-muted)', fontWeight: 400 }}>(optional)</span>
            </label>
            <textarea
              id="signer_comment"
              className="input"
              placeholder="Why are you signing this petition?"
              value={comment}
              onChange={(e) => setComment(e.target.value)}
              rows={3}
              maxLength={1000}
              disabled={submitting}
              style={{ resize: 'vertical', minHeight: '4rem' }}
            />
          </div>

          <div style={{ display: 'flex', alignItems: 'flex-start', gap: '0.5rem' }}>
            <input
              id="opted_in_email"
              type="checkbox"
              checked={optedIn}
              onChange={(e) => setOptedIn(e.target.checked)}
              disabled={submitting}
              style={{
                marginTop: '0.125rem',
                accentColor: 'var(--color-primary)',
                width: '1rem',
                height: '1rem',
                cursor: 'pointer',
              }}
            />
            <label htmlFor="opted_in_email" style={{ color: 'var(--color-text-secondary)', fontSize: '0.875rem', cursor: 'pointer' }}>
              Keep me updated on this petition and related campaigns
            </label>
          </div>

          <button
            type="submit"
            disabled={submitting}
            className="btn btn-primary btn-lg"
            style={{
              width: '100%',
              fontSize: '1rem',
              fontWeight: 700,
              padding: '0.875rem 1.5rem',
              marginTop: '0.25rem',
            }}
          >
            {submitting ? (
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.5rem' }}>
                <span className="spinner" style={{ width: '1rem', height: '1rem', borderWidth: '2px' }} />
                Signing...
              </span>
            ) : (
              'Sign This Petition'
            )}
          </button>

          <p style={{ color: 'var(--color-text-muted)', fontSize: '0.75rem', textAlign: 'center', lineHeight: 1.4 }}>
            By signing, you agree that your name may be publicly listed as a supporter. Your email will never be shared.
          </p>
        </form>
      )}
    </div>
  );
}