fix: react-doctor label-has-associated-control 9→0, async-await-in-loop 3→0 (Promise.allSettled), role-supports-aria-props 3→0 (already done in prior batch)

This commit is contained in:
Nora
2026-06-26 06:01:06 -06:00
parent ed5afe4b21
commit d6d6a366e3
7 changed files with 220 additions and 139 deletions
+39 -15
View File
@@ -340,15 +340,13 @@ await getSession(); const adminUser = await getAdminUser();
return { success: false, error: "Not authorized for this brand" }; return { success: false, error: "Not authorized for this brand" };
} }
const result: ImportResult = { created: 0, updated: 0, skipped: 0, errors: [] }; const validRows = params.contacts.filter(
(row) => row.email || row.phone,
);
for (const row of params.contacts) { const settled = await Promise.allSettled(
if (!row.email && !row.phone) { validRows.map((row) =>
result.skipped++; upsertContact({
continue;
}
try {
const r = await upsertContact({
brand_id: params.brandId, brand_id: params.brandId,
email: row.email, email: row.email,
phone: row.phone, phone: row.phone,
@@ -361,16 +359,42 @@ await getSession(); const adminUser = await getAdminUser();
external_id: row.external_id, external_id: row.external_id,
tags: row.tags, tags: row.tags,
metadata: row._metadata, metadata: row._metadata,
}); }).then(
if (r.success) result.created++; (r) => ({ row, r }),
else { (err) => ({ row, err }),
result.errors.push({ row, error: r.error }); ),
} ),
} catch (err) { );
const result: ImportResult = {
created: 0,
updated: 0,
skipped: params.contacts.length - validRows.length,
errors: [],
};
for (const entry of settled) {
if (entry.status === "rejected") {
const err = (entry as PromiseRejectedResult).reason;
result.errors.push({ result.errors.push({
row, row: validRows[settled.indexOf(entry)],
error: err instanceof Error ? err.message : String(err), error: err instanceof Error ? err.message : String(err),
}); });
continue;
}
const value = entry.value as
| { row: ContactImportEntry; r: { success: boolean; error?: string } }
| { row: ContactImportEntry; err: unknown };
if ("err" in value) {
const err = value.err;
result.errors.push({
row: value.row,
error: err instanceof Error ? err.message : String(err),
});
} else if (value.r.success) {
result.created++;
} else {
result.errors.push({ row: value.row, error: value.r.error ?? "Unknown error" });
} }
} }
+37 -10
View File
@@ -40,29 +40,56 @@ await getSession(); const adminUser = await getAdminUser();
let created = 0; let created = 0;
const errors: { product: string; error: string }[] = []; const errors: { product: string; error: string }[] = [];
const validProducts: Array<{ name: string; description: string; price: number; type: string; active: boolean; image_url?: string }> = [];
const skipped: { product: { name: string; description: string; price: number; type: string; active: boolean; image_url?: string }; error: string }[] = [];
for (const p of productsToImport) { for (const p of productsToImport) {
const priceCents = Math.round(Number(p.price) * 100); const priceCents = Math.round(Number(p.price) * 100);
if (!Number.isFinite(priceCents) || priceCents < 0) { if (!Number.isFinite(priceCents) || priceCents < 0) {
errors.push({ product: p.name, error: "Invalid price" }); skipped.push({ product: p, error: "Invalid price" });
continue; } else {
validProducts.push(p);
} }
try { }
await withBrand(brandId, (db) =>
const settled = await Promise.allSettled(
validProducts.map((p) =>
withBrand(brandId, (db) =>
db.insert(products).values({ db.insert(products).values({
brandId: brandId, brandId: brandId,
name: p.name, name: p.name,
description: p.description ?? null, description: p.description ?? null,
priceCents, priceCents: Math.round(Number(p.price) * 100),
active: p.active, active: p.active,
}) }),
); ).then(
created++; () => p,
} catch (err) { (err) => ({ p, err }),
),
),
);
for (const entry of settled) {
if (entry.status === "rejected") {
const err = (entry as PromiseRejectedResult).reason;
errors.push({ errors.push({
product: p.name, product: "<unknown>",
error: err instanceof Error ? err.message : String(err), error: err instanceof Error ? err.message : String(err),
}); });
continue;
} }
const value = entry.value;
if ("err" in value) {
errors.push({
product: value.p.name,
error: value.err instanceof Error ? value.err.message : String(value.err),
});
} else {
created++;
}
}
for (const s of skipped) {
errors.push({ product: s.product.name, error: s.error });
} }
return { success: true, created, updated: 0, errors }; return { success: true, created, updated: 0, errors };
+94 -73
View File
@@ -101,82 +101,103 @@ await getSession(); const adminUser = await getAdminUser();
since since
); );
for (const payment of paymentsData.payments ?? []) { interface SquarePayment {
id: string;
order_id?: string;
status?: string;
amount_money?: { amount?: string; currency?: string };
customer_id?: string;
receipt_number?: string;
}
const processablePayments = ((paymentsData.payments ?? []) as SquarePayment[]).filter(
(p) => Boolean(p.order_id) && completedStatuses.has(p.status ?? ""),
);
const orderResults = await Promise.allSettled(
processablePayments.map((p) =>
fetchSquareOrder(settings.square_access_token!, p.order_id!).then(
(data) => ({ payment: p, orderData: data, err: undefined as unknown }),
(err: unknown) => ({ payment: p, orderData: undefined, err }),
),
),
);
for (const result of orderResults) {
if (result.status === "rejected") {
errors.push(`Square order fetch: ${String(result.reason)}`);
continue;
}
const { payment, orderData, err } = result.value;
if (err || !orderData) {
errors.push(`Payment ${payment.id}: ${String(err ?? "no order data")}`);
continue;
}
const order = orderData.order;
// Build line items from Square order
const lineItems = (order.line_items ?? []).map((li: {
name: string;
quantity: string;
base_price_money: { amount: string; currency: string };
}) => ({
name: li.name,
quantity: parseInt(li.quantity, 10),
price: Number(li.base_price_money?.amount ?? 0) / 100,
}));
// Compute total
const total = Number(order.total_money?.amount ?? payment.amount_money?.amount ?? 0) / 100;
// Get customer info from payment
const customerName = payment.customer_id ?? "Square Customer";
const customerEmail = payment.receipt_number ?? "";
// Use idempotency key to avoid duplicates
const idempotencyKey = `square_${payment.id}`;
// Call SECURITY DEFINER RPC create_order_with_items
let createOk = false;
let errText = "";
try { try {
// Skip if no order_id const rpcRes = await pool.query(
if (!payment.order_id) continue; `SELECT * FROM create_order_with_items(
// Skip if already completed/canceled $1, $2, $3, $4, $5, $6::jsonb, $7, $8, $9, $10
if (!completedStatuses.has(payment.status ?? "")) continue; )`,
[
const orderData = await fetchSquareOrder(settings.square_access_token, payment.order_id); idempotencyKey,
const order = orderData.order; customerName,
customerEmail,
// Build line items from Square order "",
const lineItems = (order.line_items ?? []).map((li: { null, // Square orders don't have RC stop_id
name: string; JSON.stringify(
quantity: string; lineItems.map((li: { name: string; quantity: number; price: number }) => ({
base_price_money: { amount: string; currency: string }; id: null, // product lookup not available in this flow
}) => ({ quantity: li.quantity,
name: li.name, fulfillment: "shipping",
quantity: parseInt(li.quantity, 10), }))
price: Number(li.base_price_money?.amount ?? 0) / 100, ),
})); total,
"square",
// Compute total "paid",
const total = Number(order.total_money?.amount ?? payment.amount_money?.amount ?? 0) / 100; payment.id,
]
// Get customer info from payment );
const customerName = payment.customer_id ?? "Square Customer"; createOk = rpcRes.rows.length > 0;
const customerEmail = payment.receipt_number ?? ""; } catch (e: unknown) {
const msg = e instanceof Error ? e.message : String(e);
// Use idempotency key to avoid duplicates // 409 / unique violation = already exists (idempotent)
const idempotencyKey = `square_${payment.id}`; if (/duplicate key|unique constraint|already exists/i.test(msg)) {
createOk = true;
// Call SECURITY DEFINER RPC create_order_with_items
let createOk = false;
let errText = "";
try {
const rpcRes = await pool.query(
`SELECT * FROM create_order_with_items(
$1, $2, $3, $4, $5, $6::jsonb, $7, $8, $9, $10
)`,
[
idempotencyKey,
customerName,
customerEmail,
"",
null, // Square orders don't have RC stop_id
JSON.stringify(
lineItems.map((li: { name: string; quantity: number; price: number }) => ({
id: null, // product lookup not available in this flow
quantity: li.quantity,
fulfillment: "shipping",
}))
),
total,
"square",
"paid",
payment.id,
]
);
createOk = rpcRes.rows.length > 0;
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : String(e);
// 409 / unique violation = already exists (idempotent)
if (/duplicate key|unique constraint|already exists/i.test(msg)) {
createOk = true;
} else {
errText = msg.slice(0, 100);
}
}
if (createOk) {
synced++;
} else { } else {
errors.push(`Payment ${payment.id}: ${errText}`); errText = msg.slice(0, 100);
} }
} catch (err) { }
errors.push(`Payment ${payment.id}: ${String(err)}`);
if (createOk) {
synced++;
} else {
errors.push(`Payment ${payment.id}: ${errText}`);
} }
} }
} catch (err) { } catch (err) {
+28 -24
View File
@@ -273,32 +273,36 @@ export default function ProductEditForm({ product, brands }: ProductEditFormProp
</AdminInput> </AdminInput>
<div> <div>
<label className="mb-2 block text-sm font-medium text-[var(--admin-text-primary)]" htmlFor="fld-status">Status</label><label className="mb-2 block text-sm font-medium text-[var(--admin-text-primary)]">Status</label> <label className="mb-2 block text-sm font-medium text-[var(--admin-text-primary)]">
<button type="button" <span className="block mb-2">Status</span>
onClick={() => setActive((v) => !v)} <button type="button"
className={`w-full rounded-xl px-4 py-3 text-sm font-medium transition-colors ${ onClick={() => setActive((v) => !v)}
active className={`w-full rounded-xl px-4 py-3 text-sm font-medium transition-colors ${
? "bg-[var(--admin-primary-soft)] text-[var(--admin-primary)] ring-1 ring-[var(--admin-primary)]" active
: "bg-[var(--admin-bg)] text-[var(--admin-text-muted)] hover:bg-[var(--admin-bg-subtle)]" ? "bg-[var(--admin-primary-soft)] text-[var(--admin-primary)] ring-1 ring-[var(--admin-primary)]"
}`} : "bg-[var(--admin-bg)] text-[var(--admin-text-muted)] hover:bg-[var(--admin-bg-subtle)]"
> }`}
{active ? "Active" : "Inactive"} >
</button> {active ? "Active" : "Inactive"}
</button>
</label>
</div> </div>
<div> <div>
<label className="mb-2 block text-sm font-medium text-[var(--admin-text-primary)]" htmlFor="fld-taxable">Taxable</label><label className="mb-2 block text-sm font-medium text-[var(--admin-text-primary)]">Taxable</label> <label className="mb-2 block text-sm font-medium text-[var(--admin-text-primary)]">
<button type="button" <span className="block mb-2">Taxable</span>
onClick={() => setIs_taxable((v) => !v)} <button type="button"
className={`w-full rounded-xl px-4 py-3 text-sm font-medium transition-colors flex items-center gap-3 ${ onClick={() => setIs_taxable((v) => !v)}
is_taxable className={`w-full rounded-xl px-4 py-3 text-sm font-medium transition-colors flex items-center gap-3 ${
? "bg-[var(--admin-primary-soft)] text-[var(--admin-primary)] ring-1 ring-[var(--admin-primary)]" is_taxable
: "bg-[var(--admin-accent-soft)] text-[var(--admin-accent)] ring-1 ring-[var(--admin-accent)]" ? "bg-[var(--admin-primary-soft)] text-[var(--admin-primary)] ring-1 ring-[var(--admin-primary)]"
}`} : "bg-[var(--admin-accent-soft)] text-[var(--admin-accent)] ring-1 ring-[var(--admin-accent)]"
> }`}
<span className="text-lg">{is_taxable ? "✓" : "✗"}</span> >
<span>{is_taxable ? "Taxable — tax applied at checkout for nexus shipments" : "Non-taxable — always exempt from sales tax"}</span> <span className="text-lg" aria-hidden="true">{is_taxable ? "✓" : "✗"}</span>
</button> <span>{is_taxable ? "Taxable — tax applied at checkout for nexus shipments" : "Non-taxable — always exempt from sales tax"}</span>
</button>
</label>
<p className="mt-1.5 text-xs text-[var(--admin-text-muted)]">Tax is calculated at checkout for shipping orders in your brand&apos;s nexus states. Non-taxable items (e.g. cooler boxes, apparel) are always exempt.</p> <p className="mt-1.5 text-xs text-[var(--admin-text-muted)]">Tax is calculated at checkout for shipping orders in your brand&apos;s nexus states. Non-taxable items (e.g. cooler boxes, apparel) are always exempt.</p>
</div> </div>
@@ -317,7 +321,7 @@ export default function ProductEditForm({ product, brands }: ProductEditFormProp
</AdminInput> </AdminInput>
<div> <div>
<label className="mb-1 block text-sm font-medium text-[var(--admin-text-primary)]" htmlFor="fld-product-image">Product Image</label><label className="mb-1 block text-sm font-medium text-[var(--admin-text-primary)]">Product Image</label> <span className="mb-1 block text-sm font-medium text-[var(--admin-text-primary)]">Product Image</span>
<p className="text-xs text-[var(--admin-text-muted)] mb-2">JPG, PNG, WebP · 1200px max width · max 5MB (ideally under 2MB)</p> <p className="text-xs text-[var(--admin-text-muted)] mb-2">JPG, PNG, WebP · 1200px max width · max 5MB (ideally under 2MB)</p>
<label <label
+3 -1
View File
@@ -163,6 +163,7 @@ export default function ScheduleImportModal({ brandId, onClose, onComplete }: Pr
{/* Drop zone */} {/* Drop zone */}
<label <label
htmlFor="schedule-file-upload"
onDragOver={(e) => { e.preventDefault(); setDragOver(true); }} onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
onDragLeave={() => setDragOver(false)} onDragLeave={() => setDragOver(false)}
onDrop={handleDrop} onDrop={handleDrop}
@@ -186,7 +187,8 @@ export default function ScheduleImportModal({ brandId, onClose, onComplete }: Pr
</p> </p>
</label> </label>
<input aria-label="File upload" <input id="schedule-file-upload"
aria-label="File upload"
ref={fileInputRef} ref={fileInputRef}
type="file" type="file"
accept=".csv,.txt,.json" accept=".csv,.txt,.json"
+15 -12
View File
@@ -332,18 +332,21 @@ export default function StopEditForm({ stop, brands, onSaved }: StopEditFormProp
</AdminInput> </AdminInput>
<div> <div>
<label className="mb-2 block text-sm font-medium text-stone-700" htmlFor="fld-status">Status</label><label className="mb-2 block text-sm font-medium text-stone-700">Status</label> <label className="mb-2 block text-sm font-medium text-stone-700">
<button <span className="block mb-2">Status</span>
type="button" <button
onClick={() => setActive((v) => !v)} type="button"
className={`w-full rounded-xl px-4 py-3 text-sm font-medium transition-colors ${ onClick={() => setActive((v) => !v)}
active className={`w-full rounded-xl px-4 py-3 text-sm font-medium transition-colors ${
? "bg-green-100 text-green-700 border border-green-200" active
: "bg-stone-100 text-stone-500 border border-stone-200 hover:bg-stone-50" ? "bg-green-100 text-green-700 border border-green-200"
}`} : "bg-stone-100 text-stone-500 border border-stone-200 hover:bg-stone-50"
> }`}
{active ? "✓ Active — visible on storefront" : "○ Inactive — hidden from storefront"} >
</button> <span aria-hidden="true">{active ? "✓ " : "○ "}</span>
{active ? "Active — visible on storefront" : "Inactive — hidden from storefront"}
</button>
</label>
</div> </div>
<AdminButton <AdminButton
@@ -80,11 +80,11 @@ export default function WaterLogEntryEditForm({ entry, backHref = "/admin/water-
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1" htmlFor="fld-headgate">Headgate</label><label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">Headgate</label> <span className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">Headgate</span>
<p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">{entry.headgate_name}</p> <p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">{entry.headgate_name}</p>
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1" htmlFor="fld-user">User</label><label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">User</label> <span className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">User</span>
<p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">{entry.user_name}</p> <p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">{entry.user_name}</p>
</div> </div>
</div> </div>
@@ -106,11 +106,11 @@ export default function WaterLogEntryEditForm({ entry, backHref = "/admin/water-
/> />
</AdminInput> </AdminInput>
<div> <div>
<label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1" htmlFor="fld-submitted-via">Submitted Via</label><label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">Submitted Via</label> <span className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">Submitted Via</span>
<p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">{entry.submitted_via}</p> <p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">{entry.submitted_via}</p>
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1" htmlFor="fld-logged">Logged</label><label className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">Logged</label> <span className="block text-sm font-medium text-[var(--admin-text-muted)] mb-1">Logged</span>
<p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]"> <p className="rounded-lg border border-[var(--admin-border)] bg-[var(--admin-bg-subtle)] px-3 py-2 text-sm text-[var(--admin-text-secondary)]">
{new Date(entry.logged_at).toLocaleDateString("en-US", { {new Date(entry.logged_at).toLocaleDateString("en-US", {
month: "short", month: "short",