Package Exports
- @epurchasepower/can-action-carousel
- @epurchasepower/can-action-carousel/dist/production/can-plugin.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@epurchasepower/can-action-carousel) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
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.
Embedding
🚀 Simple Script Embedding
Just add one script tag to any website:
<script
src="https://unpkg.com/@epurchasepower/can-action-carousel@latest/dist/production/embed-script.js"
data-client-id="can"
data-campaign-code="173">
</script>🎯 Container-Specific Embedding
Target a specific container element:
<div data-climate-action></div>
<script src="https://unpkg.com/@epurchasepower/can-action-carousel@latest/dist/production/embed-script.js"></script>📖 Complete Integration Guide →
⚛️ Direct React Integration
For React applications, import and use directly:
import { ClimateActionPlugin } from '@epurchasepower/can-action-carousel'
<ClimateActionPlugin clientId="your-client-id" theme="light" />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 (light/dark)
- Mock Data Support: Development mode with simulated API responses
- Existing Account Login: Seamless migration from anonymous to existing accounts via security code verification
- Multi-Environment Builds: Separate builds for development, staging, and production
- Auto-Resize: Dynamic iframe height adjustment
- Parameter Validation: Comprehensive input validation and sanitization
- Interactive Release: Version selection prompts for patch/minor/major updates
- Default Configuration: clientId='can', campaignCode='173' for easy testing
Development
Prerequisites
- Node.js 18+
- pnpm
Setup
# Install dependencies
pnpm install
# Standard Development
pnpm dev # Start development server
pnpm build # Build for production
pnpm preview # Preview production build
# Iframe Development
pnpm dev:iframe # Start iframe development server (port 5174)
pnpm build:dev # Build development iframe version
pnpm build:staging # Build staging iframe version
pnpm build:production # Build production iframe version
pnpm build:all # Build all iframe environments
# Testing
pnpm test # Run tests
pnpm test:iframe:local # Open test-iframe-local.html (requires dev:iframe running)
pnpm test:iframe:staging # Open test-iframe-staging.html (tests unpkg staging)
pnpm test:iframe:production # Open test-iframe-production.html (tests unpkg production)
# Quality Checks
pnpm check-types # TypeScript type checking
pnpm lint # ESLint linting
# Publishing (Interactive)
pnpm release # Interactive release of both staging + production
pnpm release:staging # Interactive staging release (prompts for patch/minor/major)
pnpm release:production # Interactive production release (prompts for patch/minor/major)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)
cloudflaredinstalled (e.g.,brew install cloudflared)
One-time login:
pnpm --filter @climate-action-now/web-plugin run cf:loginRun 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:tunnelStart Vite with remote HMR pointing at your tunnel host:
# in a separate terminal
cd frontends/web-plugin
PUBLIC_TUNNEL_HOST=$CF_HOSTNAME pnpm devYou 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 propsArchitecture
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 configurationKey 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-0101Authentication
Existing Account Login Flow
The web plugin supports seamless migration from anonymous to existing accounts through a security code verification flow:
How It Works
- Email Detection: When an anonymous user enters an email that matches an existing account, the system automatically detects it
- Security Code: A 5-digit security code is sent to the user's email
- Verification: User enters the code to verify ownership
- Migration: Anonymous user's actions (deeds) are migrated to the existing account
- 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 emailPetitionSignForm- For petition signingEditProfileForm- 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:coverageTest 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 bundleEmbedding 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
- Create a feature branch
- Make your changes
- Write/update tests
- Run
pnpm lintandpnpm check-types - Follow Accessibility Guidelines for UI components
- Submit a pull request
License
[License information here]