Skip to content

Commit

Permalink
feat: cursor-based pagination for users table (denoland#446)
Browse files Browse the repository at this point in the history
Closes denoland#398
Towards denoland#414
  • Loading branch information
iuioiua authored Aug 22, 2023
1 parent 088c0ff commit e27502e
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 47 deletions.
6 changes: 4 additions & 2 deletions fresh.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ import * as $$0 from "./islands/Chart.tsx";
import * as $$1 from "./islands/CommentsList.tsx";
import * as $$2 from "./islands/NotificationsList.tsx";
import * as $$3 from "./islands/PageInput.tsx";
import * as $$4 from "./islands/VoteButton.tsx";
import * as $$4 from "./islands/UsersTable.tsx";
import * as $$5 from "./islands/VoteButton.tsx";

const manifest = {
routes: {
Expand Down Expand Up @@ -88,7 +89,8 @@ const manifest = {
"./islands/CommentsList.tsx": $$1,
"./islands/NotificationsList.tsx": $$2,
"./islands/PageInput.tsx": $$3,
"./islands/VoteButton.tsx": $$4,
"./islands/UsersTable.tsx": $$4,
"./islands/VoteButton.tsx": $$5,
},
baseUrl: import.meta.url,
};
Expand Down
87 changes: 87 additions & 0 deletions islands/UsersTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import { useSignal } from "@preact/signals";
import { useEffect } from "preact/hooks";
import type { User } from "@/utils/db.ts";
import GitHubAvatarImg from "@/components/GitHubAvatarImg.tsx";
import { LINK_STYLES } from "@/utils/constants.ts";

const TH_STYLES = "p-4 text-left";
const TD_STYLES = "p-4";

async function fetchUsers(cursor: string) {
let url = "/api/users";
if (cursor !== "") url += "?cursor=" + cursor;
const resp = await fetch(url);
if (!resp.ok) throw new Error(`Request failed: GET ${url}`);
return await resp.json() as { users: User[]; cursor: string };
}

function UserTableRow(props: User) {
return (
<tr class="hover:(bg-gray-50 dark:bg-gray-900) border-b border-gray-200">
<td scope="col" class={TD_STYLES}>
<GitHubAvatarImg login={props.login} size={32} />
<a
class="hover:underline ml-4 align-middle"
href={"/users/" + props.login}
>
{props.login}
</a>
</td>
<td scope="col" class={TD_STYLES + " text-gray-500"}>
{props.isSubscribed ? "Premium 🦕" : "Basic"}
</td>
<td scope="col" class={TD_STYLES + " text-gray-500"}>
${(Math.random() * 100).toFixed(2)}
</td>
</tr>
);
}

export default function UsersTable() {
const usersSig = useSignal<User[]>([]);
const cursorSig = useSignal("");
const isLoadingSig = useSignal(false);

async function loadMoreUsers() {
isLoadingSig.value = true;
try {
const { users, cursor } = await fetchUsers(cursorSig.value);
usersSig.value = [...usersSig.value, ...users];
cursorSig.value = cursor;
} catch (error) {
console.log(error.message);
} finally {
isLoadingSig.value = false;
}
}

useEffect(() => {
loadMoreUsers();
}, []);

return (
<div class="w-full rounded-lg shadow border-1 border-gray-300 overflow-x-auto">
<table class="table-auto border-collapse w-full">
<thead class="border-b border-gray-300">
<tr>
<th scope="col" class={TH_STYLES}>User</th>
<th scope="col" class={TH_STYLES}>Subscription</th>
<th scope="col" class={TH_STYLES}>Revenue</th>
</tr>
</thead>
<tbody>
{usersSig.value.map((user) => <UserTableRow {...user} />)}
</tbody>
</table>
{cursorSig.value !== "" && !isLoadingSig.value && (
<button
onClick={loadMoreUsers}
class={LINK_STYLES + " p-4"}
>
Load more
</button>
)}
</div>
);
}
48 changes: 3 additions & 45 deletions routes/dashboard/users.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,15 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import { getUsers, User } from "@/utils/db.ts";
import type { RouteContext } from "$fresh/server.ts";
import GitHubAvatarImg from "@/components/GitHubAvatarImg.tsx";
import Head from "@/components/Head.tsx";
import TabsBar from "@/components/TabsBar.tsx";
import { HEADING_WITH_MARGIN_STYLES } from "@/utils/constants.ts";
import UsersTable from "@/islands/UsersTable.tsx";

const TH_STYLES = "p-4 text-left";
const TD_STYLES = "p-4";

function UsersTable(props: { users: User[] }) {
return (
<div class="w-full rounded-lg shadow border-1 border-gray-300 overflow-x-auto">
<table class="table-auto border-collapse w-full">
<thead class="border-b border-gray-300">
<tr>
<th scope="col" class={TH_STYLES}>User</th>
<th scope="col" class={TH_STYLES}>Subscription</th>
<th scope="col" class={TH_STYLES}>Revenue</th>
</tr>
</thead>
<tbody>
{props.users.map((user) => (
<tr class="hover:(bg-gray-50 dark:bg-gray-900) border-b border-gray-200">
<td scope="col" class={TD_STYLES}>
<GitHubAvatarImg login={user.login} size={32} />
<a
class="hover:underline ml-4 align-middle"
href={"/users/" + user.login}
>
{user.login}
</a>
</td>
<td scope="col" class={TD_STYLES + " text-gray-500"}>
{user.isSubscribed ? "Premium 🦕" : "Basic"}
</td>
<td scope="col" class={TD_STYLES + " text-gray-500"}>
${(Math.random() * 100).toFixed(2)}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}

// deno-lint-ignore require-await
export default async function DashboardUsersPage(
_req: Request,
ctx: RouteContext,
) {
const users = await getUsers();

return (
<>
<Head title="Users" href={ctx.url.href} />
Expand All @@ -67,7 +25,7 @@ export default async function DashboardUsersPage(
}]}
currentPath={ctx.url.pathname}
/>
<UsersTable users={users} />
<UsersTable />
</main>
</>
);
Expand Down

0 comments on commit e27502e

Please sign in to comment.