feat: expand internationalization and add identity management
This release significantly expands internationalization support and adds comprehensive identity management features. Internationalization (i18n): - Add 5 new languages: Spanish, Italian, German, Dutch, Portuguese - Expand from 3 to 8 total supported languages - Redesign language switcher for better scalability (dropdown UI) - Complete translations for all features across all languages Identity Management: - Multiple sender identities with per-identity signatures - Sub-addressing support (user+tag@domain.com) - Context-aware tag suggestions for sub-addresses - Identity badges in email viewer and list - Full CRUD operations for managing identities Newsletter Management: - RFC 2369 List-Unsubscribe support (one-click unsubscribe) - HTTP and mailto unsubscribe methods - Security validation prevents XSS attacks - Two-step confirmation with persistent dismissal Security & Accessibility: - Dark mode email readability (intelligent color transformation) - WCAG 2.0 Level AA color contrast compliance - Comprehensive XSS prevention with validation utilities - Unit test coverage for security-critical code (57 validation tests) Testing: - Add unit tests for validation utilities - Add unit tests for email sanitization - Add unit tests for color transformation - Full test coverage for XSS attack vectors
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { SettingsSection, SettingItem } from './settings-section';
|
||||
import { IdentityManagerModal } from '@/components/identity/identity-manager-modal';
|
||||
import { useIdentityStore } from '@/stores/identity-store';
|
||||
|
||||
export function IdentitySettings() {
|
||||
const t = useTranslations('settings.identities');
|
||||
const { identities } = useIdentityStore();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsSection title={t('title')} description={t('description')}>
|
||||
{/* Identity Count */}
|
||||
<SettingItem
|
||||
label={t('identities_count.label')}
|
||||
description={t('identities_count.description')}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-foreground">
|
||||
{identities.length === 0
|
||||
? t('identities_count.count_zero')
|
||||
: identities.length === 1
|
||||
? t('identities_count.count_one')
|
||||
: t('identities_count.count_other', { count: identities.length })}
|
||||
</span>
|
||||
<Button onClick={() => setShowModal(true)} size="sm">
|
||||
{t('manage')}
|
||||
</Button>
|
||||
</div>
|
||||
</SettingItem>
|
||||
|
||||
{/* Sub-Addressing Info */}
|
||||
<SettingItem
|
||||
label={t('sub_addressing.label')}
|
||||
description={t('sub_addressing.description')}
|
||||
>
|
||||
<Button variant="outline" size="sm" onClick={() => setShowModal(true)}>
|
||||
{t('sub_addressing.learn_more')}
|
||||
</Button>
|
||||
</SettingItem>
|
||||
</SettingsSection>
|
||||
|
||||
{/* Identity Manager Modal */}
|
||||
<IdentityManagerModal
|
||||
isOpen={showModal}
|
||||
onClose={() => setShowModal(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -111,7 +111,8 @@ export function Select({ value, onChange, options }: SelectProps) {
|
||||
<select
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
className="px-3 py-1.5 text-sm rounded bg-muted border border-border text-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
dir="auto"
|
||||
className="px-3 py-1.5 text-sm rounded bg-muted border border-border text-foreground focus:outline-none focus:ring-2 focus:ring-primary cursor-pointer"
|
||||
>
|
||||
{options.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
|
||||
Reference in New Issue
Block a user