rubis/apps/api/app/mails/checkin_email.tsx
ordinarthur eb248c98b8
All checks were successful
Build & Deploy API / build-and-deploy (push) Successful in 1m8s
style(mail): fond crème edge-to-edge (au lieu d'un container blanc)
Avant : body crème + container blanc avec border-radius + bordure → effet
"card flottante" qui laissait des marges crème en haut/bas et un fond
blanc pour le contenu. Sur iPhone Mail, cette structure créait un gap
mal rendu entre la frame Mail.app et le header rubis-deep.

Maintenant :
- bodyStyle.padding 24px 0 → 0 (rubis-deep colle au haut de la zone mail)
- containerStyle background blanc → crème (toute la zone email est crème,
  cohérente avec la palette)
- containerStyle borderRadius + border supprimés (edge-to-edge)
- invoiceCardStyle (checkin) + summaryCardStyle (relance) passent en
  blanc pour se détacher du nouveau fond crème
- Dark mode CSS : .rubis-container override aussi mis à crème

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 22:55:47 +02:00

216 lines
5.7 KiB
TypeScript

/**
* Template email check-in — envoyé à L'UTILISATEUR (le patron de TPE)
* pour lui demander si une facture donnée a été payée AVANT que la 1re
* relance ne parte chez son client.
*
* Structure :
* - Header rubis-deep "Rubis · Confirmation requise"
* - Eyebrow + question Avez-vous été payé ?
* - Card facture (numéro + montant + client + échéance)
* - 2 gros boutons CTA :
* • "✓ Oui — la facture est payée" → /paid (status=paid + cancel relances)
* • "→ Non — toujours impayée, lance les relances" → /pending
* - Mention TTL 24h
*/
import * as React from 'react'
import { Section, Text, Button, Heading, Hr } from '@react-email/components'
import { BRAND, sp } from './_brand.js'
import { EmailLayout } from './_layout.js'
export type CheckinEmailProps = {
invoice: {
numero: string
amountFormatted: string
dueDateFormatted: string
}
client: { name: string }
user: { fullName: string | null }
paidUrl: string
pendingUrl: string
/** URL landing publique (footer cliquable "Rubis sur l'ongle"). */
landingUrl?: string
}
export function CheckinEmail({
invoice,
client,
user,
paidUrl,
pendingUrl,
landingUrl,
}: CheckinEmailProps) {
const greeting = user.fullName ? `Bonjour ${user.fullName.split(' ')[0]},` : 'Bonjour,'
return (
<EmailLayout
preview={`${invoice.numero} (${invoice.amountFormatted}) — payée par ${client.name} ?`}
brandName="Rubis"
brandSubtitle="Confirmation requise"
landingUrl={landingUrl}
>
<Text style={greetingStyle}>{greeting}</Text>
<Heading as="h1" style={titleStyle}>
Avez-vous é <em style={emStyle}>payé</em> sur cette facture&nbsp;?
</Heading>
<Text style={leadStyle}>
Aucune relance ne part sans votre validation. Si la facture est déjà
réglée, on évite l&apos;email inutile et on encaisse +1 rubis.
</Text>
{/* Card facture */}
<Section style={invoiceCardStyle}>
<Text style={invoiceNumeroStyle}>{invoice.numero}</Text>
<Text style={invoiceClientStyle}>{client.name}</Text>
<Text style={invoiceAmountStyle}>{invoice.amountFormatted}</Text>
<Text style={invoiceDueStyle}>
Échéance le <strong>{invoice.dueDateFormatted}</strong>
</Text>
</Section>
{/* CTA boutons */}
<Section style={ctaSectionStyle}>
<Button href={paidUrl} style={primaryButtonStyle}>
Oui, la facture est payée
</Button>
</Section>
<Section style={{ marginTop: sp.md }}>
<Button href={pendingUrl} style={secondaryButtonStyle}>
Non, toujours en attente lance les relances
</Button>
</Section>
<Hr style={hrStyle} />
<Text style={footnoteStyle}>
Ces liens expirent dans 24h. Vous pouvez aussi répondre directement
depuis l&apos;app sur la fiche facture.
</Text>
</EmailLayout>
)
}
// ---------------------------------------------------------------------------
// Styles inline
// ---------------------------------------------------------------------------
const greetingStyle: React.CSSProperties = {
fontSize: '14px',
color: BRAND.ink2,
margin: `0 0 ${sp.md} 0`,
}
const titleStyle: React.CSSProperties = {
color: BRAND.ink,
fontSize: '24px',
fontWeight: 700,
letterSpacing: '-0.018em',
lineHeight: '1.2',
margin: `0 0 ${sp.md} 0`,
}
const emStyle: React.CSSProperties = {
color: BRAND.rubis,
fontStyle: 'normal',
}
const leadStyle: React.CSSProperties = {
color: BRAND.ink2,
fontSize: '14px',
lineHeight: '1.6',
margin: `0 0 ${sp.xl} 0`,
}
const invoiceCardStyle: React.CSSProperties = {
// Blanc sur fond crème pour détacher la card visuellement (avant
// c'était crème sur container blanc — maintenant le container est crème).
backgroundColor: BRAND.white,
border: `1px solid ${BRAND.line}`,
borderRadius: BRAND.radiusCard,
padding: `${sp.lg} ${sp.xl}`,
margin: `0 0 ${sp.xl} 0`,
}
const invoiceNumeroStyle: React.CSSProperties = {
fontSize: '13px',
fontWeight: 600,
color: BRAND.ink2,
letterSpacing: '0.02em',
margin: 0,
}
const invoiceClientStyle: React.CSSProperties = {
fontSize: '13px',
color: BRAND.ink3,
margin: `${sp.xs} 0 ${sp.md} 0`,
}
const invoiceAmountStyle: React.CSSProperties = {
fontSize: '28px',
fontWeight: 800,
letterSpacing: '-0.02em',
color: BRAND.ink,
margin: 0,
lineHeight: '1',
fontVariantNumeric: 'tabular-nums',
}
const invoiceDueStyle: React.CSSProperties = {
fontSize: '12px',
color: BRAND.ink3,
margin: `${sp.sm} 0 0 0`,
}
const ctaSectionStyle: React.CSSProperties = {
marginTop: 0,
}
const primaryButtonStyle: React.CSSProperties = {
backgroundColor: BRAND.rubis,
color: BRAND.white,
borderRadius: BRAND.radiusButton,
display: 'block',
textAlign: 'center',
textDecoration: 'none',
padding: `${sp.md} ${sp.xl}`,
fontSize: '15px',
fontWeight: 600,
width: '100%',
boxSizing: 'border-box',
// Shadow rubis-teintée pour cohérence avec les boutons SPA
boxShadow: '0 4px 12px rgba(159, 18, 57, 0.25)',
}
const secondaryButtonStyle: React.CSSProperties = {
backgroundColor: BRAND.white,
color: BRAND.ink,
border: `1px solid ${BRAND.ink}`,
borderRadius: BRAND.radiusButton,
display: 'block',
textAlign: 'center',
textDecoration: 'none',
padding: `${sp.md} ${sp.xl}`,
fontSize: '15px',
fontWeight: 600,
width: '100%',
boxSizing: 'border-box',
}
const hrStyle: React.CSSProperties = {
borderColor: BRAND.line,
borderStyle: 'solid',
borderWidth: '0 0 1px 0',
margin: `${sp.xl} 0 ${sp.lg} 0`,
}
const footnoteStyle: React.CSSProperties = {
fontSize: '11.5px',
color: BRAND.ink3,
lineHeight: '1.5',
margin: 0,
fontStyle: 'italic',
}