Harden invoice public links and tighten draft UI.

Use publicId in links, compact key-point/account labels, English business names, and readable favicon deploy perms.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-07-26 12:00:10 +03:30
co-authored by Cursor
parent cbffb23ec3
commit 2e40d5eb4c
11 changed files with 152 additions and 107 deletions
@@ -11,6 +11,12 @@ import {
import tableStyles from '../pages/BusinessesPage.module.css'
import styles from '../pages/BusinessInvoicesPage.module.css'
function focusKeyPointInput(index: number) {
const el = document.querySelector<HTMLInputElement>(`input[data-keypoint-index="${index}"]`)
el?.focus()
el?.select()
}
type Props = {
itemTemplates: InvoiceItemTemplate[]
items: DraftLineItem[]
@@ -192,9 +198,9 @@ export function InvoiceDraftFields({
<div className={styles.repeatStack}>
{keyPoints.map((point, index) => (
<div key={point.key} className={styles.repeatRow}>
<div className={`${tableStyles.field} ${styles.flexGrow}`}>
<label>Point {index + 1}</label>
<div className={`${tableStyles.field} ${styles.flexGrow} ${styles.fieldNoLabel}`}>
<input
data-keypoint-index={index}
value={point.text}
onChange={(e) =>
onKeyPointsChange(
@@ -203,7 +209,19 @@ export function InvoiceDraftFields({
),
)
}
onKeyDown={(e) => {
if (e.key !== 'Enter') return
e.preventDefault()
const next = index + 1
if (next < keyPoints.length) {
focusKeyPointInput(next)
return
}
onKeyPointsChange([...keyPoints, emptyDraftKeyPoint()])
window.setTimeout(() => focusKeyPointInput(next), 0)
}}
placeholder="e.g. Payment due within 7 days"
aria-label={`Key point ${index + 1}`}
/>
</div>
<button
@@ -237,75 +255,85 @@ export function InvoiceDraftFields({
<p className={styles.templateHint}>No bank accounts yet.</p>
) : (
<div className={styles.repeatStack}>
{accounts.map((acc) => (
<div key={acc.key} className={styles.accountRow}>
<div className={`${tableStyles.field} ${styles.accountCol2}`}>
<label>Bank name</label>
<input
value={acc.bankName}
onChange={(e) =>
onAccountsChange(
accounts.map((a) =>
a.key === acc.key ? { ...a, bankName: e.target.value } : a,
),
)
}
placeholder="Bank name"
/>
</div>
<div className={`${tableStyles.field} ${styles.accountCol2}`}>
<label>Account holder</label>
<input
value={acc.accountHolderName}
onChange={(e) =>
onAccountsChange(
accounts.map((a) =>
a.key === acc.key ? { ...a, accountHolderName: e.target.value } : a,
),
)
}
placeholder="Account holder name"
/>
</div>
<div className={`${tableStyles.field} ${styles.accountCol3}`}>
<label>Card number</label>
<input
value={acc.cardNumber}
onChange={(e) =>
onAccountsChange(
accounts.map((a) =>
a.key === acc.key ? { ...a, cardNumber: e.target.value } : a,
),
)
}
placeholder="Optional"
/>
</div>
<div className={`${tableStyles.field} ${styles.accountCol5}`}>
<label>IBAN</label>
<input
value={acc.iban}
onChange={(e) =>
onAccountsChange(
accounts.map((a) =>
a.key === acc.key ? { ...a, iban: e.target.value } : a,
),
)
}
placeholder="Optional"
/>
</div>
<button
type="button"
className={styles.removeFieldBtn}
onClick={() => onAccountsChange(accounts.filter((a) => a.key !== acc.key))}
aria-label="Remove account"
title="Remove"
{accounts.map((acc, index) => {
const showLabels = index === 0
return (
<div
key={acc.key}
className={`${styles.accountRow} ${showLabels ? '' : styles.accountRowPlain}`}
>
<X size={16} />
</button>
</div>
))}
<div className={`${tableStyles.field} ${styles.accountCol2}`}>
{showLabels ? <label>Bank name</label> : null}
<input
value={acc.bankName}
onChange={(e) =>
onAccountsChange(
accounts.map((a) =>
a.key === acc.key ? { ...a, bankName: e.target.value } : a,
),
)
}
placeholder="Bank name"
aria-label="Bank name"
/>
</div>
<div className={`${tableStyles.field} ${styles.accountCol2}`}>
{showLabels ? <label>Account holder</label> : null}
<input
value={acc.accountHolderName}
onChange={(e) =>
onAccountsChange(
accounts.map((a) =>
a.key === acc.key ? { ...a, accountHolderName: e.target.value } : a,
),
)
}
placeholder="Account holder name"
aria-label="Account holder"
/>
</div>
<div className={`${tableStyles.field} ${styles.accountCol3}`}>
{showLabels ? <label>Card number</label> : null}
<input
value={acc.cardNumber}
onChange={(e) =>
onAccountsChange(
accounts.map((a) =>
a.key === acc.key ? { ...a, cardNumber: e.target.value } : a,
),
)
}
placeholder="Optional"
aria-label="Card number"
/>
</div>
<div className={`${tableStyles.field} ${styles.accountCol5}`}>
{showLabels ? <label>IBAN</label> : null}
<input
value={acc.iban}
onChange={(e) =>
onAccountsChange(
accounts.map((a) =>
a.key === acc.key ? { ...a, iban: e.target.value } : a,
),
)
}
placeholder="Optional"
aria-label="IBAN"
/>
</div>
<button
type="button"
className={styles.removeFieldBtn}
onClick={() => onAccountsChange(accounts.filter((a) => a.key !== acc.key))}
aria-label="Remove account"
title="Remove"
>
<X size={16} />
</button>
</div>
)
})}
</div>
)}
</div>