summaryrefslogtreecommitdiff
path: root/garden/src/pages/letters/index.tsx
diff options
context:
space:
mode:
authorBobby <[email protected]>2026-03-13 18:25:44 +0530
committerBobby <[email protected]>2026-03-13 18:25:44 +0530
commit344d02a7feddefb5c08f88dbe5f3a3f7e7da385f (patch)
tree94deed23d82d7f868721cc00b5550f5c27e8b8f7 /garden/src/pages/letters/index.tsx
parent9f808807a557fc10a38a44cb52be6bfcdfda68b2 (diff)
downloadpagoda-main.tar.xz
pagoda-main.zip
feat: add letters feature with detail view and listingHEADmain
- Introduced new routes for letters and their details. - Created pages for displaying letter details and listing letters. - Added new types for letters, including participants, messages, and attachments. - Implemented API calls for fetching letters and managing messages (reply, edit, delete). - Enhanced stats to include unread letters and pending districts for staff users. - Updated styles for letters and their components. - Added privacy settings for letters (public and friends). - Modified user model to include letter privacy settings. - Improved error handling and user feedback in the UI.
Diffstat (limited to 'garden/src/pages/letters/index.tsx')
-rw-r--r--garden/src/pages/letters/index.tsx105
1 files changed, 105 insertions, 0 deletions
diff --git a/garden/src/pages/letters/index.tsx b/garden/src/pages/letters/index.tsx
new file mode 100644
index 0000000..2615a1d
--- /dev/null
+++ b/garden/src/pages/letters/index.tsx
@@ -0,0 +1,105 @@
+import { createSignal, onMount, Show, For } from "solid-js";
+import { A, useNavigate, useSearchParams } from "@solidjs/router";
+import { api } from "../../api";
+import { auth } from "../../store/auth";
+import { formatDateTime } from "../../utils/format";
+import type { Letter } from "../../types/letter";
+import type { PaginatedResponse } from "../../types/admin";
+import Pagination from "../../components/Pagination";
+
+export default function Letters() {
+ const navigate = useNavigate();
+ const [searchParams, setSearchParams] = useSearchParams();
+
+ const [letters, setLetters] = createSignal<Letter[]>([]);
+ const [total, setTotal] = createSignal(0);
+ const [page, setPage] = createSignal(1);
+ const [totalPages, setTotalPages] = createSignal(0);
+ const [loading, setLoading] = createSignal(true);
+
+ onMount(() => {
+ if (!auth.token()) {
+ navigate("/login");
+ return;
+ }
+ const p = parseInt(searchParams.page as string) || 1;
+ loadLetters(p);
+ });
+
+ async function loadLetters(pageNumber = 1) {
+ setLoading(true);
+ const response = await api<PaginatedResponse<Letter>>(`/letters?page=${pageNumber}&per_page=20`, {
+ token: auth.token(),
+ });
+
+ if (response.ok) {
+ setLetters(response.data.items);
+ setTotal(response.data.total);
+ setPage(response.data.page);
+ setTotalPages(response.data.total_pages);
+ }
+ setLoading(false);
+ }
+
+ function goToPage(p: number) {
+ setSearchParams({ page: String(p) });
+ loadLetters(p);
+ }
+
+ function previewBody(message?: Letter["last_message"]) {
+ if (!message) return "No messages yet";
+ if (message.deleted) return "Message deleted";
+ const text = message.body.replace(/<[^>]*>/g, "");
+ return text.length > 80 ? text.slice(0, 80) + "..." : text;
+ }
+
+ return (
+ <section>
+ <h2 class="page-title">Letters</h2>
+
+ <div class="letters-list">
+ <Show when={!loading()} fallback={
+ <div class="letters-empty">Loading...</div>
+ }>
+ <Show when={letters().length} fallback={
+ <div class="letters-empty">No letters yet.</div>
+ }>
+ <For each={letters()}>
+ {(letter) => (
+ <A href={`/letters/${letter.ref}`} class="letter-item" classList={{ "letter-unread": letter.unread }}>
+ <div class="letter-avatars">
+ <For each={letter.participants.slice(0, 3)}>
+ {(p) => (
+ <img src={p.avatar_url} alt="" class="letter-avatar" />
+ )}
+ </For>
+ <Show when={letter.participants.length > 3}>
+ <span class="letter-avatar-more">+{letter.participants.length - 3}</span>
+ </Show>
+ </div>
+ <div class="letter-content">
+ <div class="letter-top">
+ <span class="letter-title">
+ <Show when={letter.is_system}>
+ <span class="letter-system-badge">System</span>
+ </Show>
+ {letter.title}
+ </span>
+ <span class="letter-time">{formatDateTime(letter.updated_at)}</span>
+ </div>
+ <div class="letter-preview">{previewBody(letter.last_message)}</div>
+ </div>
+ <Show when={letter.unread}>
+ <span class="letter-unread-dot" />
+ </Show>
+ </A>
+ )}
+ </For>
+ </Show>
+ </Show>
+ </div>
+
+ <Pagination page={page()} totalPages={totalPages()} total={total()} label="letters" onPage={goToPage} />
+ </section>
+ );
+} \ No newline at end of file