Skip to content

Commit

Permalink
Add UI to upgrade the glossary terms
Browse files Browse the repository at this point in the history
  • Loading branch information
sergiodxa committed Feb 24, 2024
1 parent bc937e7 commit fa10b07
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 86 deletions.
8 changes: 8 additions & 0 deletions app/models/glossary.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export class Glossary extends Post<GlossaryMeta> {
return {
...super.toJSON(),
// Glossary Attributes
title: this.title,
slug: this.slug,
term: this.term,
definition: this.definition,
Expand All @@ -75,6 +76,13 @@ export class Glossary extends Post<GlossaryMeta> {
);
}

static override update(services: Services, id: UUID, input: InsertGlossary) {
return Post.update<GlossaryMeta>(services, id, {
...input,
type: "glossary",
});
}

static async search(services: Services, query: string) {
let glossary = await Glossary.list(services);

Expand Down
5 changes: 5 additions & 0 deletions app/routes/_.cms.glossary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { redirect } from "@remix-run/cloudflare";

export function loader() {
return redirect("/cms/glossary/new");
}
82 changes: 0 additions & 82 deletions app/routes/_.cms.glossary/route.tsx

This file was deleted.

168 changes: 168 additions & 0 deletions app/routes/_.cms.glossary_.$id/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import type {
ActionFunctionArgs,
LoaderFunctionArgs,
} from "@remix-run/cloudflare";

import { json, redirect, redirectDocument } from "@remix-run/cloudflare";
import { useLoaderData } from "@remix-run/react";
import { parameterize } from "inflected";
import { z } from "zod";

import { Glossary } from "~/models/glossary.server";
import { Cache } from "~/modules/cache.server";
import { SessionStorage } from "~/modules/session.server";
import { database } from "~/services/db.server";
import { Button } from "~/ui/Button";
import { Form } from "~/ui/Form";
import { TextField } from "~/ui/TextField";
import { Schemas } from "~/utils/schemas";
import { assertUUID } from "~/utils/uuid";

import { INTENT } from "./types";

export async function loader({ request, params, context }: LoaderFunctionArgs) {
await SessionStorage.requireUser(context, request);

if (params.id === "new") {
return json({
mode: INTENT.create,
glossary: {
id: null,
title: "",
term: "",
definition: "",
slug: "",
},
});
}

let db = database(context.db);
assertUUID(params.id);

let glossary = await Glossary.show({ db }, params.id);

return json({
mode: INTENT.update,
glossary: {
id: glossary.id,
title: glossary.title,
term: glossary.term,
definition: glossary.definition,
slug: glossary.slug,
},
});
}

export async function action({ request, params, context }: ActionFunctionArgs) {
let user = await SessionStorage.requireUser(context, request, "/auth/login");

let formData = await request.formData();

let intent = formData.get("intent");

if (intent === INTENT.create) {
let { term, title, definition } = Schemas.formData()
.pipe(
z.object({
term: z.string(),
title: z.string().optional(),
definition: z.string(),
}),
)
.parse(formData);

let slug = parameterize(term);

let db = database(context.db);

await Glossary.create(
{ db },
{ authorId: user.id, slug, term, title, definition },
);

let cache = new Cache.KVStore(context.kv.cache, context.waitUntil);
let cacheKey = await cache.list("feed:glossary:");
await Promise.all([
cache.delete("feed:glossary"),
await Promise.all(cacheKey.map((key) => cache.delete(key))),
]);

throw redirectDocument(`/glossary#${slug}`);
}

if (intent === INTENT.update) {
let { term, title, definition, slug } = Schemas.formData()
.pipe(
z.object({
term: z.string(),
title: z.string().optional(),
definition: z.string(),
slug: z.string(),
}),
)
.parse(formData);

let db = database(context.db);

let id = params.id;
assertUUID(id);

await Glossary.update({ db }, id, {
authorId: user.id,
term,
title,
definition,
slug,
});

let cache = new Cache.KVStore(context.kv.cache, context.waitUntil);
let cacheKey = await cache.list("feed:glossary:");
await Promise.all([
cache.delete("feed:glossary"),
await Promise.all(cacheKey.map((key) => cache.delete(key))),
]);

throw redirectDocument(`/glossary#${slug}`);
}

throw redirect("/glossary");
}

export default function Component() {
let { mode, glossary } = useLoaderData<typeof loader>();

return (
<>
<header className="flex justify-between">
<h2 className="text-3xl font-bold">Glossary</h2>
</header>

<Form method="post" className="max-w-xs">
<input type="hidden" name="intent" value={mode} />
<TextField
label="Term"
name="term"
isRequired
defaultValue={glossary.term}
/>

{mode === INTENT.update && (
<TextField label="Slug" name="slug" defaultValue={glossary.slug} />
)}

<TextField label="Title" name="title" defaultValue={glossary.title} />
<TextField
type="textarea"
label="Definition"
name="definition"
isRequired
defaultValue={glossary.definition}
/>

<Button type="submit" variant="primary">
Create
</Button>
</Form>
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const INTENT = {
create: "CREATE_GLOSSARY_TERM" as const,
update: "UPDATE_GLOSSARY_TERM" as const,
};
25 changes: 21 additions & 4 deletions app/routes/_.glossary/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,32 @@ export default function Component() {
</div>

<dl className="flex flex-col">
{glossary.map(({ id, slug, term, definition }) => (
{glossary.map(({ id, slug, title, term, definition }) => (
<div
key={id}
id={slug}
className="py-4 target:scroll-m-4 target:rounded-md target:border-2 target:border-zinc-500 target:border-opacity-50 target:bg-zinc-100 target:p-4 target:shadow-md target:dark:border-zinc-400 target:dark:border-opacity-50 target:dark:bg-zinc-800 target:dark:text-zinc-100 target:dark:shadow-none"
>
<dt className="text-xl font-bold">
<a href={`#${slug}`}>{term}</a>
</dt>
<div className="flex flex-col gap-2 sm:flex-row sm:items-baseline sm:justify-between">
<dt className="text-xl font-bold">
<a href={`#${slug}`}>
{term}{" "}
{title ? (
<small className="text-sm text-zinc-700 dark:text-zinc-400">
(aka {title})
</small>
) : null}
</a>
</dt>

{user?.role === "admin" && (
<Form method="get" action={`/cms/glossary/${id}`}>
<Button type="submit" variant="primary">
Edit
</Button>
</Form>
)}
</div>
<dd>{definition}</dd>
</div>
))}
Expand Down

0 comments on commit fa10b07

Please sign in to comment.