164 lines
5.3 KiB
TypeScript
164 lines
5.3 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;
|
|
|
|
if (!user_id) {
|
|
return new Response("", {
|
|
status: 302,
|
|
headers: { Location: "/auth/login" },
|
|
});
|
|
}
|
|
|
|
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 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>
|
|
);
|
|
});
|