TypeScript Advanced - Standards
TypeScript Standards
Interface and Type Definitions
// types/campaign.types.ts
export interface EmailRecipient {
readonly email: string;
readonly name?: string;
readonly personalization?: PersonalizationData;
readonly tags?: string[];
readonly consent: ConsentSettings;
}
export interface PersonalizationData {
readonly [key: string]: string | number | boolean;
}
export interface CampaignSettings {
readonly analytics_enabled: boolean;
readonly ai_optimization: boolean;
readonly track_opens: boolean;
readonly track_clicks: boolean;
readonly batch_size?: number;
readonly delay_between_batches?: number;
}
export interface CreateCampaignRequest {
readonly name: string;
readonly subject: string;
readonly content: EmailContent;
readonly recipients: EmailRecipient[];
readonly settings: CampaignSettings;
readonly schedule?: ScheduleSettings;
}
export interface EmailContent {
readonly html: string;
readonly text: string;
readonly template_id?: string;
readonly variables?: TemplateVariable[];
}
export interface ScheduleSettings {
readonly send_at: Date;
readonly timezone: string;
readonly recurring?: RecurringSettings;
}
// Service implementation
export class CampaignService {
constructor(
private readonly apiClient: ApiClient,
private readonly analyticsService: AnalyticsService,
private readonly aiService: AIService
) {}
async createCampaign(
request: CreateCampaignRequest
): Promise<EmailCampaign> {
try {
// Validate request data
this.validateCampaignRequest(request);
// Track campaign creation event
await this.analyticsService.trackEvent('campaign_creation_started', {
userId: request.recipients[0]?.email, // Simplified for example
campaignType: request.settings.ai_optimization ? 'ai_optimized' : 'standard',
timestamp: new Date().toISOString()
});
// Apply AI optimization if enabled
let optimizedContent = request.content;
if (request.settings.ai_optimization) {
optimizedContent = await this.aiService.optimizeContent(
request.content,
request.recipients
);
}
// Create campaign with API
const campaign = await this.apiClient.post<EmailCampaign>('/api/v1/campaigns', {
...request,
content: optimizedContent
});
// Track successful creation
await this.analyticsService.trackEvent('campaign_created', {
campaignId: campaign.id,
hasAIOptimization: request.settings.ai_optimization,
recipientCount: request.recipients.length
});
return campaign;
} catch (error) {
await this.analyticsService.trackError('campaign_creation_failed', {
error: error.message,
requestType: 'create_campaign'
});
throw new CampaignCreationError(
'Failed to create campaign',
{ originalError: error }
);
}
}
private validateCampaignRequest(request: CreateCampaignRequest): void {
if (!request.name?.trim()) {
throw new ValidationError('Campaign name is required');
}
if (!request.subject?.trim()) {
throw new ValidationError('Campaign subject is required');
}
if (!request.content?.html?.trim()) {
throw new ValidationError('Campaign content HTML is required');
}
if (!request.recipients || request.recipients.length === 0) {
throw new ValidationError('At least one recipient is required');
}
// Validate recipients
for (const recipient of request.recipients) {
if (!this.isValidEmail(recipient.email)) {
throw new ValidationError(`Invalid email address: ${recipient.email}`);
}
}
}
private isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
}
// Custom error classes
export class CampaignCreationError extends Error {
constructor(
message: string,
public readonly context?: Record<string, any>
) {
super(message);
this.name = 'CampaignCreationError';
}
}
export class ValidationError extends Error {
constructor(
message: string,
public readonly field?: string
) {
super(message);
this.name = 'ValidationError';
}
}
Component Development Standards
// components/CampaignEditor.tsx
import React, { useState, useCallback, useMemo } from 'react';
import { useCampaign } from '../hooks/useCampaign';
import { useAnalytics } from '../hooks/useAnalytics';
import { Button } from './ui/Button';
import { Input } from './ui/Input';
import { TextArea } from './ui/TextArea';
import { LoadingSpinner } from './ui/LoadingSpinner';
interface CampaignEditorProps {
readonly campaignId?: string;
readonly onSave: (campaign: EmailCampaign) => void;
readonly onCancel: () => void;
}
export const CampaignEditor: React.FC<CampaignEditorProps> = ({
campaignId,
onSave,
onCancel
}) => {
// State management
const [formData, setFormData] = useState<CreateCampaignRequest>({
name: '',
subject: '',
content: { html: '', text: '' },
recipients: [],
settings: {
analytics_enabled: true,
ai_optimization: false,
track_opens: true,
track_clicks: true
}
});
const [errors, setErrors] = useState<Record<string, string>>({});
const [isOptimizing, setIsOptimizing] = useState(false);
// Hooks
const { createCampaign, updateCampaign, isLoading } = useCampaign();
const { trackEvent, trackError } = useAnalytics();
// Derived values
const canSave = useMemo(() => {
return formData.name.trim() &&
formData.subject.trim() &&
formData.content.html.trim() &&
formData.recipients.length > 0 &&
Object.keys(errors).length === 0;
}, [formData, errors]);
// Event handlers
const handleInputChange = useCallback((
field: keyof CreateCampaignRequest,
value: any
) => {
setFormData(prev => ({ ...prev, [field]: value }));
setErrors(prev => ({ ...prev, [field]: '' }));
}, []);
const handleContentChange = useCallback((
field: 'html' | 'text',
value: string
) => {
setFormData(prev => ({
...prev,
content: { ...prev.content, [field]: value }
}));
setErrors(prev => ({ ...prev, content: '' }));
}, []);
const handleRecipientsChange = useCallback((recipients: EmailRecipient[]) => {
setFormData(prev => ({ ...prev, recipients }));
setErrors(prev => ({ ...prev, recipients: '' }));
}, []);
const handleAIOptimization = useCallback(async () => {
if (!formData.content.html.trim()) {
setErrors(prev => ({
...prev,
content: 'Content is required for AI optimization'
}));
return;
}
setIsOptimizing(true);
try {
// Trigger AI optimization
const optimizedContent = await aiService.optimizeContent(
formData.content,
formData.recipients
);
setFormData(prev => ({
...prev,
content: optimizedContent,
settings: {
...prev.settings,
ai_optimization: true
}
}));
trackEvent('ai_optimization_applied', {
campaignId,
optimizationType: 'content'
});
} catch (error) {
trackError('ai_optimization_failed', error);
setErrors(prev => ({
...prev,
ai_optimization: 'AI optimization failed. Please try again.'
}));
} finally {
setIsOptimizing(false);
}
}, [formData, aiService, trackEvent, trackError, campaignId]);
const handleSave = useCallback(async () => {
if (!canSave) {
trackError('campaign_save_validation_failed', {
errors,
formData
});
return;
}
try {
const campaign = campaignId
? await updateCampaign(campaignId, formData)
: await createCampaign(formData);
trackEvent('campaign_saved', {
campaignId: campaign.id,
hasAIOptimization: formData.settings.ai_optimization
});
onSave(campaign);
} catch (error) {
trackError('campaign_save_failed', error);
}
}, [canSave, campaignId, formData, createCampaign, updateCampaign, onSave, trackEvent, trackError]);
// Render
if (isLoading) {
return <LoadingSpinner />;
}
return (
<div className="campaign-editor">
<form onSubmit={(e) => { e.preventDefault(); handleSave(); }}>
<div className="editor-header">
<h2>{campaignId ? 'Edit Campaign' : 'Create Campaign'}</h2>
<div className="editor-actions">
<Button
type="button"
variant="secondary"
onClick={onCancel}
>
Cancel
</Button>
<Button
type="submit"
variant="primary"
disabled={!canSave}
onClick={handleSave}
>
{campaignId ? 'Update Campaign' : 'Create Campaign'}
</Button>
</div>
</div>
<div className="editor-content">
<section className="campaign-basic-info">
<h3>Basic Information</h3>
<div className="form-group">
<label htmlFor="campaign-name">Campaign Name</label>
<Input
id="campaign-name"
type="text"
value={formData.name}
onChange={(e) => handleInputChange('name', e.target.value)}
error={errors.name}
placeholder="Enter campaign name"
/>
</div>
<div className="form-group">
<label htmlFor="campaign-subject">Email Subject</label>
<Input
id="campaign-subject"
type="text"
value={formData.subject}
onChange={(e) => handleInputChange('subject', e.target.value)}
error={errors.subject}
placeholder="Enter email subject"
maxLength={100}
/>
</div>
</section>
<section className="campaign-content">
<h3>Email Content</h3>
<div className="form-group">
<label htmlFor="content-html">HTML Content</label>
<TextArea
id="content-html"
value={formData.content.html}
onChange={(e) => handleContentChange('html', e.target.value)}
error={errors.content}
placeholder="Enter HTML content..."
rows={10}
/>
</div>
<div className="form-group">
<label htmlFor="content-text">Plain Text Content</label>
<TextArea
id="content-text"
value={formData.content.text}
onChange={(e) => handleContentChange('text', e.target.value)}
placeholder="Enter plain text content..."
rows={5}
/>
</div>
<div className="ai-optimization-section">
<Button
type="button"
variant="secondary"
onClick={handleAIOptimization}
disabled={isOptimizing || !formData.content.html.trim()}
loading={isOptimizing}
>
{isOptimizing ? 'Optimizing with AI...' : 'Optimize with AI'}
</Button>
{errors.ai_optimization && (
<p className="error-message">{errors.ai_optimization}</p>
)}
</div>
</section>
</div>
</form>
</div>
);
};