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();
+ }
+});