feat: detailed bill reminders and sidebar polish

Made-with: Cursor
This commit is contained in:
“kirukib” 2026-03-12 00:42:12 +03:00
parent bc44ed3809
commit e5e7550c8c
6 changed files with 260 additions and 25 deletions

View File

@ -83,12 +83,26 @@
gap: 4px;
}
.template-section {
padding-bottom: 4px;
}
.template-section-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.12em;
color: #9ca3af;
margin: 4px 0 6px;
margin: 4px 0 4px;
display: flex;
align-items: center;
justify-content: space-between;
}
.template-section-count {
font-size: 10px;
padding: 2px 6px;
border-radius: 999px;
border: 1px solid rgba(148, 163, 184, 0.7);
}
.template-item {
@ -121,6 +135,22 @@
border: 1px solid rgba(148, 163, 184, 0.7);
}
.template-main {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 2px;
}
.template-label {
font-weight: 500;
}
.template-description {
font-size: 11px;
color: #6b7280;
}
.sidebar-footer {
margin-top: auto;
padding-top: 12px;

View File

@ -61,8 +61,13 @@ function App() {
)
if (!sectionTemplates.length) return null
return (
<div key={section}>
<div className="template-section-label">{section}</div>
<div key={section} className="template-section">
<div className="template-section-label">
{section}
<span className="template-section-count">
{sectionTemplates.length}
</span>
</div>
{sectionTemplates.map((t) => (
<button
key={t.id}
@ -73,8 +78,15 @@ function App() {
}
onClick={() => setSelectedId(t.id)}
>
<span>{t.label}</span>
<span className="template-pill">{t.id}</span>
<div className="template-main">
<span className="template-label">{t.label}</span>
{('description' in t && (t as any).description) ? (
<span className="template-description">
{(t as any).description}
</span>
) : null}
</div>
<span className="template-pill">{t.section}</span>
</button>
))}
</div>

View File

@ -60,12 +60,24 @@ export function BaseEmailShell({
<div>
<div className="footer-brand">Yaltopia Home</div>
<div className="footer-meta">
Transactional email preview · Not an actual bill
Transactional email · Please do not reply to this address.
</div>
</div>
<div className="footer-powered">
<div>
Powered by <strong>Yaltopia Home</strong>
</div>
<div className="footer-approved">
This is an approved message from Yaltopia Home.{' '}
<a
href="https://yaltopia.home/verify-email"
className="footer-link"
>
Verify this email
</a>
.
</div>
</div>
</footer>
</div>
</div>

View File

@ -206,6 +206,15 @@ body {
color: #9ca3af;
}
.footer-approved {
margin-top: 4px;
}
.footer-link {
color: #e5e7eb;
text-decoration: underline;
}
.bank-details {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
@ -217,6 +226,10 @@ body {
margin-top: 10px;
}
.bank-row + .bank-row {
margin-top: 8px;
}
.pill {
display: inline-block;
padding: 3px 9px;

View File

@ -1,6 +1,7 @@
import {
AppointmentBookedEmail,
BillPaymentReminderEmail,
ElectricBillReminderEmail,
InvitationTeamMemberEmail,
ListingApprovedEmail,
NewsletterEmail,
@ -8,6 +9,9 @@ import {
PasswordResetEmail,
PropertyFoundRequestEmail,
PropertyRequestReceivedEmail,
RentBillReminderEmail,
SecurityBillReminderEmail,
WaterBillReminderEmail,
} from './templates'
export const templates = [
@ -51,7 +55,7 @@ export const templates = [
},
{
id: 'bill-reminder',
label: 'Bill payment reminder',
label: 'Bill summary (all charges)',
section: 'Billing',
component: BillPaymentReminderEmail,
props: {
@ -64,12 +68,106 @@ export const templates = [
total: '$1,006.20',
dueDate: 'February 5, 2026',
paymentUrl: 'https://pay.yaltopia.home/invoice/02934392392',
bankDetails: {
bankDetails: [
{
bank: 'Yaltopia Bank',
accountName: 'Yaltopia Home Estates',
accountNumber: '0293439239',
reference: 'RICKY-UNIT-12',
},
{
bank: 'Metro Homes Bank',
accountName: 'Yaltopia Home Estates',
accountNumber: '0043920091',
reference: 'YH-RICKY-UNIT-12',
},
],
paymentOptionsUrl: 'https://yaltopia.home/payments',
},
},
{
id: 'bill-rent',
label: 'Rent reminder only',
section: 'Billing',
component: RentBillReminderEmail,
props: {
recipientName: 'Ricky Ricardo',
period: 'February 2026',
amount: '$900.00',
dueDate: 'February 5, 2026',
paymentUrl: 'https://pay.yaltopia.home/invoice/RENT-02934392392',
bankDetails: [
{
bank: 'Yaltopia Bank',
accountName: 'Yaltopia Home Estates',
accountNumber: '0293439239',
reference: 'RENT-RICKY-UNIT-12',
},
],
paymentOptionsUrl: 'https://yaltopia.home/payments',
},
},
{
id: 'bill-water',
label: 'Water bill reminder',
section: 'Billing',
component: WaterBillReminderEmail,
props: {
recipientName: 'Ricky Ricardo',
period: 'February 2026',
amount: '$24.60',
dueDate: 'February 5, 2026',
bankDetails: [
{
bank: 'City Water Bank',
accountName: 'Yaltopia Home Water',
accountNumber: '2024002460',
reference: 'WATER-RICKY-UNIT-12',
},
],
paymentOptionsUrl: 'https://yaltopia.home/payments',
},
},
{
id: 'bill-security',
label: 'Security fee reminder',
section: 'Billing',
component: SecurityBillReminderEmail,
props: {
recipientName: 'Ricky Ricardo',
period: 'February 2026',
amount: '$35.40',
dueDate: 'February 5, 2026',
bankDetails: [
{
bank: 'Yaltopia Guard Bank',
accountName: 'Yaltopia Home Security',
accountNumber: '8835003540',
reference: 'SEC-RICKY-UNIT-12',
},
],
paymentOptionsUrl: 'https://yaltopia.home/payments',
},
},
{
id: 'bill-electric',
label: 'Electric bill reminder',
section: 'Billing',
component: ElectricBillReminderEmail,
props: {
recipientName: 'Ricky Ricardo',
period: 'February 2026',
amount: '$46.20',
dueDate: 'February 5, 2026',
bankDetails: [
{
bank: 'Gridline Bank',
accountName: 'Yaltopia Home Electric',
accountNumber: '6646004620',
reference: 'ELEC-RICKY-UNIT-12',
},
],
paymentOptionsUrl: 'https://yaltopia.home/payments',
},
},
{

View File

@ -127,6 +127,13 @@ export function PropertyRequestReceivedEmail(
)
}
export interface BankDetails {
bank: string
accountName: string
accountNumber: string
reference: string
}
export interface BillReminderEmailProps extends CommonEmailProps {
period: string
rent?: string
@ -136,12 +143,8 @@ export interface BillReminderEmailProps extends CommonEmailProps {
total: string
dueDate: string
paymentUrl?: string
bankDetails?: {
bank: string
accountName: string
accountNumber: string
reference: string
}
bankDetails?: BankDetails[]
paymentOptionsUrl?: string
}
export function BillPaymentReminderEmail(props: BillReminderEmailProps) {
@ -156,6 +159,7 @@ export function BillPaymentReminderEmail(props: BillReminderEmailProps) {
dueDate,
paymentUrl,
bankDetails,
paymentOptionsUrl,
} = props
return (
<BaseEmailShell
@ -210,13 +214,26 @@ export function BillPaymentReminderEmail(props: BillReminderEmailProps) {
</span>
)}
</div>
{bankDetails && (
{bankDetails && bankDetails.length > 0 && (
<div className="bank-details">
<div>Bank: {bankDetails.bank}</div>
<div>Account name: {bankDetails.accountName}</div>
<div>Account number: {bankDetails.accountNumber}</div>
<div>Reference: {bankDetails.reference}</div>
{bankDetails.map((bank) => (
<div key={`${bank.bank}-${bank.accountNumber}`} className="bank-row">
<div>{bank.bank}</div>
<div>{bank.accountName}</div>
<div>Acct: {bank.accountNumber}</div>
<div>Ref: {bank.reference}</div>
</div>
))}
</div>
)}
{paymentOptionsUrl && (
<p className="body-text-muted">
View all approved payment options at{' '}
<a href={paymentOptionsUrl} className="secondary-link">
yaltopia.home/payments
</a>
.
</p>
)}
<p className="body-text-muted">
If you have already paid, you can ignore this reminder.
@ -225,6 +242,59 @@ export function BillPaymentReminderEmail(props: BillReminderEmailProps) {
)
}
export interface SingleChargeReminderProps extends CommonEmailProps {
period: string
amount: string
dueDate: string
paymentUrl?: string
bankDetails?: BankDetails[]
paymentOptionsUrl?: string
}
export function RentBillReminderEmail(props: SingleChargeReminderProps) {
const { amount, ...rest } = props
return (
<BillPaymentReminderEmail
{...rest}
rent={amount}
total={amount}
/>
)
}
export function WaterBillReminderEmail(props: SingleChargeReminderProps) {
const { amount, ...rest } = props
return (
<BillPaymentReminderEmail
{...rest}
water={amount}
total={amount}
/>
)
}
export function SecurityBillReminderEmail(props: SingleChargeReminderProps) {
const { amount, ...rest } = props
return (
<BillPaymentReminderEmail
{...rest}
security={amount}
total={amount}
/>
)
}
export function ElectricBillReminderEmail(props: SingleChargeReminderProps) {
const { amount, ...rest } = props
return (
<BillPaymentReminderEmail
{...rest}
electric={amount}
total={amount}
/>
)
}
export interface NewsletterEmailProps extends CommonEmailProps {
title: string
intro: string