350 lines
12 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { useMCPApp } from '../../hooks/useMCPApp';
import { PageHeader } from '../../components/layout/PageHeader';
import { Card } from '../../components/layout/Card';
import { ComplianceChecklist } from '../../components/shared/ComplianceChecklist';
export function App() {
const { app, isConnected, toolResult } = useMCPApp();
const [activeTab, setActiveTab] = useState<'optin' | 'privacy' | 'terms'>('optin');
const [landingPageHtml, setLandingPageHtml] = useState<string>('');
const [privacyPolicyHtml, setPrivacyPolicyHtml] = useState<string>('');
const [termsHtml, setTermsHtml] = useState<string>('');
useEffect(() => {
if (toolResult) {
if (toolResult.landingPageHtml) setLandingPageHtml(toolResult.landingPageHtml);
if (toolResult.privacyPolicyHtml) setPrivacyPolicyHtml(toolResult.privacyPolicyHtml);
if (toolResult.termsHtml) setTermsHtml(toolResult.termsHtml);
}
}, [toolResult]);
// Analyze HTML for compliance elements
const analyzeCompliance = (html: string) => {
const lower = html.toLowerCase();
return {
hasConsent: lower.includes('consent') || lower.includes('agree'),
hasFrequency: lower.includes('frequency') || lower.includes('messages per'),
hasRates: lower.includes('msg & data rates') || lower.includes('message and data rates'),
hasStop: lower.includes('stop') && lower.includes('opt'),
hasHelp: lower.includes('help'),
hasPrivacyLink: lower.includes('privacy') && (lower.includes('href') || lower.includes('link')),
hasTermsLink: lower.includes('terms') && (lower.includes('href') || lower.includes('link')),
hasCarrierDisclosure: lower.includes('carrier') || lower.includes('t-mobile') || lower.includes('at&t'),
hasContact: lower.includes('contact') || lower.includes('email') || lower.includes('phone'),
};
};
const compliance = analyzeCompliance(landingPageHtml);
const complianceItems = [
{
label: 'Explicit consent checkbox',
checked: compliance.hasConsent,
description: 'Clear opt-in mechanism for users',
},
{
label: 'Message frequency disclosed',
checked: compliance.hasFrequency,
description: 'How often users will receive messages',
},
{
label: 'Msg & data rates notice',
checked: compliance.hasRates,
description: 'Standard carrier charges disclosure',
},
{
label: 'STOP instructions',
checked: compliance.hasStop,
description: 'How to opt out of messages',
},
{
label: 'HELP instructions',
checked: compliance.hasHelp,
description: 'How to get support',
},
{
label: 'Privacy policy link',
checked: compliance.hasPrivacyLink,
description: 'Link to privacy policy',
},
{
label: 'Terms of service link',
checked: compliance.hasTermsLink,
description: 'Link to terms',
},
{
label: 'Carrier disclosure',
checked: compliance.hasCarrierDisclosure,
description: 'Carrier liability disclosure',
},
{
label: 'Business contact info',
checked: compliance.hasContact,
description: 'How to reach the business',
},
];
const tabs = [
{ id: 'optin' as const, label: 'Opt-In Page', content: landingPageHtml },
{ id: 'privacy' as const, label: 'Privacy Policy', content: privacyPolicyHtml },
{ id: 'terms' as const, label: 'Terms of Service', content: termsHtml },
];
const activeContent = tabs.find((t) => t.id === activeTab)?.content || '';
if (!isConnected) {
return (
<div style={{ padding: 'var(--spacing-8)', textAlign: 'center' }}>
<p>Connecting to MCP host...</p>
</div>
);
}
if (!landingPageHtml && !privacyPolicyHtml && !termsHtml) {
return (
<div style={{ minHeight: '100vh', background: 'var(--color-background-primary)' }}>
<PageHeader title="Landing Page Preview" subtitle="TCPA-compliant opt-in pages" />
<div style={{ padding: 'var(--spacing-8)', textAlign: 'center' }}>
<p style={{ color: 'var(--color-text-secondary)', marginBottom: 'var(--spacing-4)' }}>
No landing page data loaded
</p>
<p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-tertiary)' }}>
This app displays landing pages passed via tool result. Request a landing page from the model.
</p>
</div>
</div>
);
}
return (
<div style={{ minHeight: '100vh', background: 'var(--color-background-primary)' }}>
<PageHeader
title="Landing Page Preview"
subtitle="TCPA-compliant opt-in pages"
/>
<div style={{ padding: 'var(--spacing-6)' }}>
<div
style={{
display: 'grid',
gridTemplateColumns: '1fr 350px',
gap: 'var(--spacing-6)',
maxWidth: '1400px',
margin: '0 auto',
}}
>
{/* Main Preview */}
<div>
{/* Tab Bar */}
<div
style={{
display: 'flex',
gap: 'var(--spacing-2)',
borderBottom: `2px solid var(--color-border-secondary)`,
marginBottom: 'var(--spacing-4)',
}}
>
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
style={{
padding: 'var(--spacing-3) var(--spacing-5)',
fontSize: 'var(--font-size-base)',
fontWeight: 600,
background: 'transparent',
border: 'none',
borderBottom:
activeTab === tab.id ? '3px solid var(--color-accent-primary)' : '3px solid transparent',
color:
activeTab === tab.id ? 'var(--color-accent-primary)' : 'var(--color-text-secondary)',
cursor: 'pointer',
transition: 'all 0.2s',
marginBottom: '-2px',
}}
>
{tab.label}
</button>
))}
</div>
{/* Preview Frame */}
<Card padding="sm">
{activeContent ? (
<div
style={{
width: '100%',
minHeight: '600px',
background: 'white',
borderRadius: 'var(--border-radius-md)',
overflow: 'auto',
}}
>
<iframe
srcDoc={activeContent}
title={`${activeTab} preview`}
style={{
width: '100%',
minHeight: '600px',
border: 'none',
borderRadius: 'var(--border-radius-md)',
}}
sandbox="allow-same-origin"
/>
</div>
) : (
<div
style={{
padding: 'var(--spacing-8)',
textAlign: 'center',
color: 'var(--color-text-tertiary)',
minHeight: '600px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<p>No content available for {tabs.find((t) => t.id === activeTab)?.label}</p>
</div>
)}
</Card>
{/* HTML Source (collapsible) */}
{activeContent && (
<details
style={{
marginTop: 'var(--spacing-4)',
padding: 'var(--spacing-4)',
background: 'var(--color-background-secondary)',
borderRadius: 'var(--border-radius-lg)',
}}
>
<summary
style={{
cursor: 'pointer',
fontWeight: 600,
fontSize: 'var(--font-size-sm)',
color: 'var(--color-text-secondary)',
userSelect: 'none',
}}
>
View HTML Source
</summary>
<pre
style={{
marginTop: 'var(--spacing-4)',
padding: 'var(--spacing-4)',
background: 'var(--color-background-primary)',
borderRadius: 'var(--border-radius-md)',
fontSize: 'var(--font-size-xs)',
overflow: 'auto',
maxHeight: '400px',
}}
>
<code>{activeContent}</code>
</pre>
</details>
)}
</div>
{/* Sidebar - Compliance Checklist */}
<div>
<ComplianceChecklist items={complianceItems} />
<Card padding="md" className="mt-6">
<h4
style={{
fontSize: 'var(--font-size-base)',
fontWeight: 600,
marginBottom: 'var(--spacing-3)',
}}
>
About TCPA Compliance
</h4>
<p
style={{
fontSize: 'var(--font-size-sm)',
color: 'var(--color-text-secondary)',
lineHeight: 1.6,
margin: 0,
}}
>
These landing pages are automatically generated to meet TCPA (Telephone Consumer Protection Act)
requirements. All required elements are included to ensure regulatory compliance and successful A2P
registration with carriers.
</p>
</Card>
<Card padding="md" className="mt-4">
<h4
style={{
fontSize: 'var(--font-size-base)',
fontWeight: 600,
marginBottom: 'var(--spacing-3)',
}}
>
Page URLs
</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--spacing-2)' }}>
{landingPageHtml && (
<div>
<div style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-tertiary)' }}>
Opt-In Page
</div>
<div
style={{
fontSize: 'var(--font-size-sm)',
fontFamily: 'monospace',
color: 'var(--color-accent-primary)',
wordBreak: 'break-all',
}}
>
{toolResult?.landingPageUrl || '(pending)'}
</div>
</div>
)}
{privacyPolicyHtml && (
<div>
<div style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-tertiary)' }}>
Privacy Policy
</div>
<div
style={{
fontSize: 'var(--font-size-sm)',
fontFamily: 'monospace',
color: 'var(--color-accent-primary)',
wordBreak: 'break-all',
}}
>
{toolResult?.privacyPolicyUrl || '(pending)'}
</div>
</div>
)}
{termsHtml && (
<div>
<div style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-tertiary)' }}>
Terms of Service
</div>
<div
style={{
fontSize: 'var(--font-size-sm)',
fontFamily: 'monospace',
color: 'var(--color-accent-primary)',
wordBreak: 'break-all',
}}
>
{toolResult?.termsUrl || '(pending)'}
</div>
</div>
)}
</div>
</Card>
</div>
</div>
</div>
</div>
);
}