Skip to content

Commit

Permalink
feat(ui): use htmx for loading in the sidebar organizations
Browse files Browse the repository at this point in the history
  • Loading branch information
SeanCassiere committed Jul 2, 2024
1 parent 7fe25aa commit f716ea3
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 90 deletions.
26 changes: 26 additions & 0 deletions src/routers/app/components/sidebar-organizations.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { FC } from "hono/jsx";

import type { TenantRecord } from "@/types/db.mjs";

export const SidebarOrganizations: FC<{
workspace: string;
tenants: Array<TenantRecord>;
}> = ({ workspace, tenants }) => {
return tenants.length > 0 ? (
<ul>
{tenants.map((item) => (
<li>
<a
href={`/app/${item.workspace}`}
class='block py-1 data-[active-tenant="true"]:text-blue-500'
data-active-tenant={workspace.length ? item.workspace === workspace : false}
>
{item.name}
</a>
</li>
))}
</ul>
) : (
<p>😞 You have no organizations.</p>
);
};
29 changes: 29 additions & 0 deletions src/routers/app/hx-router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Hono } from "hono";

import { db } from "@/config/db/drizzle.mjs";
import type { ServerContext } from "@/types/hono.mjs";

import { SidebarOrganizations } from "./components/sidebar-organizations.js";

import { checkUserAuthed } from "./utils/middleware.mjs";

const app = new Hono<ServerContext>();

app.get("/sidebar-organizations", checkUserAuthed, async (c) => {
const user = c.var.user!;

// DO NOT USE THIS PARAMETER FOR AUTH
// THIS IS PURELY BEING USED FOR STYLING
const workspace = c.req.query("current_workspace") || "";

const relationships = await db.query.usersToTenants.findMany({
where: (fields, { eq }) => eq(fields.userId, user.id),
with: { tenant: true },
});

const tenants = relationships.map((r) => r.tenant);

return c.html(<SidebarOrganizations workspace={workspace} tenants={tenants} />);
});

export default app;
48 changes: 13 additions & 35 deletions src/routers/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ import { ServiceEditPage } from "./pages/app.$workspace.$serviceId.edit.js";

import { checkTenantMembership, checkUserAuthed, checkServiceTenantMembership } from "./utils/middleware.mjs";

import hxRouter from "./hx-router.js";

import type { ServerContext } from "@/types/hono.mjs";

const app = new Hono<ServerContext>();

app.use("*", sessionMiddleware);

app.route("/hx", hxRouter);

app.get("/", checkUserAuthed, async (c) => {
const user = c.var.user!;
const view_all = c.req.query("view_all") || "false";
Expand All @@ -34,7 +38,7 @@ app.get("/", checkUserAuthed, async (c) => {
return c.redirect(`/app/${tenant.workspace}`);
}

return c.html(<NoOrganizationPage user={user} tenants={tenants} />);
return c.html(<NoOrganizationPage user={user} workspace="" />);
});

app.get("/login", async (c) => {
Expand All @@ -48,50 +52,31 @@ app.get("/login", async (c) => {
});

app.get("/:workspace", checkUserAuthed, checkTenantMembership, async (c) => {
const workspace = c.req.param("workspace");
const tenant = c.var.tenant!;
const user = c.var.user!;

const relationships = await db.query.usersToTenants.findMany({
where: (fields, { eq }) => eq(fields.userId, user.id),
with: { tenant: true },
});

const tenants = relationships.map((r) => r.tenant);

const services = await db.query.services.findMany({
where: (fields, { eq }) => eq(fields.tenantId, tenant.id),
});

return c.html(<WorkspaceLandingPage user={user} tenants={tenants} tenant={tenant} services={services} />);
return c.html(<WorkspaceLandingPage user={user} workspace={workspace} tenant={tenant} services={services} />);
});

app.get("/:workspace/edit", checkUserAuthed, checkTenantMembership, async (c) => {
const workspace = c.req.param("workspace");
const tenant = c.var.tenant!;
const user = c.var.user!;

const relationships = await db.query.usersToTenants.findMany({
where: (fields, { eq }) => eq(fields.userId, user.id),
with: { tenant: true },
});

const tenants = relationships.map((r) => r.tenant);

return c.html(<WorkspaceEditPage user={user} tenants={tenants} tenant={tenant} />);
return c.html(<WorkspaceEditPage user={user} workspace={workspace} tenant={tenant} />);
});

app.get("/:workspace/:service_id", checkUserAuthed, checkTenantMembership, checkServiceTenantMembership, async (c) => {
const tenant = c.var.tenant!;
const workspace = c.req.param("workspace");
const service = c.var.service!;
const user = c.var.user!;

const relationships = await db.query.usersToTenants.findMany({
where: (fields, { eq }) => eq(fields.userId, user.id),
with: { tenant: true },
});

const tenants = relationships.map((r) => r.tenant);

return c.html(<ServiceLandingPage user={user} tenant={tenant} tenants={tenants} service={service} />);
return c.html(<ServiceLandingPage user={user} workspace={workspace} service={service} />);
});

app.get(
Expand All @@ -100,18 +85,11 @@ app.get(
checkTenantMembership,
checkServiceTenantMembership,
async (c) => {
const tenant = c.var.tenant!;
const workspace = c.req.param("workspace");
const service = c.var.service!;
const user = c.var.user!;

const relationships = await db.query.usersToTenants.findMany({
where: (fields, { eq }) => eq(fields.userId, user.id),
with: { tenant: true },
});

const tenants = relationships.map((r) => r.tenant);

return c.html(<ServiceEditPage user={user} tenant={tenant} tenants={tenants} service={service} />);
return c.html(<ServiceEditPage user={user} workspace={workspace} service={service} />);
},
);

Expand Down
46 changes: 16 additions & 30 deletions src/routers/app/layouts/app-container.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,38 @@
import type { FC, PropsWithChildren } from "hono/jsx";
import { User } from "lucia";

import type { TenantRecord } from "@/types/db.mjs";

export interface AppContainerProps {
user: User;
tenant: TenantRecord | null;
tenants: Array<TenantRecord>;
workspace: string;
mainClass?: string;
}

export const AppContainer: FC<PropsWithChildren<AppContainerProps>> = ({
user,
tenant,
tenants,
children,
mainClass,
}) => {
export const AppContainer: FC<PropsWithChildren<AppContainerProps>> = (props) => {
const { user, children, mainClass } = props;

return (
<div className="flex flex-col md:grid md:grid-cols-4 lg:grid-cols-5 min-h-full">
<div class="flex flex-col md:grid md:grid-cols-4 lg:grid-cols-5 min-h-full">
<aside class="h-[250px] border-b flex flex-col bg-gray-100 dark:bg-gray-800 md:h-auto md:col-span-1 md:border-r md:border-b-0">
<div class="flex-grow p-2">
<p class="pb-2">Hello {user.username}!</p>
<p class="pb-2 border-b">Your organizations</p>
{tenants.length > 0 ? (
<ul>
{tenants.map((item) => (
<li>
<a
href={`/app/${item.workspace}`}
class='block py-1 data-[active-tenant="true"]:text-blue-500'
data-active-tenant={tenant ? item.id === tenant.id : false}
>
{item.name}
</a>
</li>
))}
</ul>
) : (
<p>😞 You have no organizations.</p>
)}
<SidebarFetcher {...props} />
<span class="border-t" />
</div>
<div class="p-2">
<a href="/auth/logout">👋🏼 Logout</a>
</div>
</aside>
<main className={["md:col-span-3 lg:col-span-4", mainClass].filter(Boolean).join(" ")}>{children}</main>
<main class={["md:col-span-3 lg:col-span-4", mainClass].filter(Boolean).join(" ")}>{children}</main>
</div>
);
};

const SidebarFetcher: FC<Pick<AppContainerProps, "user" | "workspace">> = ({ user, workspace }) => {
return (
<div
hx-trigger="load"
hx-get={["/app/hx/sidebar-organizations", workspace.length > 0 ? `?current_workspace=${workspace}` : ""].join("")}
></div>
);
};
13 changes: 4 additions & 9 deletions src/routers/app/pages/app.$workspace.$serviceId.edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,14 @@ import { AppContainer, type AppContainerProps } from "../layouts/app-container.j

import type { ServiceRecord } from "@/types/db.mjs";

export const ServiceEditPage: FC<{ service: ServiceRecord } & AppContainerProps> = ({
user,
tenant,
tenants,
service,
}) => {
export const ServiceEditPage: FC<{ service: ServiceRecord } & AppContainerProps> = ({ user, workspace, service }) => {
return (
<RootDocument title={`${tenant?.name} edit`}>
<AppContainer user={user} tenant={tenant} tenants={tenants} mainClass="grid place-items-center p-2 md:p-4">
<RootDocument title={`${service.name} edit - ${workspace}`}>
<AppContainer user={user} workspace={workspace} mainClass="grid place-items-center p-2 md:p-4">
<Card class="max-w-2xl w-full">
<div class="p-4 grid gap-2">
<div class="flex gap-1">
<a class={getButtonStyles("secondary", "xs")} href={`/app/${tenant?.workspace}/${service.id}`}>
<a class={getButtonStyles("secondary", "xs")} href={`/app/${workspace}/${service.id}`}>
Back ⬅️
</a>
</div>
Expand Down
10 changes: 5 additions & 5 deletions src/routers/app/pages/app.$workspace.$serviceId.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ export const ServiceLandingPage: FC<
{
service: ServiceRecord;
} & AppContainerProps
> = ({ user, tenant, tenants, service }) => {
> = ({ user, workspace, service }) => {
return (
<RootDocument title="Simple Logging Server">
<AppContainer user={user} tenant={tenant} tenants={tenants} mainClass="grid place-items-center p-2 md:p-4">
<RootDocument title={`${service.name} - ${workspace}`}>
<AppContainer user={user} workspace={workspace} mainClass="grid place-items-center p-2 md:p-4">
<Card class="max-w-2xl w-full">
<div class="p-4 grid gap-2">
<div class="flex gap-1">
<a class={getButtonStyles("secondary", "xs")} href={`/app/${tenant?.workspace}`}>
<a class={getButtonStyles("secondary", "xs")} href={`/app/${workspace}`}>
⬅️ Back
</a>
<a class={getButtonStyles("secondary", "xs")} href={`/app/${tenant?.workspace}/${service.id}/edit`}>
<a class={getButtonStyles("secondary", "xs")} href={`/app/${workspace}/${service.id}/edit`}>
✏️ Edit service
</a>
</div>
Expand Down
8 changes: 4 additions & 4 deletions src/routers/app/pages/app.$workspace.edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import { AppContainer, type AppContainerProps } from "../layouts/app-container.j

import type { TenantRecord } from "@/types/db.mjs";

export const WorkspaceEditPage: FC<{ tenant: TenantRecord } & AppContainerProps> = ({ user, tenants, tenant }) => {
export const WorkspaceEditPage: FC<{ tenant: TenantRecord } & AppContainerProps> = ({ user, workspace, tenant }) => {
return (
<RootDocument title={`${tenant.name} edit`}>
<AppContainer user={user} tenants={tenants} tenant={tenant} mainClass="grid place-items-center p-2 md:p-4">
<RootDocument title={`${workspace} edit`}>
<AppContainer user={user} workspace={workspace} mainClass="grid place-items-center p-2 md:p-4">
<Card class="max-w-2xl w-full">
<div class="p-4 grid gap-2">
<div class="flex gap-1">
<a class={getButtonStyles("secondary", "xs")} href={`/app/${tenant.workspace}`}>
<a class={getButtonStyles("secondary", "xs")} href={`/app/${workspace}`}>
⬅️ Back
</a>
</div>
Expand Down
8 changes: 4 additions & 4 deletions src/routers/app/pages/app.$workspace.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ export const WorkspaceLandingPage: FC<
tenant: TenantRecord;
services: Array<ServiceRecord>;
} & AppContainerProps
> = ({ user, tenants, tenant, services }) => {
> = ({ user, workspace, tenant, services }) => {
return (
<RootDocument title={`${tenant.name}`}>
<AppContainer user={user} tenant={tenant} tenants={tenants} mainClass="grid place-items-center p-2 md:p-4">
<RootDocument title={`${workspace}`}>
<AppContainer user={user} workspace={workspace} mainClass="grid place-items-center p-2 md:p-4">
<Card class="max-w-4xl w-full">
<div class="p-4 grid gap-2">
<div class="flex gap-1">
<a class={getButtonStyles("secondary", "xs")} href={`/app/${tenant.workspace}/edit`}>
<a class={getButtonStyles("secondary", "xs")} href={`/app/${workspace}/edit`}>
✏️ Edit organization
</a>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/routers/app/pages/app.index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { RootDocument } from "../layouts/root-document.js";
import { AppContainer, type AppContainerProps } from "../layouts/app-container.js";
import { Button } from "../components/button.js";

export const NoOrganizationPage: FC<{} & Omit<AppContainerProps, "tenant">> = ({ user, tenants }) => {
export const NoOrganizationPage: FC<{} & Omit<AppContainerProps, "tenant">> = ({ user, workspace }) => {
return (
<RootDocument title="Simple Logging Server">
<AppContainer user={user} tenants={tenants} tenant={null} mainClass="p-2 md:p-4">
<AppContainer user={user} workspace={workspace} mainClass="p-2 md:p-4">
<div class="grid gap-2">
<p>You are not a part of any active organizations.</p>
<p>Create your own organization!.</p>
Expand Down
2 changes: 1 addition & 1 deletion src/server.mts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ app.use(secureHeaders());
app.use(trimTrailingSlash());

const limiter = rateLimiter({
windowMs: 15 * 60 * 1000, // 15 minutes
windowMs: 8 * 60 * 1000, // 8 minutes
limit: 100,
standardHeaders: "draft-6",
keyGenerator: (c) => c.req.header("CF-Connecting-IP") ?? c.req.header("x-forwarded-for") ?? "",
Expand Down

0 comments on commit f716ea3

Please sign in to comment.