This commit is contained in:
Nathan Anderson 2026-01-29 11:27:15 -06:00
parent 0e81e1fb3e
commit a5886eb01e
11 changed files with 58 additions and 16 deletions

View File

@ -215,6 +215,7 @@ export interface TenantInvite {
id: string; id: string;
landlord_id?: string | null; landlord_id?: string | null;
property_id?: string | null; property_id?: string | null;
lease_id?: string | null;
available_on?: number | null; available_on?: number | null;
invited_emails?: string | null; invited_emails?: string | null;
created_on?: number | null; created_on?: number | null;

View File

@ -836,7 +836,7 @@ export class KvCacheStore {
case "roles": { case "roles": {
const roles = await sqlGetAllRoles(); const roles = await sqlGetAllRoles();
const role = roles.find((r: { id: string; description: string }) => r.id === id); const role = roles.find((r: { id: string; description: string | null }) => r.id === id);
if (!role) return null; if (!role) return null;
return { return {
id: role.id, id: role.id,
@ -847,8 +847,8 @@ export class KvCacheStore {
case "user_roles": { case "user_roles": {
const roles = await sqlGetUserRoles(id as string); const roles = await sqlGetUserRoles(id as string);
if (!roles || roles.length === 0) return null; if (!roles || roles.length === 0) return null;
return roles.map((r: { roleId: string }) => r.roleId).filter((r: { roleId: string }) => return roles.map((r: { roleId: string | null }) => r.roleId).filter((roleId: string | null) =>
r.roleId !== "" roleId !== ""
) as T; ) as T;
} }

View File

@ -81,7 +81,7 @@ export const handler = define.handlers({
stateData, stateData,
dashboardData: { dashboardData: {
...dashboardData, ...dashboardData,
pendingLeaseApprovals, pendingApprovals,
}, },
}); });
} catch (error) { } catch (error) {

View File

@ -25,7 +25,7 @@ export const handler = define.handlers({
} }
try { try {
const application = await getApplicationById(applicationId); const application = await getApplicationById(applicationId) as TenantApplication | null;
if (!application || application.applicant_id !== user_id) { if (!application || application.applicant_id !== user_id) {
return page<ApplicationDetailPageData>({ return page<ApplicationDetailPageData>({
@ -48,7 +48,7 @@ export const handler = define.handlers({
if (application.document_urls_json) { if (application.document_urls_json) {
try { try {
documentUrls = JSON.parse(application.document_urls_json as string); documentUrls = JSON.parse(application.document_urls_json as unknown as string);
} catch (e) { } catch (e) {
console.error("Failed to parse document URLs:", e); console.error("Failed to parse document URLs:", e);
} }

View File

@ -22,7 +22,7 @@ export const handler = define.handlers({
} }
try { try {
const applications = await getApplicationsByApplicant(user_id); const applications = await getApplicationsByApplicant(user_id) as TenantApplication[];
return page<ApplicationsPageData>({ return page<ApplicationsPageData>({
applications: applications || [], applications: applications || [],

View File

@ -72,7 +72,7 @@ function MessageBubble(
const attachments = Array.isArray(message.attachments_json) const attachments = Array.isArray(message.attachments_json)
? message.attachments_json ? message.attachments_json
: message.attachments_json : message.attachments_json
? JSON.parse(message.attachments_json as string) ? JSON.parse(message.attachments_json as unknown as string)
: []; : [];
const content = message.content_encrypted || message.content || ""; const content = message.content_encrypted || message.content || "";

View File

@ -220,7 +220,7 @@ export function addE2EMessage(
content, content,
content_encrypted: content, content_encrypted: content,
parent_message_id: parentMessageId, parent_message_id: parentMessageId,
attachments_json: attachmentsJson, attachments_json: attachmentsJson ? JSON.parse(attachmentsJson) as Record<string, unknown> : null,
is_read: false, is_read: false,
created_on: BASE_TIME, created_on: BASE_TIME,
}; };
@ -927,6 +927,7 @@ export function getE2ETenantApplicationsByLandlord(landlordId: string) {
application_notes: "Strong rental history.", application_notes: "Strong rental history.",
status: app.application_status || "pending", status: app.application_status || "pending",
created_at: app.submitted_on, created_at: app.submitted_on,
document_urls_json: app.document_urls_json ? JSON.stringify(app.document_urls_json) : null,
}; };
}); });
} }
@ -949,7 +950,7 @@ export function createE2ETenantApplication(
applicant_id: applicantId, applicant_id: applicantId,
application_status: "pending", application_status: "pending",
application_data_encrypted: applicationData, application_data_encrypted: applicationData,
document_urls_json: documentUrlsJson, document_urls_json: documentUrlsJson ? JSON.parse(documentUrlsJson) as Record<string, unknown> : null,
submitted_on: BASE_TIME, submitted_on: BASE_TIME,
reviewed_on: null, reviewed_on: null,
}; };
@ -1221,3 +1222,35 @@ export function getE2ELandlordDashboardData(userId: string) {
monthlyRevenue, monthlyRevenue,
}; };
} }
export function getE2EPendingApprovals() {
return e2eLeases.filter((lease) =>
lease.approval_status === "pending" || lease.approval_status === "requested"
);
}
export function addE2ELeaseWithApproval(
propertyId: string,
tenantId: string,
ownerId: string,
approvalStatus: string = "pending",
) {
const newLease: Lease = {
id: crypto.randomUUID(),
property_id: propertyId,
property_address: getE2EPropertyById(propertyId)?.address_1 || "",
tenant_id: tenantId,
owner_id: ownerId,
start_date: BASE_TIME + 1000 * 60 * 60 * 24 * 7,
end_date: BASE_TIME + 1000 * 60 * 60 * 24 * 377,
rent: 2000,
security_deposit: 2000,
lease_status: "draft",
approval_status: approvalStatus,
approval_requested_at: approvalStatus === "pending" ? BASE_TIME : null,
created_on: BASE_TIME,
updated_on: BASE_TIME,
};
e2eLeases = [newLease, ...e2eLeases];
return newLease;
}

View File

@ -216,7 +216,7 @@ export async function getPendingLeaseApprovals(
landlord_id: string, landlord_id: string,
) { ) {
if (isE2EEnabled()) { if (isE2EEnabled()) {
return getE2EPendingApprovals(landlord_id); return getE2EPendingApprovals();
} }
const pendingLeases = await db.select() const pendingLeases = await db.select()
@ -311,7 +311,7 @@ export async function getLandlordDashboardData(
// In a real implementation, you'd use an IN clause // In a real implementation, you'd use an IN clause
// For now, we'll get all applications and filter // For now, we'll get all applications and filter
applicationsResult = await db.select() applicationsResult = await db.select()
.from(tenant_applications); .from(tenant_applications) as TenantApplication[];
applicationsResult = applicationsResult.filter((app) => applicationsResult = applicationsResult.filter((app) =>
app.property_id && property_ids.includes(app.property_id) app.property_id && property_ids.includes(app.property_id)

View File

@ -434,7 +434,7 @@ export async function rejectLease(
// Get pending lease approvals for a landlord // Get pending lease approvals for a landlord
export async function getPendingApprovals(landlord_id: string) { export async function getPendingApprovals(landlord_id: string) {
if (isE2EEnabled()) { if (isE2EEnabled()) {
return getE2EPendingApprovals(landlord_id); return getE2EPendingApprovals();
} }
return await db.select() return await db.select()

View File

@ -18,7 +18,7 @@ export interface TenantApplication {
applicant_id?: string | null; applicant_id?: string | null;
application_status?: string | null; // 'pending', 'approved', 'rejected' application_status?: string | null; // 'pending', 'approved', 'rejected'
application_data_encrypted?: string | null; // Encrypted application details application_data_encrypted?: string | null; // Encrypted application details
document_urls_json?: string | null; document_urls_json?: Record<string, unknown> | null;
submitted_on?: number | null; submitted_on?: number | null;
reviewed_on?: number | null; reviewed_on?: number | null;
} }
@ -49,7 +49,14 @@ export interface ApplicationData {
} }
// Extended interface for dashboard tenant applications with UI properties // Extended interface for dashboard tenant applications with UI properties
export interface DashboardTenantApplication extends TenantApplication { export interface DashboardTenantApplication {
id: string;
property_id?: string | null;
applicant_id?: string | null;
application_status?: string | null;
application_data_encrypted?: string | null;
submitted_on?: number | null;
reviewed_on?: number | null;
applicant_name?: string; applicant_name?: string;
applicant_email?: string; applicant_email?: string;
property_address?: string; property_address?: string;
@ -57,6 +64,7 @@ export interface DashboardTenantApplication extends TenantApplication {
application_notes?: string; application_notes?: string;
status?: string; // UI-friendly status (maps to application_status) status?: string; // UI-friendly status (maps to application_status)
created_at?: number | null; // UI-friendly timestamp (maps to submitted_on) created_at?: number | null; // UI-friendly timestamp (maps to submitted_on)
document_urls_json?: string | null; // UI-friendly string representation
} }
// Create new tenant application // Create new tenant application

View File

@ -37,7 +37,7 @@ export async function isCreateTokenValid(
tokenId: string, tokenId: string,
stateId: string, stateId: string,
): Promise<WebToken | null> { ): Promise<WebToken | null> {
const token = await sqlGetWebToken(tokenId); const token = await sqlGetWebToken(tokenId) as WebToken | null;
const now = Date.now(); const now = Date.now();
if ( if (