footer icons and social links

Made-with: Cursor
This commit is contained in:
“kirukib” 2026-04-02 11:18:39 +03:00
parent 3149feb22f
commit a989624fe2
5 changed files with 186 additions and 9 deletions

View File

@ -122,10 +122,6 @@ export default function App() {
</div> </div>
</header> </header>
<div style={{ ...styles.card, padding: 12 }}>
<TemplatePicker templates={templates} value={templateId} onChange={handleTemplateChange} />
</div>
<div style={styles.grid}> <div style={styles.grid}>
<aside style={styles.card}> <aside style={styles.card}>
<div style={{ display: 'grid', gap: 14 }}> <div style={{ display: 'grid', gap: 14 }}>

View File

@ -14,5 +14,12 @@ export const brandDefaults: Brand = {
email: 'reservation@shitayesuitehotel.com', email: 'reservation@shitayesuitehotel.com',
phone1: '+251 96 688 4400', phone1: '+251 96 688 4400',
phone2: '+251 11 46 21000', phone2: '+251 11 46 21000',
social: {
facebookUrl: '',
instagramUrl: '',
xUrl: '',
youtubeUrl: '',
linkedinUrl: '',
},
}, },
} }

View File

@ -118,6 +118,62 @@ export function BrandEditor({ brand, onChange }: BrandEditorProps) {
/> />
</label> </label>
</div> </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>
</div> </div>
</div> </div>
) )

View File

@ -10,6 +10,7 @@ import {
Hr, Hr,
Img, Img,
Preview, Preview,
Link,
} from '@react-email/components' } from '@react-email/components'
import type { Brand } from './types' import type { Brand } from './types'
@ -26,6 +27,71 @@ const DEFAULT_LOGO_PLACEHOLDER =
export function EmailShell({ brand, title, previewText, children }: EmailShellProps) { export function EmailShell({ brand, title, previewText, children }: EmailShellProps) {
const logoUrl = brand.logoUrl?.trim() ? brand.logoUrl.trim() : DEFAULT_LOGO_PLACEHOLDER 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 ( return (
<Html> <Html>
<Head /> <Head />
@ -61,11 +127,56 @@ export function EmailShell({ brand, title, previewText, children }: EmailShellPr
{brand.footer.address ? `${brand.footer.address} ` : ''} {brand.footer.address ? `${brand.footer.address} ` : ''}
{brand.footer.email ? `Email: ${brand.footer.email}` : ''} {brand.footer.email ? `Email: ${brand.footer.email}` : ''}
</Text> </Text>
<Text style={{ margin: '10px 0 0', color: brand.textColor, fontSize: 12, lineHeight: '18px' }}>
{(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 ? `Tel: ${brand.footer.phone1}` : ''}
{brand.footer.phone1 && brand.footer.phone2 ? ' · ' : ''} {brand.footer.phone1 && brand.footer.phone2 ? ' · ' : ''}
{brand.footer.phone2 ? `${brand.footer.phone2}` : ''} {brand.footer.phone2 ? `${brand.footer.phone2}` : ''}
</Text> </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' }}> <Text style={{ margin: '10px 0 0', color: brand.textColor, fontSize: 12, lineHeight: '18px' }}>
© {new Date().getFullYear()} {brand.hotelName}. All rights reserved. © {new Date().getFullYear()} {brand.hotelName}. All rights reserved.
</Text> </Text>

View File

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