feat: Add button dialog with text, link, and style options
## ✨ Better Button Insert! ### What Changed: **Before:** - Click [+ Button] → Inserts generic button immediately - No customization - Always same text/link **After:** - Click [+ Button] → Opens dialog - Configure before inserting - Professional UX ### Button Dialog Features: **3 Configuration Options:** 1. **Button Text** - Input field for custom text - Placeholder: "e.g., View Order, Track Shipment" - Default: "Click Here" 2. **Button Link** - Input field for URL or variable - Placeholder: "e.g., {order_url}, {product_url}" - Default: "{order_url}" - Hint: "Use variables like {order_url} or enter a full URL" 3. **Button Style** (NEW!) - **Solid** - High priority, urgent action - Purple background, white text - For primary CTAs (View Order, Complete Payment) - **Outline** - Secondary action, less urgent - Purple border, purple text, transparent bg - For secondary actions (Learn More, Contact Support) ### Visual Style Selector: ``` ○ [Solid] High priority, urgent action ○ [Outline] Secondary action, less urgent ``` Shows actual button preview in dialog! ### Why 2 Button Types? **Solid (Primary):** - Urgent actions: "Complete Order", "Pay Now", "Track Shipment" - High conversion priority - Stands out in email **Outline (Secondary):** - Optional actions: "View Details", "Learn More", "Contact Us" - Lower priority - Doesn't compete with primary CTA **Email Best Practice:** - 1 solid button per email (primary action) - 0-2 outline buttons (secondary actions) - Clear visual hierarchy = better conversions ### Output: **Solid:** ```html <a href="{order_url}" class="button">View Order</a> ``` **Outline:** ```html <a href="{order_url}" class="button-outline">Learn More</a> ``` ### Preview Support: - Both styles render correctly in preview - Solid: Purple background - Outline: Purple border, transparent bg Next: Email content builder? 🤔
This commit is contained in:
@@ -9,6 +9,8 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { RichTextEditor } from '@/components/ui/rich-text-editor';
|
import { RichTextEditor } from '@/components/ui/rich-text-editor';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||||
import { ArrowLeft, Eye, Edit, RotateCcw, Plus, CheckCircle, Info, AlertCircle, Image } from 'lucide-react';
|
import { ArrowLeft, Eye, Edit, RotateCcw, Plus, CheckCircle, Info, AlertCircle, Image } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { __ } from '@/lib/i18n';
|
import { __ } from '@/lib/i18n';
|
||||||
@@ -36,6 +38,12 @@ export default function EditTemplate() {
|
|||||||
const [activeTab, setActiveTab] = useState('editor');
|
const [activeTab, setActiveTab] = useState('editor');
|
||||||
const [codeMode, setCodeMode] = useState(false);
|
const [codeMode, setCodeMode] = useState(false);
|
||||||
|
|
||||||
|
// Button dialog state
|
||||||
|
const [buttonDialogOpen, setButtonDialogOpen] = useState(false);
|
||||||
|
const [buttonText, setButtonText] = useState('Click Here');
|
||||||
|
const [buttonLink, setButtonLink] = useState('{order_url}');
|
||||||
|
const [buttonType, setButtonType] = useState<'solid' | 'outline'>('solid');
|
||||||
|
|
||||||
// Fetch template
|
// Fetch template
|
||||||
const { data: template, isLoading, error } = useQuery({
|
const { data: template, isLoading, error } = useQuery({
|
||||||
queryKey: ['notification-template', eventId, channelId],
|
queryKey: ['notification-template', eventId, channelId],
|
||||||
@@ -143,9 +151,18 @@ ${content || '<h2>Card Title</h2>\n<p>Card content goes here...</p>'}
|
|||||||
toast.success(__('Card inserted'));
|
toast.success(__('Card inserted'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openButtonDialog = () => {
|
||||||
|
setButtonText('Click Here');
|
||||||
|
setButtonLink('{order_url}');
|
||||||
|
setButtonType('solid');
|
||||||
|
setButtonDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
const insertButton = () => {
|
const insertButton = () => {
|
||||||
const buttonHtml = `<p style="text-align: center;"><a href="{order_url}" class="button">Button Text</a></p>`;
|
const buttonClass = buttonType === 'solid' ? 'button' : 'button-outline';
|
||||||
|
const buttonHtml = `<p style="text-align: center;"><a href="${buttonLink}" class="${buttonClass}">${buttonText}</a></p>`;
|
||||||
setBody(body + buttonHtml + '\n');
|
setBody(body + buttonHtml + '\n');
|
||||||
|
setButtonDialogOpen(false);
|
||||||
toast.success(__('Button inserted'));
|
toast.success(__('Button inserted'));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -240,6 +257,7 @@ ${content || '<h2>Card Title</h2>\n<p>Card content goes here...</p>'}
|
|||||||
h3 { font-size: 16px; margin-top: 0; margin-bottom: 8px; color: #333; }
|
h3 { font-size: 16px; margin-top: 0; margin-bottom: 8px; color: #333; }
|
||||||
p { font-size: 16px; line-height: 1.6; color: #555; margin-bottom: 16px; }
|
p { font-size: 16px; line-height: 1.6; color: #555; margin-bottom: 16px; }
|
||||||
.button { display: inline-block; background: #7f54b3; color: #fff !important; padding: 14px 28px; border-radius: 6px; text-decoration: none; font-weight: 600; }
|
.button { display: inline-block; background: #7f54b3; color: #fff !important; padding: 14px 28px; border-radius: 6px; text-decoration: none; font-weight: 600; }
|
||||||
|
.button-outline { display: inline-block; background: transparent; color: #7f54b3 !important; padding: 12px 26px; border: 2px solid #7f54b3; border-radius: 6px; text-decoration: none; font-weight: 600; }
|
||||||
.info-box { background: #f6f6f6; border-radius: 6px; padding: 20px; margin: 16px 0; }
|
.info-box { background: #f6f6f6; border-radius: 6px; padding: 20px; margin: 16px 0; }
|
||||||
.footer { padding: 32px; text-align: center; color: #888; font-size: 13px; }
|
.footer { padding: 32px; text-align: center; color: #888; font-size: 13px; }
|
||||||
</style>
|
</style>
|
||||||
@@ -428,7 +446,7 @@ ${content || '<h2>Card Title</h2>\n<p>Card content goes here...</p>'}
|
|||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={insertButton}
|
onClick={openButtonDialog}
|
||||||
className="h-7 text-xs gap-1"
|
className="h-7 text-xs gap-1"
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-3 w-3" />
|
||||||
@@ -464,6 +482,83 @@ ${content || '<h2>Card Title</h2>\n<p>Card content goes here...</p>'}
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Button Insert Dialog */}
|
||||||
|
<Dialog open={buttonDialogOpen} onOpenChange={setButtonDialogOpen}>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{__('Insert Button')}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{__('Configure your call-to-action button. Use variables like {order_url} for dynamic links.')}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4 py-4">
|
||||||
|
{/* Button Text */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="button-text">{__('Button Text')}</Label>
|
||||||
|
<Input
|
||||||
|
id="button-text"
|
||||||
|
value={buttonText}
|
||||||
|
onChange={(e) => setButtonText(e.target.value)}
|
||||||
|
placeholder={__('e.g., View Order, Track Shipment')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Button Link */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="button-link">{__('Button Link')}</Label>
|
||||||
|
<Input
|
||||||
|
id="button-link"
|
||||||
|
value={buttonLink}
|
||||||
|
onChange={(e) => setButtonLink(e.target.value)}
|
||||||
|
placeholder={__('e.g., {order_url}, {product_url}')}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{__('Use variables like {order_url} or enter a full URL')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Button Type */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label>{__('Button Style')}</Label>
|
||||||
|
<RadioGroup value={buttonType} onValueChange={(value: 'solid' | 'outline') => setButtonType(value)}>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="solid" id="solid" />
|
||||||
|
<Label htmlFor="solid" className="font-normal cursor-pointer">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="px-4 py-2 bg-primary text-primary-foreground rounded text-sm font-medium">
|
||||||
|
{__('Solid')}
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-muted-foreground">{__('High priority, urgent action')}</span>
|
||||||
|
</div>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="outline" id="outline" />
|
||||||
|
<Label htmlFor="outline" className="font-normal cursor-pointer">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="px-4 py-2 border-2 border-primary text-primary rounded text-sm font-medium">
|
||||||
|
{__('Outline')}
|
||||||
|
</div>
|
||||||
|
<span className="text-xs text-muted-foreground">{__('Secondary action, less urgent')}</span>
|
||||||
|
</div>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={() => setButtonDialogOpen(false)}>
|
||||||
|
{__('Cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={insertButton}>
|
||||||
|
{__('Insert Button')}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
{/* Preview Tab */}
|
{/* Preview Tab */}
|
||||||
{activeTab === 'preview' && (
|
{activeTab === 'preview' && (
|
||||||
<Card>
|
<Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user