diff --git a/e2e/health.spec.ts b/e2e/health.spec.ts index ddab198..7f7a6b5 100644 --- a/e2e/health.spec.ts +++ b/e2e/health.spec.ts @@ -1,5 +1,18 @@ import { expect, test } from "@playwright/test"; +/** + * These checks call the API from Node, not from the browser, so Chromium's + * `--host-resolver-rules` mapping for the `.localhost` host does not + * apply and Node's resolver would fail on platforms that do not resolve + * *.localhost (Windows). The health endpoints are not tenant-specific, so the + * local runs talk to loopback directly; a deployed target is used unchanged. + */ +function healthUrl(path: string) { + const base = process.env.E2E_BASE_URL ?? "http://acme-realty.localhost:3000"; + const resolved = base.includes(".localhost") ? "http://127.0.0.1:3000" : base; + return `${resolved.replace(/\/$/, "")}${path}`; +} + /** * Deployment health gates. * @@ -13,7 +26,7 @@ import { expect, test } from "@playwright/test"; */ test("database has every migration the deployed code expects", async ({ request }) => { - const response = await request.get("/api/readyz"); + const response = await request.get(healthUrl("/api/readyz")); expect( response.status(), @@ -34,12 +47,12 @@ test("database has every migration the deployed code expects", async ({ request }); test("liveness endpoint responds", async ({ request }) => { - const response = await request.get("/api/health"); + const response = await request.get(healthUrl("/api/health")); expect(response.status()).toBe(200); }); test("readyz never leaks credentials", async ({ request }) => { - const response = await request.get("/api/readyz"); + const response = await request.get(healthUrl("/api/readyz")); const raw = await response.text(); // Host names are fine; secrets are not. diff --git a/src/app/(admin)/admin/contracts/page.tsx b/src/app/(admin)/admin/contracts/page.tsx index c16211d..cc41e25 100644 --- a/src/app/(admin)/admin/contracts/page.tsx +++ b/src/app/(admin)/admin/contracts/page.tsx @@ -109,7 +109,7 @@ function UploadContractForm({ transactions }: { transactions: TransactionWithout name="file" accept=".pdf,application/pdf" required - className="w-full rounded-[var(--radius-md)] border border-[var(--line)] bg-white px-3 py-2 text-sm text-[var(--ink-800)] file:mr-3 file:rounded file:border-0 file:bg-[var(--sand-100)] file:px-2 file:py-0.5 file:text-xs file:font-medium" + className="w-full rounded-[var(--radius-md)] border border-[var(--line)] bg-white min-h-11 px-3 py-2 text-base sm:text-sm text-[var(--ink-800)] file:mr-3 file:rounded file:border-0 file:bg-[var(--sand-100)] file:px-2 file:py-0.5 file:text-xs file:font-medium" />
diff --git a/src/app/(marketing)/properties/page.tsx b/src/app/(marketing)/properties/page.tsx index 207a35b..5a0e473 100644 --- a/src/app/(marketing)/properties/page.tsx +++ b/src/app/(marketing)/properties/page.tsx @@ -62,11 +62,23 @@ export default async function PropertiesPage({
-
+ {/* On a phone these six fields pushed every listing below the fold, + so they collapse behind a toggle and buyers see inventory first. + Checkbox + peer is used rather than
or JS so the panel + works server-rendered, and it is force-open from `md` up. The + checkbox has no name, so it is never submitted with the form. */} + +
-
+ + Tap to {activeFilters.length > 0 ? "edit" : "filter"} + + +
Reserved -
+ {/* Sibling of the peer checkbox so the actions collapse with the + fields they apply to, instead of floating above the listings. */} +
+ + + + +
-
- - - - -
diff --git a/src/app/(marketing)/testimonials/page.tsx b/src/app/(marketing)/testimonials/page.tsx index e951de3..a60d317 100644 --- a/src/app/(marketing)/testimonials/page.tsx +++ b/src/app/(marketing)/testimonials/page.tsx @@ -59,8 +59,8 @@ export default async function TestimonialsPage({ ))} - - + + diff --git a/src/components/admin/add-buyer-form.tsx b/src/components/admin/add-buyer-form.tsx index 6582ad4..2fec3e0 100644 --- a/src/components/admin/add-buyer-form.tsx +++ b/src/components/admin/add-buyer-form.tsx @@ -13,7 +13,7 @@ import type { ProvisionUserResult } from "@/modules/provisioning/provision-user" const INITIAL: ProvisionUserResult | null = null; const inputCls = - "admin-focus w-full rounded-[var(--radius-md)] border border-[var(--line)] bg-white px-3 py-2 text-sm text-[var(--ink-900)] placeholder:text-[var(--ink-400)]"; + "admin-focus w-full rounded-[var(--radius-md)] border border-[var(--line)] bg-white min-h-11 px-3 py-2 text-base sm:text-sm text-[var(--ink-900)] placeholder:text-[var(--ink-400)]"; const labelCls = "block text-xs font-medium uppercase tracking-[0.12em] text-[var(--ink-500)]"; diff --git a/src/components/admin/announcement-composer.tsx b/src/components/admin/announcement-composer.tsx index 03d3dfc..e10eb68 100644 --- a/src/components/admin/announcement-composer.tsx +++ b/src/components/admin/announcement-composer.tsx @@ -9,7 +9,7 @@ import { Select } from "@/components/ui/select"; const INITIAL: AnnouncementFormState = { ok: false, error: null }; const inputClass = - "admin-focus w-full rounded-[var(--radius-md)] border border-[var(--line)] bg-white px-3 py-2 text-sm text-[var(--ink-900)] placeholder:text-[var(--ink-400)]"; + "admin-focus w-full rounded-[var(--radius-md)] border border-[var(--line)] bg-white min-h-11 px-3 py-2 text-base sm:text-sm text-[var(--ink-900)] placeholder:text-[var(--ink-400)]"; function PostButton() { const { pending } = useFormStatus(); diff --git a/src/components/admin/branding-management.tsx b/src/components/admin/branding-management.tsx index 0fdf9c2..ced51cc 100644 --- a/src/components/admin/branding-management.tsx +++ b/src/components/admin/branding-management.tsx @@ -682,7 +682,7 @@ function ColorField({
onChange(event.target.value.toUpperCase())} /> - onChange(event.target.value.toUpperCase())} /> + onChange(event.target.value.toUpperCase())} />
); diff --git a/src/components/admin/deal-board-view.tsx b/src/components/admin/deal-board-view.tsx index a4362c0..f8a8fef 100644 --- a/src/components/admin/deal-board-view.tsx +++ b/src/components/admin/deal-board-view.tsx @@ -514,8 +514,11 @@ export function DealBoardView({ ) : null}
-
-
+ {/* The 960px floor is what keeps the stage columns side by side, but on + a phone it forced the whole board to scroll sideways inside a 335px + viewport. Below `md` the stages stack vertically instead. */} +
+
{visibleColumns.map((column) => ( {inspect && column.key === "OVERDUE" ? ( diff --git a/src/components/admin/front-desk-logbook.tsx b/src/components/admin/front-desk-logbook.tsx index 2380515..b7af98b 100644 --- a/src/components/admin/front-desk-logbook.tsx +++ b/src/components/admin/front-desk-logbook.tsx @@ -10,7 +10,7 @@ import { WhatsAppButton } from "@/components/shared/whatsapp-button"; import { Select } from "@/components/ui/select"; const inputClass = - "admin-focus w-full rounded-[var(--radius-md)] border border-[var(--line)] bg-white px-3 py-2 text-sm text-[var(--ink-900)] placeholder:text-[var(--ink-400)]"; + "admin-focus w-full rounded-[var(--radius-md)] border border-[var(--line)] bg-white min-h-11 px-3 py-2 text-base sm:text-sm text-[var(--ink-900)] placeholder:text-[var(--ink-400)]"; const buttonClass = "admin-focus inline-flex items-center justify-center gap-1.5 rounded-[var(--radius-md)] bg-[var(--brand-700)] px-3.5 py-2 text-sm font-semibold text-white transition-colors hover:bg-[var(--brand-800,#15803d)]"; @@ -45,7 +45,7 @@ export function FrontDeskLogbookView({ logbook }: { logbook: FrontDeskLogbook })
- +
@@ -105,7 +105,7 @@ export function FrontDeskLogbookView({ logbook }: { logbook: FrontDeskLogbook }) - + - + @@ -66,7 +66,7 @@ export function LeadQuickForm({ onSuccess }: { onSuccess: () => void }) { - +