feat: Enhanced Email Customization - Logo, Social, Hero Text! 🎨

## Frontend Improvements (1-3, 5)

### 1. Logo URL with WP Media Library 
- Added "Select" button next to logo URL input
- Opens WordPress Media Library
- Logo preview below input
- Easier for users to select from existing media

### 2. Footer Text with {current_year} Variable 
- Updated placeholder to show {current_year} usage
- Help text explains dynamic year variable
- Backend will replace with actual year

### 3. Social Links in Footer 
**Platforms Supported:**
- Facebook
- Twitter
- Instagram
- LinkedIn
- YouTube
- Website

**Features:**
- Add/remove social links
- Platform dropdown with icons
- URL input for each link
- Visual icons in UI
- Will render as icons in email footer

### 5. Hero Card Text Color 
- Added hero_text_color field
- Color picker + hex input
- Applied to preview
- Separate control for heading/text color
- Usually white for dark gradients

**Updated Interface:**
```typescript
interface EmailSettings {
  // ... existing
  hero_text_color: string;
  social_links: SocialLink[];
}

interface SocialLink {
  platform: string;
  url: string;
}
```

**File:**
- `routes/Settings/Notifications/EmailCustomization.tsx`

Next: Wire to backend (task 4)!
This commit is contained in:
dwindown
2025-11-13 13:38:51 +07:00
parent 704e9942e1
commit 7badee9ee4

View File

@@ -8,18 +8,27 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { __ } from '@/lib/i18n';
import { ArrowLeft, RefreshCw } from 'lucide-react';
import { ArrowLeft, RefreshCw, Upload, Plus, Trash2, Facebook, Twitter, Instagram, Linkedin, Youtube, Globe } from 'lucide-react';
import { toast } from 'sonner';
import { openWPMediaLogo } from '@/lib/wp-media';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
interface SocialLink {
platform: string;
url: string;
}
interface EmailSettings {
primary_color: string;
secondary_color: string;
hero_gradient_start: string;
hero_gradient_end: string;
hero_text_color: string;
button_text_color: string;
logo_url: string;
header_text: string;
footer_text: string;
social_links: SocialLink[];
}
export default function EmailCustomization() {
@@ -35,10 +44,12 @@ export default function EmailCustomization() {
secondary_color: '#7f54b3',
hero_gradient_start: '#667eea',
hero_gradient_end: '#764ba2',
hero_text_color: '#ffffff',
button_text_color: '#ffffff',
logo_url: '',
header_text: '',
footer_text: '',
social_links: [],
},
});
@@ -47,10 +58,12 @@ export default function EmailCustomization() {
secondary_color: '#7f54b3',
hero_gradient_start: '#667eea',
hero_gradient_end: '#764ba2',
hero_text_color: '#ffffff',
button_text_color: '#ffffff',
logo_url: '',
header_text: '',
footer_text: '',
social_links: [],
});
// Update form when settings load
@@ -104,6 +117,50 @@ export default function EmailCustomization() {
setFormData(prev => ({ ...prev, [field]: value }));
};
const handleLogoSelect = () => {
openWPMediaLogo((media) => {
if (media && media.url) {
handleChange('logo_url', media.url);
}
});
};
const addSocialLink = () => {
setFormData(prev => ({
...prev,
social_links: [...prev.social_links, { platform: 'facebook', url: '' }],
}));
};
const removeSocialLink = (index: number) => {
setFormData(prev => ({
...prev,
social_links: prev.social_links.filter((_, i) => i !== index),
}));
};
const updateSocialLink = (index: number, field: 'platform' | 'url', value: string) => {
setFormData(prev => ({
...prev,
social_links: prev.social_links.map((link, i) =>
i === index ? { ...link, [field]: value } : link
),
}));
};
const getSocialIcon = (platform: string) => {
const icons: Record<string, any> = {
facebook: Facebook,
twitter: Twitter,
instagram: Instagram,
linkedin: Linkedin,
youtube: Youtube,
website: Globe,
};
const Icon = icons[platform] || Globe;
return <Icon className="h-4 w-4" />;
};
if (isLoading) {
return (
<SettingsLayout
@@ -252,9 +309,33 @@ export default function EmailCustomization() {
</div>
</div>
<div className="space-y-2">
<Label htmlFor="hero_text_color">{__('Text Color')}</Label>
<div className="flex gap-2">
<Input
id="hero_text_color"
type="color"
value={formData.hero_text_color}
onChange={(e) => handleChange('hero_text_color', e.target.value)}
className="w-20 h-10 p-1 cursor-pointer"
/>
<Input
type="text"
value={formData.hero_text_color}
onChange={(e) => handleChange('hero_text_color', e.target.value)}
placeholder="#ffffff"
className="flex-1"
/>
</div>
<p className="text-xs text-muted-foreground">
{__('Text and heading color for hero cards (usually white)')}
</p>
</div>
{/* Preview */}
<div className="mt-4 p-6 rounded-lg text-white text-center" style={{
background: `linear-gradient(135deg, ${formData.hero_gradient_start} 0%, ${formData.hero_gradient_end} 100%)`
<div className="mt-4 p-6 rounded-lg text-center" style={{
background: `linear-gradient(135deg, ${formData.hero_gradient_start} 0%, ${formData.hero_gradient_end} 100%)`,
color: formData.hero_text_color
}}>
<h3 className="text-xl font-bold mb-2">{__('Preview')}</h3>
<p className="text-sm opacity-90">{__('This is how your hero cards will look')}</p>
@@ -323,16 +404,37 @@ export default function EmailCustomization() {
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="logo_url">{__('Logo URL')}</Label>
<Input
id="logo_url"
type="url"
value={formData.logo_url}
onChange={(e) => handleChange('logo_url', e.target.value)}
placeholder="https://example.com/logo.png"
/>
<div className="flex gap-2">
<Input
id="logo_url"
type="url"
value={formData.logo_url}
onChange={(e) => handleChange('logo_url', e.target.value)}
placeholder="https://example.com/logo.png"
className="flex-1"
/>
<Button
type="button"
variant="outline"
onClick={handleLogoSelect}
className="gap-2"
>
<Upload className="h-4 w-4" />
{__('Select')}
</Button>
</div>
<p className="text-xs text-muted-foreground">
{__('Full URL to your logo image (recommended: 200x60px)')}
</p>
{formData.logo_url && (
<div className="mt-2 p-4 border rounded-lg bg-muted/30">
<img
src={formData.logo_url}
alt="Logo preview"
className="max-h-16 object-contain"
/>
</div>
)}
</div>
<div className="space-y-2">
@@ -356,10 +458,112 @@ export default function EmailCustomization() {
type="text"
value={formData.footer_text}
onChange={(e) => handleChange('footer_text', e.target.value)}
placeholder={__(2024 Your Store. All rights reserved.')}
placeholder={__({current_year} Your Store. All rights reserved.')}
/>
<p className="text-xs text-muted-foreground">
{__('Text shown in email footer (copyright, address, etc.)')}
{__('Text shown in email footer. Use {current_year} for dynamic year.')}
</p>
</div>
{/* Social Links */}
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label>{__('Social Links')}</Label>
<Button
type="button"
variant="outline"
size="sm"
onClick={addSocialLink}
className="gap-2"
>
<Plus className="h-4 w-4" />
{__('Add Social Link')}
</Button>
</div>
{formData.social_links.length === 0 ? (
<p className="text-sm text-muted-foreground">
{__('No social links added. Click "Add Social Link" to get started.')}
</p>
) : (
<div className="space-y-2">
{formData.social_links.map((link, index) => (
<div key={index} className="flex gap-2 items-start p-3 border rounded-lg">
<div className="flex-1 grid grid-cols-2 gap-2">
<div>
<Select
value={link.platform}
onValueChange={(value) => updateSocialLink(index, 'platform', value)}
>
<SelectTrigger className="h-9">
<div className="flex items-center gap-2">
{getSocialIcon(link.platform)}
<SelectValue />
</div>
</SelectTrigger>
<SelectContent>
<SelectItem value="facebook">
<div className="flex items-center gap-2">
<Facebook className="h-4 w-4" />
Facebook
</div>
</SelectItem>
<SelectItem value="twitter">
<div className="flex items-center gap-2">
<Twitter className="h-4 w-4" />
Twitter
</div>
</SelectItem>
<SelectItem value="instagram">
<div className="flex items-center gap-2">
<Instagram className="h-4 w-4" />
Instagram
</div>
</SelectItem>
<SelectItem value="linkedin">
<div className="flex items-center gap-2">
<Linkedin className="h-4 w-4" />
LinkedIn
</div>
</SelectItem>
<SelectItem value="youtube">
<div className="flex items-center gap-2">
<Youtube className="h-4 w-4" />
YouTube
</div>
</SelectItem>
<SelectItem value="website">
<div className="flex items-center gap-2">
<Globe className="h-4 w-4" />
Website
</div>
</SelectItem>
</SelectContent>
</Select>
</div>
<Input
type="url"
value={link.url}
onChange={(e) => updateSocialLink(index, 'url', e.target.value)}
placeholder="https://..."
className="h-9"
/>
</div>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => removeSocialLink(index)}
className="h-9 w-9 p-0 text-destructive hover:text-destructive"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
))}
</div>
)}
<p className="text-xs text-muted-foreground">
{__('Social links will appear as icons in the email footer')}
</p>
</div>
</div>