3.05 Email Signature Automatizare si Branding
- AD prefill: 'Din cont' button pre-fills name + company from Authentik session - Logo size slider: 50%-200% scale control in Stil & Aranjare section - Promotional banner: configurable image+link below signature with preview - US/SDT custom graphics: dedicated dash (US) and dot (SDT) decorative icons replacing Beletage's grey/accent slashes for company-specific branding
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="7" viewBox="0 0 11 7">
|
||||||
|
<circle cx="5.5" cy="3.5" r="2.5" fill="#0182A1"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 142 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 11 11">
|
||||||
|
<circle cx="5.5" cy="5.5" r="3" fill="#A7A9AA"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 142 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="7" viewBox="0 0 11 7">
|
||||||
|
<rect x="1" y="2.5" width="9" height="2" rx="1" fill="#345476"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 156 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 11 11">
|
||||||
|
<rect x="1" y="4.5" width="9" height="2" rx="1" fill="#A7A9AA"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 158 B |
@@ -8,6 +8,7 @@ import { SavedSignaturesPanel } from './saved-signatures-panel';
|
|||||||
import { Separator } from '@/shared/components/ui/separator';
|
import { Separator } from '@/shared/components/ui/separator';
|
||||||
import { Button } from '@/shared/components/ui/button';
|
import { Button } from '@/shared/components/ui/button';
|
||||||
import { RotateCcw } from 'lucide-react';
|
import { RotateCcw } from 'lucide-react';
|
||||||
|
import type { SignatureBanner } from '../types';
|
||||||
|
|
||||||
export function EmailSignatureModule() {
|
export function EmailSignatureModule() {
|
||||||
const {
|
const {
|
||||||
@@ -17,6 +18,10 @@ export function EmailSignatureModule() {
|
|||||||
|
|
||||||
const { saved, loading, save, remove } = useSavedSignatures();
|
const { saved, loading, save, remove } = useSavedSignatures();
|
||||||
|
|
||||||
|
const setBanner = (banner: SignatureBanner | undefined) => {
|
||||||
|
updateField('banner', banner);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-6 lg:grid-cols-[360px_1fr]">
|
<div className="grid gap-6 lg:grid-cols-[360px_1fr]">
|
||||||
{/* Left panel — configurator */}
|
{/* Left panel — configurator */}
|
||||||
@@ -29,6 +34,7 @@ export function EmailSignatureModule() {
|
|||||||
onSetVariant={setVariant}
|
onSetVariant={setVariant}
|
||||||
onSetCompany={setCompany}
|
onSetCompany={setCompany}
|
||||||
onSetAddress={setAddress}
|
onSetAddress={setAddress}
|
||||||
|
onSetBanner={setBanner}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type {
|
|||||||
SignatureColors,
|
SignatureColors,
|
||||||
SignatureLayout,
|
SignatureLayout,
|
||||||
SignatureVariant,
|
SignatureVariant,
|
||||||
|
SignatureBanner,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import {
|
import {
|
||||||
COMPANY_BRANDING,
|
COMPANY_BRANDING,
|
||||||
@@ -14,9 +15,11 @@ import {
|
|||||||
SDT_ADDRESSES,
|
SDT_ADDRESSES,
|
||||||
} from "../services/company-branding";
|
} from "../services/company-branding";
|
||||||
import type { AddressKey } from "../services/company-branding";
|
import type { AddressKey } from "../services/company-branding";
|
||||||
|
import { useAuth } from "@/core/auth";
|
||||||
import { Input } from "@/shared/components/ui/input";
|
import { Input } from "@/shared/components/ui/input";
|
||||||
import { Label } from "@/shared/components/ui/label";
|
import { Label } from "@/shared/components/ui/label";
|
||||||
import { Switch } from "@/shared/components/ui/switch";
|
import { Switch } from "@/shared/components/ui/switch";
|
||||||
|
import { Button } from "@/shared/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -26,6 +29,7 @@ import {
|
|||||||
} from "@/shared/components/ui/select";
|
} from "@/shared/components/ui/select";
|
||||||
import { Separator } from "@/shared/components/ui/separator";
|
import { Separator } from "@/shared/components/ui/separator";
|
||||||
import { cn } from "@/shared/lib/utils";
|
import { cn } from "@/shared/lib/utils";
|
||||||
|
import { UserCheck, ImagePlus, Trash2 } from "lucide-react";
|
||||||
|
|
||||||
interface SignatureConfiguratorProps {
|
interface SignatureConfiguratorProps {
|
||||||
config: SignatureConfig;
|
config: SignatureConfig;
|
||||||
@@ -38,6 +42,7 @@ interface SignatureConfiguratorProps {
|
|||||||
onSetVariant: (variant: SignatureVariant) => void;
|
onSetVariant: (variant: SignatureVariant) => void;
|
||||||
onSetCompany: (company: CompanyId) => void;
|
onSetCompany: (company: CompanyId) => void;
|
||||||
onSetAddress?: (address: string[]) => void;
|
onSetAddress?: (address: string[]) => void;
|
||||||
|
onSetBanner?: (banner: SignatureBanner | undefined) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Color palette per company */
|
/** Color palette per company */
|
||||||
@@ -108,8 +113,10 @@ export function SignatureConfigurator({
|
|||||||
onSetVariant,
|
onSetVariant,
|
||||||
onSetCompany,
|
onSetCompany,
|
||||||
onSetAddress,
|
onSetAddress,
|
||||||
|
onSetBanner,
|
||||||
}: SignatureConfiguratorProps) {
|
}: SignatureConfiguratorProps) {
|
||||||
const palette = COMPANY_PALETTES[config.company];
|
const palette = COMPANY_PALETTES[config.company];
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -248,7 +255,25 @@ export function SignatureConfigurator({
|
|||||||
|
|
||||||
{/* Personal data */}
|
{/* Personal data */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<h3 className="text-sm font-semibold">Date personale</h3>
|
<h3 className="text-sm font-semibold">Date personale</h3>
|
||||||
|
{user && user.name && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-7 text-xs"
|
||||||
|
onClick={() => {
|
||||||
|
onUpdateField("name", user.name);
|
||||||
|
if (user.company && user.company !== "group") {
|
||||||
|
onSetCompany(user.company);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UserCheck className="mr-1 h-3 w-3" />
|
||||||
|
Din cont
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="sig-prefix">Titulatură (prefix)</Label>
|
<Label htmlFor="sig-prefix">Titulatură (prefix)</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -359,6 +384,28 @@ export function SignatureConfigurator({
|
|||||||
{/* Layout sliders */}
|
{/* Layout sliders */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<h3 className="text-sm font-semibold">Stil & Aranjare</h3>
|
<h3 className="text-sm font-semibold">Stil & Aranjare</h3>
|
||||||
|
|
||||||
|
{/* Logo scale slider */}
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<Label>Dimensiune logo</Label>
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{Math.round((config.layout.logoScale ?? 1) * 100)}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={50}
|
||||||
|
max={200}
|
||||||
|
step={5}
|
||||||
|
value={Math.round((config.layout.logoScale ?? 1) * 100)}
|
||||||
|
onChange={(e) =>
|
||||||
|
onUpdateLayout("logoScale", parseInt(e.target.value, 10) / 100)
|
||||||
|
}
|
||||||
|
className="mt-1 w-full accent-primary"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{LAYOUT_CONTROLS.map(({ key, label, min, max }) => (
|
{LAYOUT_CONTROLS.map(({ key, label, min, max }) => (
|
||||||
<div key={key}>
|
<div key={key}>
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
@@ -380,6 +427,137 @@ export function SignatureConfigurator({
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
{/* Promotional banner */}
|
||||||
|
{onSetBanner && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="text-sm font-semibold">Banner promoțional</h3>
|
||||||
|
{config.banner ? (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-7 text-xs text-destructive hover:text-destructive"
|
||||||
|
onClick={() => onSetBanner(undefined)}
|
||||||
|
>
|
||||||
|
<Trash2 className="mr-1 h-3 w-3" />
|
||||||
|
Șterge
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-7 text-xs"
|
||||||
|
onClick={() =>
|
||||||
|
onSetBanner({
|
||||||
|
imageUrl: "",
|
||||||
|
linkUrl: "",
|
||||||
|
alt: "Banner",
|
||||||
|
width: 540,
|
||||||
|
height: 80,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ImagePlus className="mr-1 h-3 w-3" />
|
||||||
|
Adaugă
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{config.banner && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="banner-img">URL Imagine</Label>
|
||||||
|
<Input
|
||||||
|
id="banner-img"
|
||||||
|
placeholder="https://example.com/banner.png"
|
||||||
|
value={config.banner.imageUrl}
|
||||||
|
onChange={(e) =>
|
||||||
|
onSetBanner({
|
||||||
|
...config.banner!,
|
||||||
|
imageUrl: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="banner-link">URL Link (click)</Label>
|
||||||
|
<Input
|
||||||
|
id="banner-link"
|
||||||
|
placeholder="https://example.com"
|
||||||
|
value={config.banner.linkUrl}
|
||||||
|
onChange={(e) =>
|
||||||
|
onSetBanner({
|
||||||
|
...config.banner!,
|
||||||
|
linkUrl: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="banner-alt">Text alternativ</Label>
|
||||||
|
<Input
|
||||||
|
id="banner-alt"
|
||||||
|
placeholder="Banner promoțional"
|
||||||
|
value={config.banner.alt}
|
||||||
|
onChange={(e) =>
|
||||||
|
onSetBanner({ ...config.banner!, alt: e.target.value })
|
||||||
|
}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="banner-w">Lățime (px)</Label>
|
||||||
|
<Input
|
||||||
|
id="banner-w"
|
||||||
|
type="number"
|
||||||
|
value={config.banner.width}
|
||||||
|
onChange={(e) =>
|
||||||
|
onSetBanner({
|
||||||
|
...config.banner!,
|
||||||
|
width: parseInt(e.target.value, 10) || 540,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="banner-h">Înălțime (px)</Label>
|
||||||
|
<Input
|
||||||
|
id="banner-h"
|
||||||
|
type="number"
|
||||||
|
value={config.banner.height}
|
||||||
|
onChange={(e) =>
|
||||||
|
onSetBanner({
|
||||||
|
...config.banner!,
|
||||||
|
height: parseInt(e.target.value, 10) || 80,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{config.banner.imageUrl && (
|
||||||
|
<div className="rounded-md border p-2">
|
||||||
|
<p className="mb-1 text-xs text-muted-foreground">
|
||||||
|
Previzualizare:
|
||||||
|
</p>
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
src={config.banner.imageUrl}
|
||||||
|
alt={config.banner.alt || "Banner"}
|
||||||
|
className="max-h-24 w-full rounded object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
'use client';
|
"use client";
|
||||||
|
|
||||||
import { useState, useCallback, useMemo } from 'react';
|
import { useState, useCallback, useMemo } from "react";
|
||||||
import type { CompanyId } from '@/core/auth/types';
|
import type { CompanyId } from "@/core/auth/types";
|
||||||
import type { SignatureConfig, SignatureVariant, SignatureColors, SignatureLayout } from '../types';
|
import type {
|
||||||
import { getBranding } from '../services/company-branding';
|
SignatureConfig,
|
||||||
|
SignatureVariant,
|
||||||
|
SignatureColors,
|
||||||
|
SignatureLayout,
|
||||||
|
} from "../types";
|
||||||
|
import { getBranding } from "../services/company-branding";
|
||||||
|
|
||||||
const DEFAULT_LAYOUT: SignatureLayout = {
|
const DEFAULT_LAYOUT: SignatureLayout = {
|
||||||
greenLineWidth: 97,
|
greenLineWidth: 97,
|
||||||
@@ -14,46 +19,55 @@ const DEFAULT_LAYOUT: SignatureLayout = {
|
|||||||
sectionSpacing: 10,
|
sectionSpacing: 10,
|
||||||
titleSpacing: 2,
|
titleSpacing: 2,
|
||||||
logoSpacing: 10,
|
logoSpacing: 10,
|
||||||
|
logoScale: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
function createDefaultConfig(company: CompanyId = 'beletage'): SignatureConfig {
|
function createDefaultConfig(company: CompanyId = "beletage"): SignatureConfig {
|
||||||
const branding = getBranding(company);
|
const branding = getBranding(company);
|
||||||
return {
|
return {
|
||||||
prefix: 'arh.',
|
prefix: "arh.",
|
||||||
name: '',
|
name: "",
|
||||||
title: '',
|
title: "",
|
||||||
phone: '',
|
phone: "",
|
||||||
company,
|
company,
|
||||||
colors: { ...branding.defaultColors },
|
colors: { ...branding.defaultColors },
|
||||||
layout: { ...DEFAULT_LAYOUT },
|
layout: { ...DEFAULT_LAYOUT },
|
||||||
variant: 'full',
|
variant: "full",
|
||||||
useSvg: false,
|
useSvg: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSignatureConfig(initialCompany: CompanyId = 'beletage') {
|
export function useSignatureConfig(initialCompany: CompanyId = "beletage") {
|
||||||
const [config, setConfig] = useState<SignatureConfig>(() => createDefaultConfig(initialCompany));
|
const [config, setConfig] = useState<SignatureConfig>(() =>
|
||||||
|
createDefaultConfig(initialCompany),
|
||||||
|
);
|
||||||
|
|
||||||
const updateField = useCallback(<K extends keyof SignatureConfig>(
|
const updateField = useCallback(
|
||||||
key: K,
|
<K extends keyof SignatureConfig>(key: K, value: SignatureConfig[K]) => {
|
||||||
value: SignatureConfig[K]
|
|
||||||
) => {
|
|
||||||
setConfig((prev) => ({ ...prev, [key]: value }));
|
setConfig((prev) => ({ ...prev, [key]: value }));
|
||||||
}, []);
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const updateColor = useCallback((key: keyof SignatureColors, value: string) => {
|
const updateColor = useCallback(
|
||||||
|
(key: keyof SignatureColors, value: string) => {
|
||||||
setConfig((prev) => ({
|
setConfig((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
colors: { ...prev.colors, [key]: value },
|
colors: { ...prev.colors, [key]: value },
|
||||||
}));
|
}));
|
||||||
}, []);
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const updateLayout = useCallback((key: keyof SignatureLayout, value: number) => {
|
const updateLayout = useCallback(
|
||||||
|
(key: keyof SignatureLayout, value: number) => {
|
||||||
setConfig((prev) => ({
|
setConfig((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
layout: { ...prev.layout, [key]: value },
|
layout: { ...prev.layout, [key]: value },
|
||||||
}));
|
}));
|
||||||
}, []);
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const setVariant = useCallback((variant: SignatureVariant) => {
|
const setVariant = useCallback((variant: SignatureVariant) => {
|
||||||
setConfig((prev) => ({ ...prev, variant }));
|
setConfig((prev) => ({ ...prev, variant }));
|
||||||
@@ -80,7 +94,8 @@ export function useSignatureConfig(initialCompany: CompanyId = 'beletage') {
|
|||||||
setConfig(loaded);
|
setConfig(loaded);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return useMemo(() => ({
|
return useMemo(
|
||||||
|
() => ({
|
||||||
config,
|
config,
|
||||||
updateField,
|
updateField,
|
||||||
updateColor,
|
updateColor,
|
||||||
@@ -90,5 +105,17 @@ export function useSignatureConfig(initialCompany: CompanyId = 'beletage') {
|
|||||||
setAddress,
|
setAddress,
|
||||||
resetToDefaults,
|
resetToDefaults,
|
||||||
loadConfig,
|
loadConfig,
|
||||||
}), [config, updateField, updateColor, updateLayout, setVariant, setCompany, setAddress, resetToDefaults, loadConfig]);
|
}),
|
||||||
|
[
|
||||||
|
config,
|
||||||
|
updateField,
|
||||||
|
updateColor,
|
||||||
|
updateLayout,
|
||||||
|
setVariant,
|
||||||
|
setCompany,
|
||||||
|
setAddress,
|
||||||
|
resetToDefaults,
|
||||||
|
loadConfig,
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,10 +103,13 @@ export const COMPANY_BRANDING: Record<CompanyId, CompanyBranding> = {
|
|||||||
svg: "/logos/logo-us-light.svg",
|
svg: "/logos/logo-us-light.svg",
|
||||||
},
|
},
|
||||||
slashGrey: {
|
slashGrey: {
|
||||||
png: "https://beletage.ro/img/Grey-slash.png",
|
png: "/logos/us-dash-grey.svg",
|
||||||
svg: "https://beletage.ro/img/Grey-slash.svg",
|
svg: "/logos/us-dash-grey.svg",
|
||||||
|
},
|
||||||
|
slashAccent: {
|
||||||
|
png: "/logos/us-dash-accent.svg",
|
||||||
|
svg: "/logos/us-dash-accent.svg",
|
||||||
},
|
},
|
||||||
slashAccent: { png: "", svg: "" },
|
|
||||||
logoDimensions: { width: 140, height: 24 },
|
logoDimensions: { width: 140, height: 24 },
|
||||||
address: [...ADDR_CHRISTESCU],
|
address: [...ADDR_CHRISTESCU],
|
||||||
website: "www.urbanswitch.ro",
|
website: "www.urbanswitch.ro",
|
||||||
@@ -122,10 +125,13 @@ export const COMPANY_BRANDING: Record<CompanyId, CompanyBranding> = {
|
|||||||
svg: "/logos/logo-sdt-light.svg",
|
svg: "/logos/logo-sdt-light.svg",
|
||||||
},
|
},
|
||||||
slashGrey: {
|
slashGrey: {
|
||||||
png: "https://beletage.ro/img/Grey-slash.png",
|
png: "/logos/sdt-dot-grey.svg",
|
||||||
svg: "https://beletage.ro/img/Grey-slash.svg",
|
svg: "/logos/sdt-dot-grey.svg",
|
||||||
|
},
|
||||||
|
slashAccent: {
|
||||||
|
png: "/logos/sdt-dot-accent.svg",
|
||||||
|
svg: "/logos/sdt-dot-accent.svg",
|
||||||
},
|
},
|
||||||
slashAccent: { png: "", svg: "" },
|
|
||||||
logoDimensions: { width: 71, height: 24 },
|
logoDimensions: { width: 71, height: 24 },
|
||||||
address: [...ADDR_CHRISTESCU],
|
address: [...ADDR_CHRISTESCU],
|
||||||
website: "www.studiideteren.ro",
|
website: "www.studiideteren.ro",
|
||||||
|
|||||||
@@ -37,13 +37,19 @@ export function generateSignatureHtml(config: SignatureConfig): string {
|
|||||||
sectionSpacing,
|
sectionSpacing,
|
||||||
titleSpacing,
|
titleSpacing,
|
||||||
logoSpacing,
|
logoSpacing,
|
||||||
|
logoScale,
|
||||||
} = config.layout;
|
} = config.layout;
|
||||||
const colors = config.colors;
|
const colors = config.colors;
|
||||||
|
|
||||||
const isReply = config.variant === "reply" || config.variant === "minimal";
|
const isReply = config.variant === "reply" || config.variant === "minimal";
|
||||||
const isMinimal = config.variant === "minimal";
|
const isMinimal = config.variant === "minimal";
|
||||||
|
|
||||||
const logoDim = branding.logoDimensions ?? { width: 162, height: 24 };
|
const rawDim = branding.logoDimensions ?? { width: 162, height: 24 };
|
||||||
|
const scale = logoScale ?? 1;
|
||||||
|
const logoDim = {
|
||||||
|
width: Math.round(rawDim.width * scale),
|
||||||
|
height: Math.round(rawDim.height * scale),
|
||||||
|
};
|
||||||
|
|
||||||
const hide =
|
const hide =
|
||||||
"mso-hide:all;display:none!important;max-height:0;overflow:hidden;font-size:0;line-height:0;";
|
"mso-hide:all;display:none!important;max-height:0;overflow:hidden;font-size:0;line-height:0;";
|
||||||
@@ -122,6 +128,7 @@ export function generateSignatureHtml(config: SignatureConfig): string {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
${branding.motto ? `<tr style="${hideBottom}"><td style="padding:${mottoSpacing}px 0 0 0;"><span style="font-size:12px; color:${colors.motto}; font-style:italic;">${esc(branding.motto)}</span></td></tr>` : ""}
|
${branding.motto ? `<tr style="${hideBottom}"><td style="padding:${mottoSpacing}px 0 0 0;"><span style="font-size:12px; color:${colors.motto}; font-style:italic;">${esc(branding.motto)}</span></td></tr>` : ""}
|
||||||
|
${config.banner?.imageUrl ? `<tr style="${hideBottom}"><td style="padding:12px 0 0 0;"><a href="${esc(config.banner.linkUrl || "#")}" style="text-decoration:none; border:0;"><img src="${esc(config.banner.imageUrl)}" alt="${esc(config.banner.alt || "Banner")}" width="${config.banner.width || 540}" height="${config.banner.height || 80}" style="display:block; border:0; max-width:540px; height:auto;"></a></td></tr>` : ""}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>`;
|
</table>`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,16 @@ export interface SignatureLayout {
|
|||||||
sectionSpacing: number;
|
sectionSpacing: number;
|
||||||
titleSpacing: number;
|
titleSpacing: number;
|
||||||
logoSpacing: number;
|
logoSpacing: number;
|
||||||
|
/** Logo scale factor (0.5–2.0, default 1.0) */
|
||||||
|
logoScale: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignatureBanner {
|
||||||
|
imageUrl: string;
|
||||||
|
linkUrl: string;
|
||||||
|
alt: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompanyBranding {
|
export interface CompanyBranding {
|
||||||
@@ -52,6 +62,8 @@ export interface SignatureConfig {
|
|||||||
useSvg: boolean;
|
useSvg: boolean;
|
||||||
/** Override the default company address */
|
/** Override the default company address */
|
||||||
addressOverride?: string[];
|
addressOverride?: string[];
|
||||||
|
/** Optional promotional banner below signature */
|
||||||
|
banner?: SignatureBanner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SavedSignature {
|
export interface SavedSignature {
|
||||||
|
|||||||
Reference in New Issue
Block a user