166 lines
4.8 KiB
TypeScript
166 lines
4.8 KiB
TypeScript
// routes/landlord/tenants/invite.tsx
|
|
import { define } from "../../../utils.ts";
|
|
import { page } from "fresh";
|
|
import type { SessionCookie } from "../../auth/callback.ts";
|
|
import type { Property, TenantInvite } from "../../../db/interfaces.ts";
|
|
import { getPropertiesByOwner } from "../../../services/propertyService.ts";
|
|
import LandlordInviteTenant from "../../../islands/LandlordInviteTenant.tsx";
|
|
import { getTenantInviteById } from "../../../services/tenantInviteService.ts";
|
|
|
|
function parseInvitedEmails(value?: string | null) {
|
|
if (!value) return [];
|
|
try {
|
|
const parsed = JSON.parse(value);
|
|
if (Array.isArray(parsed)) {
|
|
return parsed.filter((item) => typeof item === "string");
|
|
}
|
|
} catch (_error) {
|
|
// Fall back to comma-separated parsing.
|
|
}
|
|
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
}
|
|
|
|
interface InviteTenantPageData {
|
|
stateData: SessionCookie;
|
|
properties: Property[];
|
|
invite: TenantInvite | null;
|
|
error?: string;
|
|
}
|
|
|
|
export const handler = define.handlers({
|
|
async GET(ctx) {
|
|
const stateData = ctx.state.data as SessionCookie;
|
|
const { user_id } = stateData;
|
|
|
|
if (!user_id) {
|
|
return new Response("", {
|
|
status: 302,
|
|
headers: { Location: "/auth/login" },
|
|
});
|
|
}
|
|
|
|
try {
|
|
const properties = await getPropertiesByOwner(user_id);
|
|
const url = new URL(ctx.req.url);
|
|
const inviteId = url.searchParams.get("invite_id");
|
|
|
|
const invite = inviteId ? await getTenantInviteById(inviteId) : null;
|
|
if (inviteId && (!invite || invite.landlord_id !== user_id)) {
|
|
return page({
|
|
stateData,
|
|
properties,
|
|
invite: null,
|
|
error: "Invite not found. Please start a new invite.",
|
|
});
|
|
}
|
|
|
|
return page({
|
|
stateData,
|
|
properties,
|
|
invite,
|
|
});
|
|
} catch (error) {
|
|
console.error("Error loading invite tenant page:", error);
|
|
return page({
|
|
stateData,
|
|
properties: [],
|
|
invite: null,
|
|
error: "Unable to load your properties. Please try again.",
|
|
});
|
|
}
|
|
},
|
|
});
|
|
|
|
export default define.page(function InviteTenantPage({ data }) {
|
|
const { stateData, properties, invite, error } = data as InviteTenantPageData;
|
|
const landlordName = stateData?.user_name || "Your landlord";
|
|
const inviteEmails = parseInvitedEmails(invite?.invited_emails);
|
|
|
|
return (
|
|
<div class="min-h-screen bg-gray-50">
|
|
<div class="bg-white shadow">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="flex flex-col gap-4 py-6 sm:flex-row sm:items-center sm:justify-between">
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-gray-900">
|
|
Invite a Tenant
|
|
</h1>
|
|
<p class="mt-1 text-sm text-gray-500">
|
|
Send a pre-filled invite email linked to the right rental.
|
|
</p>
|
|
</div>
|
|
<div class="flex space-x-3">
|
|
<a
|
|
href="/landlord/dashboard"
|
|
class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
|
>
|
|
Back to Dashboard
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
{error && (
|
|
<div class="mb-6 bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{properties.length === 0
|
|
? (
|
|
<div class="bg-white shadow rounded-lg p-6">
|
|
<h2 class="text-lg font-semibold text-gray-900">
|
|
Add a property first
|
|
</h2>
|
|
<p class="mt-2 text-sm text-gray-500">
|
|
You need at least one property to generate an invite link.
|
|
</p>
|
|
<a
|
|
href="/landlord/properties/add"
|
|
class="mt-4 inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
|
|
>
|
|
Add Property
|
|
</a>
|
|
</div>
|
|
)
|
|
: invite
|
|
? (
|
|
<LandlordInviteTenant
|
|
properties={properties}
|
|
landlordName={landlordName}
|
|
inviteId={invite.id}
|
|
invitePropertyId={invite.property_id ?? ""}
|
|
inviteAvailableOn={invite.available_on ?? null}
|
|
inviteEmails={inviteEmails}
|
|
/>
|
|
)
|
|
: (
|
|
<LandlordInviteTenant
|
|
properties={properties}
|
|
landlordName={landlordName}
|
|
inviteId=""
|
|
invitePropertyId=""
|
|
inviteAvailableOn={null}
|
|
inviteEmails={[]}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|