feat: detailed bill reminders and sidebar polish
Made-with: Cursor
This commit is contained in:
parent
bc44ed3809
commit
e5e7550c8c
32
src/App.css
32
src/App.css
|
|
@ -83,12 +83,26 @@
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.template-section {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.template-section-label {
|
.template-section-label {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.12em;
|
letter-spacing: 0.12em;
|
||||||
color: #9ca3af;
|
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 {
|
.template-item {
|
||||||
|
|
@ -121,6 +135,22 @@
|
||||||
border: 1px solid rgba(148, 163, 184, 0.7);
|
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 {
|
.sidebar-footer {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
|
|
|
||||||
20
src/App.tsx
20
src/App.tsx
|
|
@ -61,8 +61,13 @@ function App() {
|
||||||
)
|
)
|
||||||
if (!sectionTemplates.length) return null
|
if (!sectionTemplates.length) return null
|
||||||
return (
|
return (
|
||||||
<div key={section}>
|
<div key={section} className="template-section">
|
||||||
<div className="template-section-label">{section}</div>
|
<div className="template-section-label">
|
||||||
|
{section}
|
||||||
|
<span className="template-section-count">
|
||||||
|
{sectionTemplates.length}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{sectionTemplates.map((t) => (
|
{sectionTemplates.map((t) => (
|
||||||
<button
|
<button
|
||||||
key={t.id}
|
key={t.id}
|
||||||
|
|
@ -73,8 +78,15 @@ function App() {
|
||||||
}
|
}
|
||||||
onClick={() => setSelectedId(t.id)}
|
onClick={() => setSelectedId(t.id)}
|
||||||
>
|
>
|
||||||
<span>{t.label}</span>
|
<div className="template-main">
|
||||||
<span className="template-pill">{t.id}</span>
|
<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>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -60,11 +60,23 @@ export function BaseEmailShell({
|
||||||
<div>
|
<div>
|
||||||
<div className="footer-brand">Yaltopia Home</div>
|
<div className="footer-brand">Yaltopia Home</div>
|
||||||
<div className="footer-meta">
|
<div className="footer-meta">
|
||||||
Transactional email preview · Not an actual bill
|
Transactional email · Please do not reply to this address.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="footer-powered">
|
<div className="footer-powered">
|
||||||
Powered by <strong>Yaltopia Home</strong>
|
<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>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,15 @@ body {
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer-approved {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-link {
|
||||||
|
color: #e5e7eb;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.bank-details {
|
.bank-details {
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||||
"Liberation Mono", "Courier New", monospace;
|
"Liberation Mono", "Courier New", monospace;
|
||||||
|
|
@ -217,6 +226,10 @@ body {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bank-row + .bank-row {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.pill {
|
.pill {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 3px 9px;
|
padding: 3px 9px;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
AppointmentBookedEmail,
|
AppointmentBookedEmail,
|
||||||
BillPaymentReminderEmail,
|
BillPaymentReminderEmail,
|
||||||
|
ElectricBillReminderEmail,
|
||||||
InvitationTeamMemberEmail,
|
InvitationTeamMemberEmail,
|
||||||
ListingApprovedEmail,
|
ListingApprovedEmail,
|
||||||
NewsletterEmail,
|
NewsletterEmail,
|
||||||
|
|
@ -8,6 +9,9 @@ import {
|
||||||
PasswordResetEmail,
|
PasswordResetEmail,
|
||||||
PropertyFoundRequestEmail,
|
PropertyFoundRequestEmail,
|
||||||
PropertyRequestReceivedEmail,
|
PropertyRequestReceivedEmail,
|
||||||
|
RentBillReminderEmail,
|
||||||
|
SecurityBillReminderEmail,
|
||||||
|
WaterBillReminderEmail,
|
||||||
} from './templates'
|
} from './templates'
|
||||||
|
|
||||||
export const templates = [
|
export const templates = [
|
||||||
|
|
@ -51,7 +55,7 @@ export const templates = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'bill-reminder',
|
id: 'bill-reminder',
|
||||||
label: 'Bill payment reminder',
|
label: 'Bill summary (all charges)',
|
||||||
section: 'Billing',
|
section: 'Billing',
|
||||||
component: BillPaymentReminderEmail,
|
component: BillPaymentReminderEmail,
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -64,12 +68,106 @@ export const templates = [
|
||||||
total: '$1,006.20',
|
total: '$1,006.20',
|
||||||
dueDate: 'February 5, 2026',
|
dueDate: 'February 5, 2026',
|
||||||
paymentUrl: 'https://pay.yaltopia.home/invoice/02934392392',
|
paymentUrl: 'https://pay.yaltopia.home/invoice/02934392392',
|
||||||
bankDetails: {
|
bankDetails: [
|
||||||
bank: 'Yaltopia Bank',
|
{
|
||||||
accountName: 'Yaltopia Home Estates',
|
bank: 'Yaltopia Bank',
|
||||||
accountNumber: '0293439239',
|
accountName: 'Yaltopia Home Estates',
|
||||||
reference: 'RICKY-UNIT-12',
|
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',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,13 @@ export function PropertyRequestReceivedEmail(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BankDetails {
|
||||||
|
bank: string
|
||||||
|
accountName: string
|
||||||
|
accountNumber: string
|
||||||
|
reference: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface BillReminderEmailProps extends CommonEmailProps {
|
export interface BillReminderEmailProps extends CommonEmailProps {
|
||||||
period: string
|
period: string
|
||||||
rent?: string
|
rent?: string
|
||||||
|
|
@ -136,12 +143,8 @@ export interface BillReminderEmailProps extends CommonEmailProps {
|
||||||
total: string
|
total: string
|
||||||
dueDate: string
|
dueDate: string
|
||||||
paymentUrl?: string
|
paymentUrl?: string
|
||||||
bankDetails?: {
|
bankDetails?: BankDetails[]
|
||||||
bank: string
|
paymentOptionsUrl?: string
|
||||||
accountName: string
|
|
||||||
accountNumber: string
|
|
||||||
reference: string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BillPaymentReminderEmail(props: BillReminderEmailProps) {
|
export function BillPaymentReminderEmail(props: BillReminderEmailProps) {
|
||||||
|
|
@ -156,6 +159,7 @@ export function BillPaymentReminderEmail(props: BillReminderEmailProps) {
|
||||||
dueDate,
|
dueDate,
|
||||||
paymentUrl,
|
paymentUrl,
|
||||||
bankDetails,
|
bankDetails,
|
||||||
|
paymentOptionsUrl,
|
||||||
} = props
|
} = props
|
||||||
return (
|
return (
|
||||||
<BaseEmailShell
|
<BaseEmailShell
|
||||||
|
|
@ -210,14 +214,27 @@ export function BillPaymentReminderEmail(props: BillReminderEmailProps) {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{bankDetails && (
|
{bankDetails && bankDetails.length > 0 && (
|
||||||
<div className="bank-details">
|
<div className="bank-details">
|
||||||
<div>Bank: {bankDetails.bank}</div>
|
{bankDetails.map((bank) => (
|
||||||
<div>Account name: {bankDetails.accountName}</div>
|
<div key={`${bank.bank}-${bank.accountNumber}`} className="bank-row">
|
||||||
<div>Account number: {bankDetails.accountNumber}</div>
|
<div>{bank.bank}</div>
|
||||||
<div>Reference: {bankDetails.reference}</div>
|
<div>{bank.accountName}</div>
|
||||||
|
<div>Acct: {bank.accountNumber}</div>
|
||||||
|
<div>Ref: {bank.reference}</div>
|
||||||
|
</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">
|
<p className="body-text-muted">
|
||||||
If you have already paid, you can ignore this reminder.
|
If you have already paid, you can ignore this reminder.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -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 {
|
export interface NewsletterEmailProps extends CommonEmailProps {
|
||||||
title: string
|
title: string
|
||||||
intro: string
|
intro: string
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user