2026-02-14 02:20:19 +00:00

157 lines
5.2 KiB
TypeScript

// routes/tenant/messages/[id]/index.tsx
import { define } from "../../../../utils.ts";
import { page } from "fresh";
import { getMessageThread } from "../../../../services/messageService.ts";
import { Message } from "../../../../db/interfaces.ts";
import { AlertMessage } from "../../maintenance/new/components/AlertMessage.tsx";
interface MessageThreadPageData {
thread: Message[];
error?: string;
currentUserId: string;
rootMessageId: string;
}
export const handler = define.handlers({
async GET(ctx) {
const { user_id } = ctx.state.data;
const messageId = ctx.params.id;
try {
const thread = await getMessageThread(messageId);
if (!thread || thread.length === 0) {
return page<MessageThreadPageData>({
thread: [],
error: "Message thread not found.",
currentUserId: user_id,
rootMessageId: messageId,
});
}
// The root message is the first one in the thread array (due to the service's logic)
const rootMessageId = thread[0].parent_message_id || thread[0].id;
return page<MessageThreadPageData>({
thread,
currentUserId: user_id,
rootMessageId,
});
} catch (error) {
console.error("Error loading message thread:", error);
return page<MessageThreadPageData>({
thread: [],
error: "Failed to load message thread.",
currentUserId: user_id,
rootMessageId: messageId,
});
}
},
});
// Helper component for rendering a single message
function MessageBubble(
{ message, isOutgoing }: { message: Message; isOutgoing: boolean },
) {
const bubbleClass = isOutgoing
? "bg-blue-500 text-white self-end rounded-br-none"
: "bg-gray-200 text-gray-800 self-start rounded-tl-none";
const alignmentClass = isOutgoing ? "justify-end" : "justify-start";
const date = message.created_on
? new Date(message.created_on).toLocaleString()
: "Unknown Date";
const attachments = Array.isArray(message.attachments_json)
? message.attachments_json
: message.attachments_json
? JSON.parse(message.attachments_json as unknown as string)
: [];
const content = message.content_encrypted || message.content || "";
return (
<div class={`flex ${alignmentClass} mb-4`}>
<div class={`max-w-3/4 p-4 rounded-lg shadow-md ${bubbleClass}`}>
<p class="text-xs opacity-80 mb-1">
{isOutgoing ? "You" : "Landlord"} - {date}
</p>
<p class="text-sm whitespace-pre-wrap">{content}</p>
{attachments.length > 0 && (
<div class="mt-3 pt-3 border-t border-opacity-30">
<p class="text-xs font-semibold mb-1">Attachments:</p>
<ul class="space-y-1">
{attachments.map((att: { url: string; name: string }) => (
<li key={att.url}>
<a
href={att.url}
target="_blank"
class="text-xs underline hover:text-opacity-80"
>
{att.name}
</a>
</li>
))}
</ul>
</div>
)}
</div>
</div>
);
}
export default define.page(function MessageThreadPage({ data }) {
const { thread, error, currentUserId, rootMessageId } =
data as MessageThreadPageData;
const rootMessage = thread[0];
const subject = rootMessage?.subject || "Conversation";
return (
<div class="min-h-screen bg-gray-50">
{/* Header */}
<div class="bg-white shadow">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="py-6 flex justify-between items-center">
<div>
<h1 class="text-3xl font-bold text-gray-900">Message Thread</h1>
<p class="mt-1 text-sm text-gray-500">Subject: {subject}</p>
</div>
<a
href={`/tenant/messages/new?reply_to=${rootMessageId}`}
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Reply
</a>
</div>
</div>
</div>
{/* Main Content */}
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8 py-8">
{error && <AlertMessage type="error">{error}</AlertMessage>}
<div class="flex flex-col space-y-4 p-6 bg-white shadow overflow-hidden sm:rounded-lg">
{thread.map((message) => (
<MessageBubble
key={message.id}
message={message}
isOutgoing={message.sender_id === currentUserId}
/>
))}
</div>
{/* Reply Button at the bottom for convenience */}
<div class="mt-8 flex justify-end">
<a
href={`/tenant/messages/new?reply_to=${rootMessageId}`}
class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Reply to Thread
</a>
</div>
</div>
</div>
);
});