2026-02-14 06:10:15 +00:00

377 lines
14 KiB
TypeScript

// routes/landlord/onboarding/property-setup.tsx
import { define } from "../../../utils.ts";
import { page } from "fresh";
import type { SessionCookie } from "../../auth/callback.ts";
import { properties } from "../../../db/schema.ts";
import { db } from "../../../db/db.ts";
import { markRoleOnboarded } from "../../../services/onboardingService.ts";
export const handler = define.handlers({
GET(ctx) {
const stateData = ctx.state.data as SessionCookie;
return page({ stateData });
},
async POST(ctx) {
const stateData = ctx.state.data as SessionCookie;
const { user_id } = stateData;
const formData = await ctx.req.formData();
const now = Date.now();
const id = crypto.randomUUID();
const propertyPayload = {
id,
owner_id: user_id,
address_1: (formData.get("address_1") || "").toString(),
address_2: (formData.get("address_2") || "").toString(),
city: (formData.get("city") || "").toString(),
state: (formData.get("state") || "").toString(),
zip: (formData.get("zip") || "").toString(),
country: (formData.get("country") || "").toString(),
property_type: (formData.get("property_type") || "").toString(),
bedrooms: parseInt((formData.get("bedrooms") || "0").toString(), 10) || 0,
bathrooms: parseInt((formData.get("bathrooms") || "0").toString(), 10) ||
0,
monthly_rent:
parseFloat((formData.get("monthly_rent") || "0").toString()) || 0,
security_deposit:
parseFloat((formData.get("security_deposit") || "0").toString()) || 0,
lease_duration_months: parseInt(
(formData.get("lease_duration_months") || "12").toString(),
10,
) || 12,
amenities: (formData.get("amenities") || "").toString(),
description: (formData.get("description") || "").toString(),
rental_status: "draft",
created_on: now,
updated_on: now,
};
await db.insert(properties).values(propertyPayload);
// Mark onboarding complete (step 0 or null effectively means finished)
await markRoleOnboarded(user_id, "landlord");
return new Response("", {
status: 302,
headers: { Location: "/landlord/dashboard" },
});
},
});
export default define.page(function PropertySetup({ data }) {
const { stateData: _stateData, error } = data as {
stateData: SessionCookie;
error?: string;
};
return (
<div class="min-h-screen bg-gray-50 py-12 px-4 sm:px-6 lg:px-8 landlord-route">
<div class="max-w-3xl mx-auto">
<div class="text-center mb-8">
<h2 class="text-3xl font-extrabold text-gray-900">
Add Your First Rental Property
</h2>
<p class="mt-2 text-sm text-gray-600">
Enter details for at least one property to complete onboarding.
</p>
</div>
<div class="bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
{error && (
<div class="mb-6 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
{error}
</div>
)}
<form method="POST" class="space-y-6">
<div>
<h3 class="text-lg font-medium text-gray-900 mb-4">
Property Address
</h3>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div class="sm:col-span-2">
<label
for="address_1"
class="block text-sm font-medium text-gray-700"
>
Street Address *
</label>
<input
id="address_1"
name="address_1"
type="text"
required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
/>
</div>
<div class="sm:col-span-2">
<label
for="address_2"
class="block text-sm font-medium text-gray-700"
>
Apartment, suite, etc. (optional)
</label>
<input
id="address_2"
name="address_2"
type="text"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
/>
</div>
<div>
<label
for="city"
class="block text-sm font-medium text-gray-700"
>
City *
</label>
<input
id="city"
name="city"
type="text"
required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
/>
</div>
<div>
<label
for="state"
class="block text-sm font-medium text-gray-700"
>
State/Province *
</label>
<input
id="state"
name="state"
type="text"
required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
/>
</div>
<div>
<label
for="zip"
class="block text-sm font-medium text-gray-700"
>
ZIP/Postal Code *
</label>
<input
id="zip"
name="zip"
type="text"
required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
/>
</div>
<div>
<label
for="country"
class="block text-sm font-medium text-gray-700"
>
Country *
</label>
<select
id="country"
name="country"
required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<option value="US">United States</option>
<option value="CA">Canada</option>
<option value="UK">United Kingdom</option>
<option value="AU">Australia</option>
</select>
</div>
</div>
</div>
<div>
<h3 class="text-lg font-medium text-gray-900 mb-4">
Property Details
</h3>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-3">
<div>
<label
for="property_type"
class="block text-sm font-medium text-gray-700"
>
Property Type *
</label>
<select
id="property_type"
name="property_type"
required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<option value="">Select type</option>
<option value="apartment">Apartment</option>
<option value="house">House</option>
<option value="condo">Condo</option>
<option value="townhouse">Townhouse</option>
<option value="studio">Studio</option>
</select>
</div>
<div>
<label
for="bedrooms"
class="block text-sm font-medium text-gray-700"
>
Bedrooms *
</label>
<select
id="bedrooms"
name="bedrooms"
required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<option value="0">Studio</option>
<option value="1">1 Bedroom</option>
<option value="2">2 Bedrooms</option>
<option value="3">3 Bedrooms</option>
<option value="4">4 Bedrooms</option>
<option value="5">5+ Bedrooms</option>
</select>
</div>
<div>
<label
for="bathrooms"
class="block text-sm font-medium text-gray-700"
>
Bathrooms *
</label>
<select
id="bathrooms"
name="bathrooms"
required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<option value="1">1 Bathroom</option>
<option value="2">2 Bathrooms</option>
<option value="3">3 Bathrooms</option>
</select>
</div>
<div>
<label
for="monthly_rent"
class="block text-sm font-medium text-gray-700"
>
Monthly Rent ($) *
</label>
<input
id="monthly_rent"
name="monthly_rent"
type="number"
min="0"
step="0.01"
required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
/>
</div>
<div>
<label
for="security_deposit"
class="block text-sm font-medium text-gray-700"
>
Security Deposit ($) *
</label>
<input
id="security_deposit"
name="security_deposit"
type="number"
min="0"
step="0.01"
required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
/>
</div>
<div class="sm:col-span-3">
<label
for="lease_duration_months"
class="block text-sm font-medium text-gray-700"
>
Lease Duration (months) *
</label>
<select
id="lease_duration_months"
name="lease_duration_months"
required
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<option value="6">6 months</option>
<option value="12" selected>12 months</option>
<option value="18">18 months</option>
<option value="24">24 months</option>
</select>
</div>
</div>
</div>
<div>
<h3 class="text-lg font-medium text-gray-900 mb-4">
Additional Information
</h3>
<div class="space-y-4">
<div>
<label
for="amenities"
class="block text-sm font-medium text-gray-700"
>
Amenities (comma-separated)
</label>
<input
id="amenities"
name="amenities"
type="text"
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
/>
</div>
<div>
<label
for="description"
class="block text-sm font-medium text-gray-700"
>
Property Description
</label>
<textarea
id="description"
name="description"
rows={4}
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
</textarea>
</div>
</div>
</div>
<div class="flex justify-end pt-6">
<button
type="submit"
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Save Property & Finish
</button>
</div>
</form>
</div>
</div>
</div>
</div>
);
});