224 lines
7.3 KiB
TypeScript

// routes/landlord/messages/[id]/index.tsx
import { define } from "../../../../utils.ts";
import { page } from "fresh";
import {
getMessageThread,
markThreadAsRead,
} from "../../../../services/messageService.ts";
import { Message } from "../../../../db/interfaces.ts";
import { AlertMessage } from "../../../tenant/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,
});
}
const rootMessageId = thread[0].parent_message_id || thread[0].id;
await markThreadAsRead(user_id, rootMessageId);
return page<MessageThreadPageData>({
thread,
currentUserId: user_id,
rootMessageId,
});
} catch (err) {
console.error("Error loading message thread:", err);
return page<MessageThreadPageData>({
thread: [],
error: "Failed to load message thread.",
currentUserId: user_id,
rootMessageId: messageId,
});
}
},
});
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" : "Tenant"} - {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"
rel="noopener noreferrer"
class="text-xs underline hover:text-opacity-80"
>
{att.name}
</a>
</li>
))}
</ul>
</div>
)}
</div>
</div>
);
}
export default define.page(function LandlordMessageThreadPage({ data }) {
const { thread, error, currentUserId, rootMessageId } =
data as MessageThreadPageData;
const rootMessage = thread[0];
const subject = rootMessage?.subject || "Conversation";
const replyRecipientId = rootMessage
? (rootMessage.sender_id === currentUserId
? rootMessage.recipient_id
: rootMessage.sender_id)
: null;
const replySubject = rootMessage?.subject
? `Re: ${rootMessage.subject}`
: "Re: Message";
return (
<div class="min-h-screen bg-gray-50 landlord-route">
<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>
<div class="flex space-x-3">
<a
href="/landlord/messages"
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 shadow-sm"
>
All messages
</a>
<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 shadow-sm"
>
Dashboard
</a>
</div>
</div>
</div>
</div>
<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>
{replyRecipientId && (
<div class="mt-8 p-6 bg-white shadow overflow-hidden sm:rounded-lg">
<h2 class="text-lg font-semibold text-gray-900 mb-4">
Reply to thread
</h2>
<form
method="POST"
action="/api/create/message"
class="space-y-4"
>
<input
type="hidden"
name="sender"
value={`/landlord/messages/${rootMessageId}`}
/>
<input
type="hidden"
name="lease_id"
value={rootMessage?.lease_id ?? ""}
/>
<input
type="hidden"
name="recipient_id"
value={replyRecipientId}
/>
<input
type="hidden"
name="message_type"
value={rootMessage?.message_type ?? "general"}
/>
<input type="hidden" name="subject" value={replySubject} />
<div>
<label
for="reply-content"
class="block text-sm font-medium text-gray-700"
>
Your reply
</label>
<textarea
id="reply-content"
name="content"
rows={4}
required
placeholder="Type your reply..."
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-sm"
/>
</div>
<div class="flex justify-end">
<button
type="submit"
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"
>
Send reply
</button>
</div>
</form>
</div>
)}
</div>
</div>
);
});