diff --git a/TODO.md b/TODO.md index 63cb0b0..c215d72 100644 --- a/TODO.md +++ b/TODO.md @@ -3,6 +3,7 @@ ## High Priority ### Implement Lease Creation for Landlord Dashboard + - **Add "Create New Lease" button** to the landlord dashboard - **Lease creation form** with basic price information: - Rent amount @@ -18,6 +19,7 @@ - Update tenant field when invitation is accepted ### Bug Fixes + - **Fix TypeScript import errors** in receipt API routes - **Add missing `getTransactionByStripeId` function** to paymentService.ts - **Fix type annotations** for API route handlers @@ -26,6 +28,7 @@ ## Technical Implementation Details ### Lease Creation Flow + 1. **Landlord Dashboard Enhancement** - Add "Create New Lease" button component - Create lease creation modal/page @@ -42,11 +45,13 @@ - PUT `/api/leases/:id/accept` - Tenant accepts invitation ### Payment Service Fixes + - Implement `getTransactionByStripeId(stripeId: string)` function - Fix import paths in API routes - Add proper TypeScript types for Fresh handlers ## Development Tasks + - [ ] Create lease creation UI components - [ ] Implement lease creation API endpoint - [ ] Add tenant invitation system @@ -56,6 +61,7 @@ - [ ] Update landlord dashboard with new lease button ## Testing + - [ ] Unit tests for lease creation - [ ] Integration tests for tenant invitation flow - [ ] E2E tests for complete lease lifecycle diff --git a/routes/api/receipt/[id].ts b/routes/api/receipt/[id].ts index bbd009c..d1d1951 100644 --- a/routes/api/receipt/[id].ts +++ b/routes/api/receipt/[id].ts @@ -2,7 +2,7 @@ import { define } from "../../../utils.ts"; import { getTransactionByStripeId } from "../../../services/paymentService.ts"; export const handler = define.handlers({ - async GET(ctx: any) { + async GET(ctx) { const stripeId = ctx.params.id; if (!stripeId) { return new Response("Not Found", { status: 404 }); diff --git a/routes/api/update/lease_invite.ts b/routes/api/update/lease_invite.ts index 93bf3b6..6b7c3e3 100644 --- a/routes/api/update/lease_invite.ts +++ b/routes/api/update/lease_invite.ts @@ -28,7 +28,8 @@ export const handler = define.handlers({ } } - const lease_id = (data.lease_id as string) || (data["lease_id"] as string); + const lease_id = (data.lease_id as string) || + (data["lease_id"] as string); if (!lease_id) { return new Response(JSON.stringify({ error: "lease_id is required" }), { status: 400, @@ -46,10 +47,13 @@ export const handler = define.handlers({ const property_id = lease.property_id as string | undefined; if (!property_id) { - return new Response(JSON.stringify({ error: "lease has no property_id" }), { - status: 400, - headers: { "Content-Type": "application/json" }, - }); + return new Response( + JSON.stringify({ error: "lease has no property_id" }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); } const invite = await createTenantInvite(user_id as string, property_id); diff --git a/routes/landlord/dashboard/index.tsx b/routes/landlord/dashboard/index.tsx index 9bfc72d..c5186cd 100644 --- a/routes/landlord/dashboard/index.tsx +++ b/routes/landlord/dashboard/index.tsx @@ -91,7 +91,7 @@ export const handler = define.handlers({ export default define.page( function LandlordDashboard({ data }: LandlordDashboardProps) { - const { stateData, dashboardData, error } = data; + const { stateData: _stateData, dashboardData, error } = data; if (error) { return ( @@ -452,10 +452,16 @@ export default define.page(

- -
@@ -536,4 +542,3 @@ export default define.page( ); }, ); - diff --git a/routes/landlord/payments/index.tsx b/routes/landlord/payments/index.tsx index 6f0dd53..b973a2f 100644 --- a/routes/landlord/payments/index.tsx +++ b/routes/landlord/payments/index.tsx @@ -282,4 +282,3 @@ export default define.page(function LandlordPayments({ data }) { ); }); - diff --git a/routes/landlord/portfolio/index.tsx b/routes/landlord/portfolio/index.tsx index f391d1d..46f7260 100644 --- a/routes/landlord/portfolio/index.tsx +++ b/routes/landlord/portfolio/index.tsx @@ -171,4 +171,3 @@ export default define.page(function LandlordPortfolio({ data }) { ); }); - diff --git a/routes/landlord/properties/cleanup.tsx b/routes/landlord/properties/cleanup.tsx index a8da508..01f6c10 100644 --- a/routes/landlord/properties/cleanup.tsx +++ b/routes/landlord/properties/cleanup.tsx @@ -536,4 +536,3 @@ export default define.page(function LandlordPropertyCleanup({ data }) { ); }); - diff --git a/services/leaseService.ts b/services/leaseService.ts index 394da6f..2ddd6ed 100644 --- a/services/leaseService.ts +++ b/services/leaseService.ts @@ -247,7 +247,3 @@ export async function getLeaseStatsByOwner(owner_id: string) { without_tenant: allLeases.filter((l) => l.tenant_id === null).length, }; } - - - - diff --git a/services/paymentService.ts b/services/paymentService.ts index 42667d4..c239937 100644 --- a/services/paymentService.ts +++ b/services/paymentService.ts @@ -391,7 +391,7 @@ export async function cancelAutoPayment( export async function getTransactionByStripeId(stripeId: string) { if (isE2EEnabled()) { const transactions = getE2EPaymentTransactionsByOwner(""); // Get all transactions and filter - return transactions.find(tx => tx.stripe_payment_id === stripeId) || null; + return transactions.find((tx) => tx.stripe_payment_id === stripeId) || null; } const result = await db.select() .from(payment_transactions) diff --git a/tests/e2e/landlord-dashboard.spec.ts b/tests/e2e/landlord-dashboard.spec.ts index 7825176..267f401 100644 --- a/tests/e2e/landlord-dashboard.spec.ts +++ b/tests/e2e/landlord-dashboard.spec.ts @@ -75,5 +75,3 @@ test("landlord can update maintenance status", async ({ page }: { page: Page }) await expect(page).toHaveURL(/\/landlord\/maintenance/); await expect(statusSelect).toHaveValue("in_progress"); }); - - diff --git a/tests/e2e/lease-creation.spec.ts b/tests/e2e/lease-creation.spec.ts new file mode 100644 index 0000000..77c6954 --- /dev/null +++ b/tests/e2e/lease-creation.spec.ts @@ -0,0 +1,41 @@ +import { expect, Page, test } from "@playwright/test"; +import { applyDeterministicPage, e2eHeaders, useE2EUser } from "./helpers.ts"; + +test("lease acceptance workflow (E2E fixtures)", async ({ page }: { page: Page }) => { + await applyDeterministicPage(page); + + // Landlord side: navigate to leased homes + await useE2EUser(page, "landlord"); + await page.goto("/landlord/dashboard"); + const link = page.getByRole("link", { name: /create new lease/i }); + if (await link.count()) { + await link.click(); + } else { + await page.goto("/leased-home"); + } + await expect(page.getByRole("heading", { name: /leased homes/i })) + .toBeVisible(); + + // Create an invite for an existing e2e lease (server-side fixture) + const inviteResp = await page.request.post("/api/update/lease_invite", { + headers: { "Content-Type": "application/json", ...e2eHeaders("landlord") }, + data: { lease_id: "e2e-lease-1" }, + }); + expect(inviteResp.ok()).toBeTruthy(); + + // Tenant accepts the lease + await useE2EUser(page, "tenant"); + const acceptResp = await page.request.post("/api/update/lease_accept", { + headers: { "Content-Type": "application/json", ...e2eHeaders("tenant") }, + data: { lease_id: "e2e-lease-1" }, + }); + expect(acceptResp.ok()).toBeTruthy(); + + const body = await acceptResp.json(); + expect(body?.success).toBeTruthy(); + // The response may include the updated lease; ensure status is active if present + const lease = body?.lease; + if (lease) { + expect(lease.lease_status === "active").toBeTruthy(); + } +});