Skip to content

Commit

Permalink
feat: users table (denoland#396)
Browse files Browse the repository at this point in the history
This change also:
* Makes the site text size slightly smaller. Previously, it seemed too
big.
* Adds a `<TabsBar />` component.

Closes denoland#370

![localhost_8000_dashboard_users
(2)](https://github.com/denoland/saaskit/assets/29347852/7f26c0bf-bd4c-4749-b5ae-d921497c0a58)
  • Loading branch information
iuioiua authored Jul 27, 2023
1 parent cfab35f commit a01cb2a
Show file tree
Hide file tree
Showing 7 changed files with 270 additions and 135 deletions.
26 changes: 26 additions & 0 deletions components/TabsBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import { LINK_STYLES } from "@/utils/constants.ts";
import { cx } from "@twind/core";

export default function TabsBar(
props: { links: { path: string; innerText: string }[]; currentPath: string },
) {
return (
<div class="flex flex-row w-full mb-8">
{props.links.map((link) => (
<a
href={link.path}
class={cx(
"px-4 py-2 rounded-lg",
link.path === props.currentPath
? "bg-gray-100 text-black dark:(bg-gray-800 text-white)"
: "",
LINK_STYLES,
)}
>
{link.innerText}
</a>
))}
</div>
);
}
48 changes: 26 additions & 22 deletions fresh.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,19 @@ import * as $11 from "./routes/blog/index.tsx";
import * as $12 from "./routes/callback.ts";
import * as $13 from "./routes/dashboard/_middleware.ts";
import * as $14 from "./routes/dashboard/index.tsx";
import * as $15 from "./routes/feed.ts";
import * as $16 from "./routes/index.tsx";
import * as $17 from "./routes/item/[id].tsx";
import * as $18 from "./routes/notifications/[id].ts";
import * as $19 from "./routes/notifications/_middleware.ts";
import * as $20 from "./routes/notifications/index.tsx";
import * as $21 from "./routes/pricing.tsx";
import * as $22 from "./routes/signin.ts";
import * as $23 from "./routes/signout.ts";
import * as $24 from "./routes/submit.tsx";
import * as $25 from "./routes/user/[login].tsx";
import * as $15 from "./routes/dashboard/stats.tsx";
import * as $16 from "./routes/dashboard/users.tsx";
import * as $17 from "./routes/feed.ts";
import * as $18 from "./routes/index.tsx";
import * as $19 from "./routes/item/[id].tsx";
import * as $20 from "./routes/notifications/[id].ts";
import * as $21 from "./routes/notifications/_middleware.ts";
import * as $22 from "./routes/notifications/index.tsx";
import * as $23 from "./routes/pricing.tsx";
import * as $24 from "./routes/signin.ts";
import * as $25 from "./routes/signout.ts";
import * as $26 from "./routes/submit.tsx";
import * as $27 from "./routes/user/[login].tsx";
import * as $$0 from "./islands/Chart.tsx";
import * as $$1 from "./islands/PageInput.tsx";
import * as $$2 from "./islands/VoteButton.tsx";
Expand All @@ -49,17 +51,19 @@ const manifest = {
"./routes/callback.ts": $12,
"./routes/dashboard/_middleware.ts": $13,
"./routes/dashboard/index.tsx": $14,
"./routes/feed.ts": $15,
"./routes/index.tsx": $16,
"./routes/item/[id].tsx": $17,
"./routes/notifications/[id].ts": $18,
"./routes/notifications/_middleware.ts": $19,
"./routes/notifications/index.tsx": $20,
"./routes/pricing.tsx": $21,
"./routes/signin.ts": $22,
"./routes/signout.ts": $23,
"./routes/submit.tsx": $24,
"./routes/user/[login].tsx": $25,
"./routes/dashboard/stats.tsx": $15,
"./routes/dashboard/users.tsx": $16,
"./routes/feed.ts": $17,
"./routes/index.tsx": $18,
"./routes/item/[id].tsx": $19,
"./routes/notifications/[id].ts": $20,
"./routes/notifications/_middleware.ts": $21,
"./routes/notifications/index.tsx": $22,
"./routes/pricing.tsx": $23,
"./routes/signin.ts": $24,
"./routes/signout.ts": $25,
"./routes/submit.tsx": $26,
"./routes/user/[login].tsx": $27,
},
islands: {
"./islands/Chart.tsx": $$0,
Expand Down
2 changes: 1 addition & 1 deletion routes/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Footer from "@/components/Footer.tsx";

export default function App(props: AppProps) {
return (
<div class="dark:bg-gray-900 text-lg">
<div class="dark:bg-gray-900">
<div class="flex flex-col min-h-screen mx-auto max-w-7xl w-full dark:text-white">
<Header
url={props.url}
Expand Down
117 changes: 5 additions & 112 deletions routes/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,117 +1,10 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import type { Handlers, PageProps } from "$fresh/server.ts";
import { DAY } from "std/datetime/constants.ts";
import Chart from "@/islands/Chart.tsx";
import { getDatesSince, getManyMetrics } from "@/utils/db.ts";
import Head from "@/components/Head.tsx";
import type { SignedInState } from "@/utils/middleware.ts";

interface DashboardPageData extends SignedInState {
dates: Date[];
visitsCounts: number[];
usersCounts: number[];
itemsCounts: number[];
votesCounts: number[];
}
import type { Handlers } from "$fresh/server.ts";
import { redirect } from "@/utils/redirect.ts";

export const handler: Handlers<DashboardPageData, SignedInState> = {
async GET(_req, ctx) {
const msAgo = 30 * DAY;
const dates = getDatesSince(msAgo).map((date) => new Date(date));

const [
visitsCounts,
usersCounts,
itemsCounts,
votesCounts,
] = await Promise.all([
getManyMetrics("visits_count", dates),
getManyMetrics("users_count", dates),
getManyMetrics("items_count", dates),
getManyMetrics("votes_count", dates),
]);

return ctx.render({
...ctx.state,
dates,
visitsCounts: visitsCounts.map(Number),
usersCounts: usersCounts.map(Number),
itemsCounts: itemsCounts.map(Number),
votesCounts: votesCounts.map(Number),
});
export const handler: Handlers = {
GET(_req) {
return redirect("/dashboard/stats");
},
};

export default function DashboardPage(props: PageProps<DashboardPageData>) {
const datasets = [
{
label: "Site visits",
data: props.data.visitsCounts,
borderColor: "#be185d",
},
{
label: "Users created",
data: props.data.usersCounts,
borderColor: "#e85d04",
},
{
label: "Items created",
data: props.data.itemsCounts,
borderColor: "#219ebc",
},
{
label: "Votes",
data: props.data.votesCounts,
borderColor: "#4338ca",
},
];

const max = Math.max(...datasets[0].data);

const labels = props.data.dates.map((date) =>
new Date(date).toLocaleDateString("en-us", {
month: "short",
day: "numeric",
})
);

return (
<>
<Head title="Dashboard" href={props.url.href} />
<main class="flex-1 p-4 flex flex-col">
<h1 class="text-3xl font-bold">Dashboard</h1>
<div class="flex-1 relative">
<Chart
type="line"
options={{
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: "index",
},
scales: {
x: {
max,
grid: { display: false },
},
y: {
beginAtZero: true,
grid: { display: false },
ticks: { precision: 0 },
},
},
}}
data={{
labels,
datasets: datasets.map((dataset) => ({
...dataset,
pointRadius: 0,
cubicInterpolationMode: "monotone",
})),
}}
/>
</div>
</main>
</>
);
}
128 changes: 128 additions & 0 deletions routes/dashboard/stats.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import type { Handlers, PageProps } from "$fresh/server.ts";
import { DAY } from "std/datetime/constants.ts";
import Chart from "@/islands/Chart.tsx";
import { getDatesSince, getManyMetrics } from "@/utils/db.ts";
import Head from "@/components/Head.tsx";
import type { SignedInState } from "@/utils/middleware.ts";
import TabsBar from "@/components/TabsBar.tsx";

interface DashboardPageData extends SignedInState {
dates: Date[];
visitsCounts: number[];
usersCounts: number[];
itemsCounts: number[];
votesCounts: number[];
}

export const handler: Handlers<DashboardPageData, SignedInState> = {
async GET(_req, ctx) {
const msAgo = 30 * DAY;
const dates = getDatesSince(msAgo).map((date) => new Date(date));

const [
visitsCounts,
usersCounts,
itemsCounts,
votesCounts,
] = await Promise.all([
getManyMetrics("visits_count", dates),
getManyMetrics("users_count", dates),
getManyMetrics("items_count", dates),
getManyMetrics("votes_count", dates),
]);

return ctx.render({
...ctx.state,
dates,
visitsCounts: visitsCounts.map(Number),
usersCounts: usersCounts.map(Number),
itemsCounts: itemsCounts.map(Number),
votesCounts: votesCounts.map(Number),
});
},
};

export default function DashboardPage(props: PageProps<DashboardPageData>) {
const datasets = [
{
label: "Site visits",
data: props.data.visitsCounts,
borderColor: "#be185d",
},
{
label: "Users created",
data: props.data.usersCounts,
borderColor: "#e85d04",
},
{
label: "Items created",
data: props.data.itemsCounts,
borderColor: "#219ebc",
},
{
label: "Votes",
data: props.data.votesCounts,
borderColor: "#4338ca",
},
];

const max = Math.max(...datasets[0].data);

const labels = props.data.dates.map((date) =>
new Date(date).toLocaleDateString("en-us", {
month: "short",
day: "numeric",
})
);

return (
<>
<Head title="Dashboard" href={props.url.href} />
<main class="flex-1 p-4 flex flex-col">
<h1 class="text-3xl font-bold mb-8">Dashboard</h1>
<TabsBar
links={[{
path: "/dashboard/stats",
innerText: "Stats",
}, {
path: "/dashboard/users",
innerText: "Users",
}]}
currentPath={props.url.pathname}
/>
<div class="flex-1 relative">
<Chart
type="line"
options={{
maintainAspectRatio: false,
interaction: {
intersect: false,
mode: "index",
},
scales: {
x: {
max,
grid: { display: false },
},
y: {
beginAtZero: true,
grid: { display: false },
ticks: { precision: 0 },
},
},
}}
data={{
labels,
datasets: datasets.map((dataset) => ({
...dataset,
pointRadius: 0,
cubicInterpolationMode: "monotone",
})),
}}
/>
</div>
</main>
</>
);
}
Loading

0 comments on commit a01cb2a

Please sign in to comment.