single selector + footer icons

Made-with: Cursor
This commit is contained in:
“kirukib” 2026-04-02 11:24:03 +03:00
parent a989624fe2
commit adbf36578b
5 changed files with 138 additions and 184 deletions

View File

@ -122,11 +122,14 @@ export default function App() {
</div>
</header>
<div style={{ ...styles.card, padding: 12 }}>
<TemplatePicker templates={templates} value={templateId} onChange={handleTemplateChange} />
</div>
<div style={styles.grid}>
<aside style={styles.card}>
<div style={{ display: 'grid', gap: 14 }}>
<BrandEditor brand={brand} onChange={setBrand} />
<TemplatePicker templates={templates} value={templateId} onChange={handleTemplateChange} />
{template ? <VariablesForm key={templateId} template={template} data={data} onChange={setData} /> : null}
<div style={{ display: 'grid', gap: 8 }}>

View File

@ -14,12 +14,11 @@ export const brandDefaults: Brand = {
email: 'reservation@shitayesuitehotel.com',
phone1: '+251 96 688 4400',
phone2: '+251 11 46 21000',
social: {
facebookUrl: '',
instagramUrl: '',
xUrl: '',
youtubeUrl: '',
linkedinUrl: '',
},
// Placeholder social links (edit in the Brand editor to match your real profiles).
socialFacebookUrl: 'https://facebook.com/yourhotel',
socialInstagramUrl: 'https://instagram.com/yourhotel',
socialTwitterUrl: 'https://x.com/yourhotel',
socialLinkedInUrl: 'https://linkedin.com/company/yourhotel',
},
}

View File

@ -119,60 +119,50 @@ export function BrandEditor({ brand, onChange }: BrandEditorProps) {
</label>
</div>
<div style={{ display: 'grid', gap: 10, paddingTop: 4 }}>
<div style={{ fontSize: 12, opacity: 0.8 }}>Social links</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
<label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<span style={{ fontSize: 12, opacity: 0.8 }}>Facebook URL</span>
<input
value={brand.footer.social?.facebookUrl || ''}
onChange={(e) =>
onChange({ ...brand, footer: { ...brand.footer, social: { ...brand.footer.social, facebookUrl: e.target.value } } })
}
style={{ padding: '8px 10px', border: '1px solid #e5e7eb', borderRadius: 8 }}
/>
</label>
<label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<span style={{ fontSize: 12, opacity: 0.8 }}>Instagram URL</span>
<input
value={brand.footer.social?.instagramUrl || ''}
onChange={(e) =>
onChange({ ...brand, footer: { ...brand.footer, social: { ...brand.footer.social, instagramUrl: e.target.value } } })
}
style={{ padding: '8px 10px', border: '1px solid #e5e7eb', borderRadius: 8 }}
/>
</label>
<label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<span style={{ fontSize: 12, opacity: 0.8 }}>X URL</span>
<input
value={brand.footer.social?.xUrl || ''}
onChange={(e) =>
onChange({ ...brand, footer: { ...brand.footer, social: { ...brand.footer.social, xUrl: e.target.value } } })
}
style={{ padding: '8px 10px', border: '1px solid #e5e7eb', borderRadius: 8 }}
/>
</label>
<label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<span style={{ fontSize: 12, opacity: 0.8 }}>YouTube URL</span>
<input
value={brand.footer.social?.youtubeUrl || ''}
onChange={(e) =>
onChange({ ...brand, footer: { ...brand.footer, social: { ...brand.footer.social, youtubeUrl: e.target.value } } })
}
style={{ padding: '8px 10px', border: '1px solid #e5e7eb', borderRadius: 8 }}
/>
</label>
<label style={{ display: 'flex', flexDirection: 'column', gap: 6, gridColumn: '1 / -1' }}>
<span style={{ fontSize: 12, opacity: 0.8 }}>LinkedIn URL</span>
<input
value={brand.footer.social?.linkedinUrl || ''}
onChange={(e) =>
onChange({ ...brand, footer: { ...brand.footer, social: { ...brand.footer.social, linkedinUrl: e.target.value } } })
}
style={{ padding: '8px 10px', border: '1px solid #e5e7eb', borderRadius: 8 }}
/>
</label>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginTop: 4 }}>
<label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<span style={{ fontSize: 12, opacity: 0.8 }}>Facebook URL</span>
<input
value={brand.footer.socialFacebookUrl || ''}
onChange={(e) =>
onChange({ ...brand, footer: { ...brand.footer, socialFacebookUrl: e.target.value } })
}
style={{ padding: '8px 10px', border: '1px solid #e5e7eb', borderRadius: 8 }}
/>
</label>
<label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<span style={{ fontSize: 12, opacity: 0.8 }}>Instagram URL</span>
<input
value={brand.footer.socialInstagramUrl || ''}
onChange={(e) =>
onChange({ ...brand, footer: { ...brand.footer, socialInstagramUrl: e.target.value } })
}
style={{ padding: '8px 10px', border: '1px solid #e5e7eb', borderRadius: 8 }}
/>
</label>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
<label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<span style={{ fontSize: 12, opacity: 0.8 }}>Twitter / X URL</span>
<input
value={brand.footer.socialTwitterUrl || ''}
onChange={(e) =>
onChange({ ...brand, footer: { ...brand.footer, socialTwitterUrl: e.target.value } })
}
style={{ padding: '8px 10px', border: '1px solid #e5e7eb', borderRadius: 8 }}
/>
</label>
<label style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<span style={{ fontSize: 12, opacity: 0.8 }}>LinkedIn URL</span>
<input
value={brand.footer.socialLinkedInUrl || ''}
onChange={(e) =>
onChange({ ...brand, footer: { ...brand.footer, socialLinkedInUrl: e.target.value } })
}
style={{ padding: '8px 10px', border: '1px solid #e5e7eb', borderRadius: 8 }}
/>
</label>
</div>
</div>
</div>

View File

@ -27,71 +27,6 @@ const DEFAULT_LOGO_PLACEHOLDER =
export function EmailShell({ brand, title, previewText, children }: EmailShellProps) {
const logoUrl = brand.logoUrl?.trim() ? brand.logoUrl.trim() : DEFAULT_LOGO_PLACEHOLDER
const phoneColor = brand.textColor
const iconColor = brand.primaryColor
const IconPhone = () => (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.62 10.79C8.05 13.85 10.15 15.95 13.21 17.38L15.46 15.13C15.64 14.95 15.89 14.88 16.13 14.93C17.36 15.18 18.64 15.18 19.87 14.93C20.39 14.82 20.82 15.25 20.71 15.77C20.45 17 20.03 18.16 19.46 19.23C19.27 19.59 18.88 19.78 18.48 19.72C14.3 19.08 10.92 15.7 10.28 11.52C10.22 11.12 10.41 10.73 10.77 10.54C11.84 9.97 13 9.55 14.23 9.29C14.75 9.18 15.18 9.61 15.07 10.13C14.82 11.36 14.82 12.64 15.07 13.87C15.12 14.11 15.05 14.36 14.87 14.54L12.62 16.79"
stroke={iconColor}
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
const IconSocial = ({ kind }: { kind: 'facebook' | 'instagram' | 'x' | 'youtube' | 'linkedin' }) => {
const common = { stroke: iconColor, strokeWidth: 1.6, strokeLinecap: 'round' as const, strokeLinejoin: 'round' as const }
if (kind === 'facebook') {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M14 8H16V5H14C12.34 5 11 6.34 11 8V10H9V13H11V19H14V13H16L17 10H14V8C14 7.45 14.45 7 15 7H16"
{...common}
/>
</svg>
)
}
if (kind === 'instagram') {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 2H17C19.76 2 22 4.24 22 7V17C22 19.76 19.76 22 17 22H7C4.24 22 2 19.76 2 17V7C2 4.24 4.24 2 7 2Z" {...common} />
<path d="M17.5 6.5H17.51" {...common} />
<path d="M12 17C14.761 17 17 14.761 17 12C17 9.239 14.761 7 12 7C9.239 7 7 9.239 7 12C7 14.761 9.239 17 12 17Z" {...common} />
</svg>
)
}
if (kind === 'x') {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 4L20 20" {...common} />
<path d="M20 4L4 20" {...common} />
</svg>
)
}
if (kind === 'youtube') {
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M22 12C22 12 22 17 21 18C20 19 17 19 12 19C7 19 4 19 3 18C2 17 2 12 2 12C2 12 2 7 3 6C4 5 7 5 12 5C17 5 20 5 21 6C22 7 22 12 22 12Z"
{...common}
/>
<path d="M10 15L15 12L10 9V15Z" {...common} />
</svg>
)
}
// linkedin
return (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 9H3V21H6V9Z" {...common} />
<path d="M4.5 3C5.33 3 6 3.67 6 4.5C6 5.33 5.33 6 4.5 6C3.67 6 3 5.33 3 4.5C3 3.67 3.67 3 4.5 3Z" {...common} />
<path d="M21 21H18V14.5C18 13.12 17.88 11 15.5 11C13.1 11 13 13.12 13 14.5V21H10V9H13V10.5H13.04C13.49 9.71 14.49 9 16 9C19 9 21 10.87 21 14.5V21Z" {...common} />
</svg>
)
}
return (
<Html>
<Head />
@ -125,59 +60,88 @@ export function EmailShell({ brand, title, previewText, children }: EmailShellPr
<Section style={{ padding: '18px 0 28px' }}>
<Text style={{ margin: 0, color: brand.textColor, fontSize: 12, lineHeight: '18px' }}>
{brand.footer.address ? `${brand.footer.address} ` : ''}
{brand.footer.email ? `Email: ${brand.footer.email}` : ''}
{brand.footer.email ? `\u2709 ${brand.footer.email}` : ''}
</Text>
{(brand.footer.phone1 || brand.footer.phone2) ? (
<Section style={{ marginTop: 10 }}>
<Section style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<IconPhone />
<Text style={{ margin: 0, color: phoneColor, fontSize: 12, lineHeight: '18px' }}>
{brand.footer.phone1 ? `Tel: ${brand.footer.phone1}` : ''}
{brand.footer.phone1 && brand.footer.phone2 ? ' · ' : ''}
{brand.footer.phone2 ? `${brand.footer.phone2}` : ''}
</Text>
</Section>
</Section>
) : null}
{/* Social icons */}
{brand.footer.social &&
(brand.footer.social.facebookUrl ||
brand.footer.social.instagramUrl ||
brand.footer.social.xUrl ||
brand.footer.social.youtubeUrl ||
brand.footer.social.linkedinUrl) ? (
<Section style={{ marginTop: 12, display: 'flex', gap: 14, alignItems: 'center' }}>
{brand.footer.social.facebookUrl ? (
<Link href={brand.footer.social.facebookUrl} style={{ display: 'inline-block' }}>
<IconSocial kind="facebook" />
</Link>
) : null}
{brand.footer.social.instagramUrl ? (
<Link href={brand.footer.social.instagramUrl} style={{ display: 'inline-block' }}>
<IconSocial kind="instagram" />
</Link>
) : null}
{brand.footer.social.xUrl ? (
<Link href={brand.footer.social.xUrl} style={{ display: 'inline-block' }}>
<IconSocial kind="x" />
</Link>
) : null}
{brand.footer.social.youtubeUrl ? (
<Link href={brand.footer.social.youtubeUrl} style={{ display: 'inline-block' }}>
<IconSocial kind="youtube" />
</Link>
) : null}
{brand.footer.social.linkedinUrl ? (
<Link href={brand.footer.social.linkedinUrl} style={{ display: 'inline-block' }}>
<IconSocial kind="linkedin" />
</Link>
) : null}
</Section>
) : null}
<Text style={{ margin: '10px 0 0', color: brand.textColor, fontSize: 12, lineHeight: '18px' }}>
{brand.footer.phone1 ? `\u260E ${brand.footer.phone1}` : ''}
{brand.footer.phone1 && brand.footer.phone2 ? ' · ' : ''}
{brand.footer.phone2 ? brand.footer.phone2 : ''}
</Text>
<Section style={{ margin: '14px 0 0', padding: 0 }}>
<Text style={{ margin: '0 0 8px', color: brand.textColor, fontSize: 12, opacity: 0.85, lineHeight: '18px' }}>
Follow us
</Text>
<Link
href={brand.footer.socialFacebookUrl || '#'}
style={{
display: 'inline-block',
marginRight: 8,
marginBottom: 8,
padding: '8px 12px',
borderRadius: 999,
backgroundColor: brand.primaryColor,
color: '#ffffff',
textDecoration: 'none',
fontSize: 12,
fontWeight: 700,
}}
>
f
</Link>
<Link
href={brand.footer.socialInstagramUrl || '#'}
style={{
display: 'inline-block',
marginRight: 8,
marginBottom: 8,
padding: '8px 12px',
borderRadius: 999,
backgroundColor: brand.primaryColor,
color: '#ffffff',
textDecoration: 'none',
fontSize: 12,
fontWeight: 700,
}}
>
IG
</Link>
<Link
href={brand.footer.socialTwitterUrl || '#'}
style={{
display: 'inline-block',
marginRight: 8,
marginBottom: 8,
padding: '8px 12px',
borderRadius: 999,
backgroundColor: brand.primaryColor,
color: '#ffffff',
textDecoration: 'none',
fontSize: 12,
fontWeight: 700,
}}
>
X
</Link>
<Link
href={brand.footer.socialLinkedInUrl || '#'}
style={{
display: 'inline-block',
padding: '8px 12px',
borderRadius: 999,
backgroundColor: brand.primaryColor,
color: '#ffffff',
textDecoration: 'none',
fontSize: 12,
fontWeight: 700,
}}
>
in
</Link>
</Section>
<Text style={{ margin: '14px 0 0', color: brand.textColor, fontSize: 12, lineHeight: '18px' }}>
© {new Date().getFullYear()} {brand.hotelName}. All rights reserved.
</Text>
</Section>

View File

@ -5,13 +5,11 @@ export type BrandFooter = {
email?: string
phone1?: string
phone2?: string
social?: {
facebookUrl?: string
instagramUrl?: string
xUrl?: string
youtubeUrl?: string
linkedinUrl?: string
}
socialFacebookUrl?: string
socialInstagramUrl?: string
socialTwitterUrl?: string
socialLinkedInUrl?: string
}
export type Brand = {