350 lines
12 KiB
TypeScript
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>
|
|
);
|
|
}
|