import { define } from "../../../utils.ts"; import { page } from "fresh"; import type { SessionCookie } from "../../auth/callback.ts"; import { createTenantApplication, hasUserAppliedForProperty, } from "../../../services/tenantApplicationService.ts"; import { markRoleOnboarded, startGuestOnboarding, } from "../../../services/onboardingService.ts"; import { getPropertyById } from "../../../services/propertyService.ts"; import { getPhotosByProperty } from "../../../services/propertyMediaService.ts"; import { getTenantInviteById, removeTenantInviteEmail, } from "../../../services/tenantInviteService.ts"; import { getUserById } from "../../../services/userService.ts"; import LoginPurposeButton from "../../../islands/LoginPurposeButton.tsx"; import type { Property, PropertyMedia, TenantInvite, User, } from "../../../db/interfaces.ts"; type InvitePreviewData = { invite: TenantInvite | null; property: Property | null; photos: PropertyMedia[]; user: User | null; inviteeEmail: string; status?: string; error?: string; }; function buildPropertyLabel(property: Property | null) { return property?.title?.trim() || property?.address_1?.trim() || "Rental Property"; } function buildPropertyAddress(property: Property | null) { if (!property) return ""; return [ property.address_1, property.address_2, property.city, property.state, property.zip, ].filter(Boolean).join(", "); } function formatCurrency(amount?: number | null) { if (amount === null || amount === undefined) return "N/A"; return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0, }).format(amount); } function formatNumber(value?: number | null) { if (value === null || value === undefined) return "N/A"; return new Intl.NumberFormat("en-US").format(value); } async function buildInvitePreview( ctx: { params: Record; req: Request; state: { data: SessionCookie }; }, options: { error?: string } = {}, ) { const inviteId = decodeURIComponent(ctx.params.invite_id || ""); const url = new URL(ctx.req.url); const status = url.searchParams.get("status") || undefined; const stateData = ctx.state.data as SessionCookie; const userId = stateData?.user_id; const user = userId ? await getUserById(userId) : null; const invite = inviteId ? await getTenantInviteById(inviteId) : null; const property = invite?.property_id ? await getPropertyById(invite.property_id) : null; const photos = property?.id ? await getPhotosByProperty(property.id) : []; const inviteeEmail = user?.email?.trim() || ""; return page({ invite, property, photos, user, inviteeEmail, status, error: options.error, }); } export const handler = define.handlers({ async GET(ctx) { return await buildInvitePreview(ctx); }, async POST(ctx) { const inviteId = decodeURIComponent(ctx.params.invite_id || ""); const formData = await ctx.req.formData(); const intent = (formData.get("intent") || "").toString(); const stateData = ctx.state.data as SessionCookie; const userId = stateData?.user_id; const user = userId ? await getUserById(userId) : null; const invite = inviteId ? await getTenantInviteById(inviteId) : null; if (!invite) { return await buildInvitePreview(ctx, { error: "We could not find that invite.", }); } if (intent === "start") { if (!userId) { return new Response("", { status: 302, headers: { Location: "/auth/login" }, }); } if (!invite.property_id) { return await buildInvitePreview(ctx, { error: "This invite is missing the property details.", }); } await startGuestOnboarding(userId, "tenant"); const alreadyApplied = await hasUserAppliedForProperty( invite.property_id, userId, ); if (!alreadyApplied) { const payload = JSON.stringify({ source: "invite", invite_id: invite.id, requested_at: Date.now(), }); await createTenantApplication(invite.property_id, userId, payload); } await markRoleOnboarded(userId, "tenant"); return new Response("", { status: 302, headers: { Location: "/tenant/dashboard" }, }); } if (intent === "decline") { const inviteeEmail = (user?.email || formData.get("invitee_email") || "") .toString() .trim(); if (!inviteeEmail) { return await buildInvitePreview(ctx, { error: "Enter the email tied to the invite to decline it.", }); } const removal = await removeTenantInviteEmail(invite.id, inviteeEmail); if (!removal.removed) { return await buildInvitePreview(ctx, { error: "We could not match that email to this invite.", }); } return new Response("", { status: 302, headers: { Location: `/tenant/invite/${ encodeURIComponent(invite.id) }?status=declined`, }, }); } return new Response("", { status: 302, headers: { Location: `/tenant/invite/${encodeURIComponent(inviteId)}` }, }); }, }); export default define.page(function TenantInvitePreview({ data }) { const { invite, property, photos, user, inviteeEmail, status, error, } = data as InvitePreviewData; if (!invite || !property) { return (

Invite Preview

{error || "We could not load this invite."}

Ask your landlord to resend the invite or verify the link.

Back to Home
); } const propertyLabel = buildPropertyLabel(property); const propertyAddress = buildPropertyAddress(property); const photoUrls = photos .map((photo) => photo.file_url) .filter((url): url is string => Boolean(url && url.trim().length > 0)); const fallbackPhoto = property.image_url?.trim() || ""; const displayedPhotos = photoUrls.length > 0 ? photoUrls : fallbackPhoto ? [fallbackPhoto] : []; const availableOn = invite.available_on ? new Date(invite.available_on).toLocaleDateString() : null; const cancelHref = user?.id ? "/tenant/dashboard" : "/"; const isLoggedIn = Boolean(user?.id); return (

Tenant Invitation

{propertyLabel}

{propertyAddress && (

{propertyAddress}

)} {inviteeEmail && (

Invite sent to {inviteeEmail}

)} {status === "declined" && (
Invite declined. If this was a mistake, reach out to your landlord for a fresh link.
)} {error && (
{error}
)}

Property Photos

{displayedPhotos.length} photo {displayedPhotos.length === 1 ? "" : "s"}
{displayedPhotos.length > 0 ? (
{`${propertyLabel} {displayedPhotos.slice(1, 3).map((photo, index) => ( {`${propertyLabel} ))}
) : (
No photos yet. Your landlord can add them later.
)}

Property Overview

{property.description || "No description has been added yet."}

Bedrooms

{formatNumber(property.bedrooms)}

Bathrooms

{formatNumber(property.bathrooms)}

Square Feet

{formatNumber(property.square_footage)}

Property Type

{property.property_type || "N/A"}

{property.amenities && (

Amenities

{property.amenities}

)}

Ready to proceed?

Start your lease, decline the invite, or come back later.

{isLoggedIn ? (
) : ( Sign In to Start Lease )}
{!inviteeEmail && ( )}
Cancel
{!isLoggedIn && (

Sign in to save your decision and view the full tenant portal.

)}
); });