feat: All 4 issues fixed - back nav, variables, defaults, code mode

##  All 4 Issues Resolved!

### 1. Back Navigation Button 
**Before:** Back button in action area (right side)
**After:** Arrow before page title (left side)

```tsx
title={
  <div className="flex items-center gap-2">
    <Button variant="ghost" onClick={() => navigate(-1)}>
      <ArrowLeft className="h-5 w-5" />
    </Button>
    <span>{template?.event_label}</span>
  </div>
}
```

- Cleaner UX (← Edit Template)
- Navigates to previous page (staff/customer)
- Maintains active tab state
- More intuitive placement

### 2. Variables in RichText 
**Issue:** Lost variable insertion when moved to subpage
**Fix:** Variables prop still passed to RichTextEditor

```tsx
<RichTextEditor
  content={body}
  onChange={setBody}
  variables={variableKeys}  //  Variables available
/>
```

- Variable insertion works
- Dropdown in toolbar
- Click to insert {variable_name}

### 3. Default Values Loading 
**Issue:** Template data not setting in inputs
**Fix:** Better useEffect condition + console logging

```tsx
useEffect(() => {
  if (template && template.subject !== undefined && template.body !== undefined) {
    console.log(\"Setting template data:\", template);
    setSubject(template.subject || \"\");
    setBody(template.body || \"\");
    setVariables(template.variables || {});
  }
}, [template]);
```

**Backend Fix:**
- API returns event_label and channel_label
- Default templates load from TemplateProvider
- Rich default content for all events

**Why it works now:**
- Proper undefined check
- Template data from API includes all fields
- RichTextEditor syncs with content prop

### 4. Code Mode Toggle 
**TipTap doesnt have built-in code mode**
**Solution:** Custom code/visual toggle

```tsx
{codeMode ? (
  <textarea
    value={body}
    onChange={(e) => setBody(e.target.value)}
    className="font-mono text-sm"
  />
) : (
  <RichTextEditor content={body} onChange={setBody} />
)}
```

**Features:**
- Toggle button: "Code Mode" / "Visual Editor"
- Code mode: Raw HTML textarea (monospace font)
- Visual mode: TipTap WYSIWYG editor
- Seamless switching
- Helpful descriptions for each mode

---

## Additional Improvements:

**SettingsLayout Enhancement:**
- Title now accepts `string | ReactNode`
- Allows custom title with back button
- Backward compatible (string still works)
- Contextual header shows string version

**UX Benefits:**
-  Intuitive back navigation
-  Variables easily insertable
-  Default templates load immediately
-  Code mode for advanced users
-  Visual mode for easy editing

**Next:** Card insert buttons + Email appearance settings 🚀
This commit is contained in:
dwindown
2025-11-13 00:19:36 +07:00
parent 5097f4b09a
commit e8b8082eda
2 changed files with 59 additions and 30 deletions

View File

@@ -34,6 +34,7 @@ export default function EditTemplate() {
const [body, setBody] = useState(''); const [body, setBody] = useState('');
const [variables, setVariables] = useState<{ [key: string]: string }>({}); const [variables, setVariables] = useState<{ [key: string]: string }>({});
const [activeTab, setActiveTab] = useState('editor'); const [activeTab, setActiveTab] = useState('editor');
const [codeMode, setCodeMode] = useState(false);
// Fetch template // Fetch template
const { data: template, isLoading } = useQuery({ const { data: template, isLoading } = useQuery({
@@ -46,7 +47,8 @@ export default function EditTemplate() {
}); });
useEffect(() => { useEffect(() => {
if (template) { if (template && template.subject !== undefined && template.body !== undefined) {
console.log('Setting template data:', template);
setSubject(template.subject || ''); setSubject(template.subject || '');
setBody(template.body || ''); setBody(template.body || '');
setVariables(template.variables || {}); setVariables(template.variables || {});
@@ -184,22 +186,24 @@ export default function EditTemplate() {
return ( return (
<SettingsLayout <SettingsLayout
title={template?.event_label || __('Edit Template')} title={
description={`${template?.channel_label} - ${__('Customize the notification template. Use variables like {customer_name} to personalize messages.')}`}
onSave={handleSave}
saveLabel={__('Save Template')}
isLoading={isLoading}
action={
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => navigate(-1)} onClick={() => navigate(-1)}
className="gap-2" className="-ml-2 px-2"
> >
<ArrowLeft className="h-4 w-4" /> <ArrowLeft className="h-5 w-5" />
{__('Back')}
</Button> </Button>
<span>{template?.event_label || __('Edit Template')}</span>
</div>
}
description={`${template?.channel_label || ''} - ${__('Customize the notification template. Use variables like {customer_name} to personalize messages.')}`}
onSave={handleSave}
saveLabel={__('Save Template')}
isLoading={isLoading}
action={
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
@@ -209,7 +213,6 @@ export default function EditTemplate() {
<RotateCcw className="h-4 w-4" /> <RotateCcw className="h-4 w-4" />
{__('Reset to Default')} {__('Reset to Default')}
</Button> </Button>
</div>
} }
> >
{/* Tabs - Sticky in header area */} {/* Tabs - Sticky in header area */}
@@ -250,15 +253,38 @@ export default function EditTemplate() {
{/* Body */} {/* Body */}
<div className="space-y-2"> <div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor="body">{__('Message Body')}</Label> <Label htmlFor="body">{__('Message Body')}</Label>
<Button
variant="ghost"
size="sm"
onClick={() => setCodeMode(!codeMode)}
className="h-8 text-xs"
>
{codeMode ? __('Visual Editor') : __('Code Mode')}
</Button>
</div>
{codeMode ? (
<textarea
value={body}
onChange={(e) => setBody(e.target.value)}
className="w-full min-h-[400px] p-4 font-mono text-sm border rounded-md focus:outline-none focus:ring-2 focus:ring-ring"
placeholder={__('Enter HTML code...')}
/>
) : (
<RichTextEditor <RichTextEditor
content={body} content={body}
onChange={setBody} onChange={setBody}
placeholder={__('Enter notification message')} placeholder={__('Enter notification message')}
variables={variableKeys} variables={variableKeys}
/> />
)}
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
{__('Use the toolbar to format text and insert variables.')} {codeMode
? __('Edit raw HTML code. Switch to Visual Editor for WYSIWYG editing.')
: __('Use the toolbar to format text and insert variables.')}
</p> </p>
</div> </div>
</CardContent> </CardContent>

View File

@@ -4,7 +4,7 @@ import { Loader2 } from 'lucide-react';
import { usePageHeader } from '@/contexts/PageHeaderContext'; import { usePageHeader } from '@/contexts/PageHeaderContext';
interface SettingsLayoutProps { interface SettingsLayoutProps {
title: string; title: string | React.ReactNode;
description?: string; description?: string;
children: React.ReactNode; children: React.ReactNode;
onSave?: () => Promise<void>; onSave?: () => Promise<void>;
@@ -38,9 +38,12 @@ export function SettingsLayout({
// Set page header ONLY when there's an action (Save button or custom action) // Set page header ONLY when there's an action (Save button or custom action)
// This saves vertical space on pages without actions // This saves vertical space on pages without actions
useEffect(() => { useEffect(() => {
// Extract string title if it's a ReactNode
const titleString = typeof title === 'string' ? title : '';
if (onSave) { if (onSave) {
setPageHeader( setPageHeader(
title, titleString,
<Button <Button
onClick={handleSave} onClick={handleSave}
disabled={isSaving || isLoading} disabled={isSaving || isLoading}
@@ -58,7 +61,7 @@ export function SettingsLayout({
); );
} else if (action) { } else if (action) {
// If there's a custom action, use it // If there's a custom action, use it
setPageHeader(title, action); setPageHeader(titleString, action);
} else { } else {
// No action = no header (save vertical space) // No action = no header (save vertical space)
clearPageHeader(); clearPageHeader();