JSPM

  • Created
  • Published
  • Downloads 363
  • Score
    100M100P100Q99339F
  • License ISC

Embeddable web component for Climate Action Now carousel

Package Exports

  • @epurchasepower/can-action-carousel

Readme

Climate Action Now Web Plugin

A React-based web plugin for embedding climate action cards into any website. Built with TypeScript, Vite, and Tailwind CSS.

Features

  • Action Cards: Display climate actions with rich UI components
  • Action Flows: Multi-step flows for petition signing and learning actions
  • Action Completion Tracking: Shows when users have completed actions with date-specific messages
  • Responsive Design: Works seamlessly on desktop and mobile devices
  • Theme Support: Customizable themes for different tenants
  • Mock Data Support: Development mode with simulated API responses
  • Existing Account Login: Seamless migration from anonymous to existing accounts via security code verification

Development

Prerequisites

  • Node.js 18+
  • pnpm

Setup

# Install dependencies
pnpm install

# Start development server
pnpm dev

# Run tests
pnpm test

# Run tests in watch mode
pnpm test:watch

# Build for production
pnpm build

# Type checking
pnpm check-types

# Linting
pnpm lint

# Preview production build
pnpm preview

Stable public URL (Cloudflare Tunnel)

Use Cloudflare Tunnel to get a stable HTTPS URL (free) for localhost:5173.

Prerequisites:

  • A domain managed by Cloudflare (free plan works)
  • cloudflared installed (e.g., brew install cloudflared)

One-time login:

pnpm --filter @climate-action-now/web-plugin run cf:login

Run a named tunnel and route a hostname to your local Vite server:

# choose a hostname you control in Cloudflare DNS, e.g. dev-plugin.yourdomain.com
export CF_HOSTNAME=dev-plugin.yourdomain.com

# starts Cloudflare Tunnel (keeps running) and maps hostname → http://localhost:5173
pnpm --filter @climate-action-now/web-plugin run cf:tunnel

Start Vite with remote HMR pointing at your tunnel host:

# in a separate terminal
cd frontends/web-plugin
PUBLIC_TUNNEL_HOST=$CF_HOSTNAME pnpm dev

You can now open https://$CF_HOSTNAME from any device and Vite HMR will work over WSS.

Configuration

The web plugin supports flexible configuration for both development and production. See CONFIGURATION.md for detailed configuration options.

Quick Start

Create a .env.development file based on .env.example:

VITE_API_BASE_URL=http://localhost:8040
VITE_TENANT_ID=can  # Optional - can be provided via props
VITE_CAMPAIGN_CODE=climate-2024  # Optional - can be provided via props

Architecture

Directory Structure

src/
├── components/         # React components
│   ├── ActionCard/    # Action card components
│   ├── ui/            # Reusable UI components
│   └── ...
├── features/          # Feature-specific modules
│   ├── actions/       # Action-related features
│   ├── carousel/      # Carousel functionality
│   └── ...
├── hooks/             # Custom React hooks
├── services/          # API services
├── types/             # TypeScript type definitions
├── themes/            # Theme configurations
└── config/            # Application configuration

Key Components

ActionCard

The main component for displaying climate actions:

<ActionCard
  action={action}
  onComplete={(actionId) => handleComplete(actionId)}
  onSkip={() => handleSkip()}
  onShare={() => handleShare()}
  onShowWho={() => handleShowWho()}
  onShowWhy={() => handleShowWhy()}
  currentUser={currentUser}
/>

CompletionTag

Displays action completion status with date-aware messages:

  • "You already took this action today."
  • "You took this action yesterday."
  • "You took this action on MM/DD/YY."

Also shows when actions can be repeated:

  • "You can take it again if you want."
  • "You can take it again tomorrow."
  • "You can take it again on MM/DD/YY."

TellFlow

Manages the Tell action flow for contacting elected officials:

<TellFlow
  action={tellAction}
  onClose={(completed) => handleClose(completed)}
  onSkip={() => handleSkip()}
/>

Features:

  • Address collection form
  • Representative lookup
  • Email and phone contact methods
  • Template personalization
  • Progress tracking across multiple recipients

API Integration

Action History

Fetch action completion history:

import { useActionHistory } from '~/hooks/useActionHistory'

const { data, isLoading, error } = useActionHistory(actionId)

Creating Deeds

Mark actions as completed:

import { useCreateDeed } from '~/hooks/useActionHistory'

const createDeedMutation = useCreateDeed()

// Mark action as completed
await createDeedMutation.mutateAsync({
  actionId: 'action-123',
  extraData: { source: 'web' },
})

Services

ActionsService

Handles all action-related API calls:

// Get actions
const actions = await actionsService.getActions({
  page: 1,
  pageSize: 10,
})

// Get action history
const history = await actionsService.getActionHistory(actionId)

// Create deed (mark as completed)
const deed = await actionsService.createDeed(actionId, extraData)

RepresentativeService

Fetches elected officials based on user address:

// Get representatives
const reps = await representativeService.getRepresentatives(address, tellAction)

// Validate address
const isValid = representativeService.validateAddress(address)

// Parse address components
const parsed = representativeService.parseAddress(address)

TemplateService

Personalizes message templates with merge fields:

// Personalize template
const { content, missingFields } = personalizeTemplate(template, {
  recipient,
  user,
})

// Format phone number
const formatted = formatPhoneNumber('2025550101')
// Returns: (202) 555-0101

Authentication

Existing Account Login Flow

The web plugin supports seamless migration from anonymous to existing accounts through a security code verification flow:

How It Works

  1. Email Detection: When an anonymous user enters an email that matches an existing account, the system automatically detects it
  2. Security Code: A 5-digit security code is sent to the user's email
  3. Verification: User enters the code to verify ownership
  4. Migration: Anonymous user's actions (deeds) are migrated to the existing account
  5. Auto-Login: User is automatically logged in to their existing account

Implementation

The login flow uses these key components:

useEmailValidation Hook

import { useEmailValidation } from '~/hooks/useEmailValidation'

function MyForm() {
  const {
    checkEmail,
    showSecurityCodeModal,
    currentEmail,
    closeModal,
    handleValidationSuccess
  } = useEmailValidation({
    onValidationComplete: () => {
      // Continue with form submission
    }
  })

  return (
    <>
      <input
        type="email"
        onChange={(e) => checkEmail(e.target.value)}
      />

      {showSecurityCodeModal && (
        <SecurityCodeModal
          isOpen={showSecurityCodeModal}
          email={currentEmail}
          onClose={closeModal}
          onSuccess={handleValidationSuccess}
        />
      )}
    </>
  )
}

SecurityCodeModal Component

  • Handles the complete security code flow
  • Multi-step UI: explanation → code entry → validation → success
  • Includes resend functionality with rate limiting
  • Fully accessible with keyboard navigation

Integration Points

The email validation is integrated into these forms:

  • RequiredFieldsForm - For tell actions requiring email
  • PetitionSignForm - For petition signing
  • EditProfileForm - For profile updates (if email collection is added)

Testing

The project uses Vitest for unit testing:

# Run all tests
pnpm test

# Run tests in watch mode
pnpm test:watch

# Run tests with coverage
pnpm test:coverage

Test Structure

  • Component tests: src/components/**/__tests__/
  • Hook tests: src/hooks/__tests__/
  • Service tests: src/services/__tests__/

Building for Production

# Build the plugin
pnpm build

# The output will be in dist/
# - dist/web-plugin.js - The main JavaScript bundle
# - dist/web-plugin.css - The CSS bundle

Embedding the Plugin

To embed the plugin in a website:

<!-- Add the CSS -->
<link rel="stylesheet" href="path/to/web-plugin.css" />

<!-- Add the JavaScript -->
<script src="path/to/web-plugin.js"></script>

<!-- Add the container element -->
<div id="climate-action-plugin"></div>

<!-- Initialize the plugin -->
<script>
  ClimateActionPlugin.init({
    tenantId: 'your-tenant-id',
    container: '#climate-action-plugin',
  })
</script>

Configuration

Theme Configuration

Themes can be configured per tenant in src/themes/:

export const customTheme: Theme = {
  colors: {
    primary: '#00A651',
    secondary: '#0096A3',
    accent: {
      warning: '#F7931E',
      error: '#FF0000',
      success: '#00A651',
    },
  },
  fonts: {
    heading: 'Roboto, sans-serif',
    body: 'Open Sans, sans-serif',
  },
}

API Configuration

Configure API endpoints in src/config/config.ts:

export const config = {
  api: {
    baseUrl: import.meta.env.VITE_API_BASE_URL,
    actionsEndpoint: '/v1/actions',
  },
  useMockData: import.meta.env.VITE_USE_MOCK_DATA === 'true',
}

Contributing

  1. Create a feature branch
  2. Make your changes
  3. Write/update tests
  4. Run pnpm lint and pnpm check-types
  5. Follow Accessibility Guidelines for UI components
  6. Submit a pull request

License

[License information here]