WE GOT USER REGISTRATION WORKING!!!

Changed files
+191 -34
db
src
+3 -1
db/seed.ts
···
]);
await db.insert(Works).values([
-
{
+
{
+
slug: "1234",
author: "test",
title: "Hey there title",
content: "<p>i have evil html</p>",
tags: [{ label: "test", url: "#" }],
},
{
+
slug: "1235",
author: "another",
title: "Hello world",
content: "<p>whoag i have <b>BOLD</b></p>",
+2
src/actions/index.ts
···
+
import { usersActions } from "./users";
import { worksActions } from "./works";
export const server = {
+
usersActions,
worksActions,
};
+7
src/actions/works.ts
···
import { ActionError, defineAction } from "astro:actions";
import { z } from "astro:content";
import { db, eq, Users, Works } from "astro:db";
+
import { customAlphabet } from "nanoid";
const workSchema = z.object({
title: z.string(),
···
eq(Users.userDid, context.locals.loggedInUser.did)
);
+
// check nanoid for collision probability: https://zelark.github.io/nano-id-cc/
+
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
+
const nanoid = customAlphabet(alphabet, 16);
+
const slug = nanoid();
+
const work = await db.insert(Works).values({
+
slug,
author: userId[0].did,
title: input.title,
content: input.content,
+2 -2
src/assets/styles/base.css
···
--space-2xl-3xl: clamp(4.5rem, 3.2727rem + 5.4545cqi, 7.5rem);
/* font tokens */
-
--serif: var(--plex-serif);
--sans: var(--atkinson);
+
--serif: var(--plex-serif);
--mono: var(--plex-mono);
-
+
--body: var(--serif);
}
+15
src/components/Navbar.astro
···
+
---
+
const LINKS = [
+
{ label: "Home", url: "/" },
+
{ label: "Works", url: "/works" },
+
{ label: "Login", url: "/login" },
+
{ label: "Settings", url: "/user" },
+
];
+
---
+
<nav id="main-nav">
+
<ul>
+
{LINKS.map(({ label, url }) => (
+
<li><a href={url}>{label}</a></li>
+
))}
+
</ul>
+
</nav>
+32 -3
src/layouts/Layout.astro
···
---
+
import "$/styles/base.css";
import { Font } from "astro:assets";
-
import "$/styles/base.css";
+
import Navbar from "~/Navbar.astro";
+
+
interface Props {
+
title?: string;
+
skipLink?: string;
+
}
-
const { title = "fanfics" } = Astro.props;
+
const { title = "fanfics", skipLink } = Astro.props;
---
<html lang="en">
<head>
···
<slot name="head" />
</head>
<body>
+
<header>
+
{skipLink && (
+
<a class="skip-link" href={`#${skipLink}`}>Skip to content</a>
+
)}
+
+
<Navbar />
+
</header>
+
<slot />
+
+
<footer>
+
copyright or something ig
+
</footer>
</body>
-
</html>
+
</html>
+
+
<style>
+
.skip-link {
+
position: absolute;
+
left: calc(max-content * -1);
+
}
+
+
footer {
+
margin-top: auto;
+
}
+
</style>
src/layouts/Work.astro src/layouts/WorkPage.astro
+5 -5
src/lib/types.ts
···
export interface Work {
-
id: number;
+
slug: string;
title: string;
-
author: number | string;
-
tags: Array<Tag>;
+
author: string;
+
tags: Tag[];
content: string;
-
createdAt: Date | string;
-
updatedAt: Date | string | undefined;
+
createdAt: Date;
+
updatedAt: Date | undefined;
}
export interface Tag {
+8 -11
src/pages/index.astro
···
---
import Layout from "../layouts/Layout.astro";
-
import Settings from "~/Settings.astro";
const currentUser = Astro.locals.loggedInUser;
---
-
<Layout>
-
<h1>hi</h1>
-
<main>
+
<Layout skipLink="content">
+
<main id="content">
+
<h1>hi</h1>
<p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Praesentium eum est quisquam distinctio magni recusandae quia vero tempore consectetur! Dolore repellat, voluptatem dignissimos sit eaque iste atque facilis in saepe?</p>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorem, maxime libero eveniet repellat corporis, architecto voluptate maiores ullam accusamus quasi nostrum nihil placeat cum earum ex voluptatum, harum sunt quam!</p>
</main>
-
<Settings />
-
{currentUser
? <>
-
<p>you're logged in!</p>
-
<form action="/oauth/logout" method="post">
-
<button>logout</button>
-
</form>
-
</>
+
<p>you're logged in!</p>
+
<form action="/oauth/logout" method="post">
+
<button>logout</button>
+
</form>
+
</>
: <a href="/login">login</a>}
</Layout>
+8
src/pages/not-found.astro
···
+
---
+
import Layout from "../layouts/Layout.astro"
+
---
+
<Layout>
+
<h1>Not found!</h1>
+
<p>There's a chance that the resource you're looking for doesn't exist or there was a typo in the URL.</p>
+
<p>You can go back to the <a href="/">home page</a> or report the issue through the contact form.</p>
+
</Layout>
+22
src/pages/user/[id].astro
···
+
---
+
import { db, eq, Users, Works } from "astro:db";
+
+
const { id } = Astro.params;
+
+
const user = await db.select()
+
.from(Users)
+
.where(eq(Users.userDid, id!))
+
.innerJoin(Works, eq(Works.author, Users.userDid))
+
.limit(1);
+
+
if (user.length === 0) {
+
return Astro.redirect("/not-found");
+
}
+
---
+
+
{user.map(({ Users, Works }) => (
+
<>
+
<h1>{Users.userDid}</h1>
+
{JSON.stringify(Works)}
+
</>
+
))}
+72
src/pages/user/index.astro
···
+
---
+
import Layout from "@/layouts/Layout.astro";
+
import { actions } from "astro:actions";
+
import { db, eq, Users, Works } from "astro:db";
+
import Dialog from "~/Dialog.astro";
+
+
const loggedInUser = Astro.locals.loggedInUser;
+
+
const user = await db.select()
+
.from(Users)
+
.where(eq(Users.userDid, loggedInUser?.did ?? ""))
+
.fullJoin(Works, eq(Users.userDid, Works.author));
+
+
if (!loggedInUser) {
+
return Astro.redirect("/login");
+
}
+
---
+
<Layout>
+
<h1>User Settings</h1>
+
<p>{loggedInUser?.handle}</p>
+
+
<!-- registration will only happen in the below form! -->
+
{!user && (
+
<>
+
<h2>Connect account</h2>
+
<div class="info">
+
<p>Right now, you aren't connected to the site. You can connect your BlueSky / self-hosted PDS account to this website to post a work.</p>
+
<p>Please check out the Terms of Service, Privacy Policy, and Code of Conduct before connecting your account.</p>
+
</div>
+
<button id="trigger-confirm">Connect your PDS Account</button>
+
+
<Dialog id="connect-account" title="Are you sure?">
+
<form action={actions.usersActions.addUser} method="post">
+
<input type="hidden" name="did" value={loggedInUser.did} />
+
+
<button formmethod="dialog">Cancel</button>
+
<button>Confirm</button>
+
</form>
+
</Dialog>
+
</>
+
)}
+
+
<!-- this is weird but this should ONLY render if the user is registered -->
+
{user.map(({ Users, Works }) => {
+
if (Users) {
+
return (
+
<>
+
<time datetime={Users.joinedAt.toISOString()}>{Users.joinedAt}</time>
+
<p>{Users.userDid}</p>
+
+
{Works
+
? <section>
+
<ul>
+
{JSON.stringify(Works)}
+
</ul>
+
</section>
+
: <a href="/works/add">go write! if you want :)</a>
+
}
+
</>
+
)
+
}
+
})}
+
</Layout>
+
+
<script>
+
const trigger = document.getElementById("trigger-confirm");
+
const confirmDialog = document.getElementById("connect-account") as HTMLDialogElement;
+
+
trigger?.addEventListener("click", (_) => {
+
confirmDialog.showModal();
+
});
+
</script>
+14 -11
src/pages/works/[id].astro
···
const { id } = Astro.params;
-
const works = await db.select()
+
const work = await db.select()
.from(Works)
-
.where(eq(Works.id, Number(id)))
+
.where(eq(Works.slug, id!))
.innerJoin(Users, eq(Works.author, Users.userDid))
.limit(1);
+
if (work.length === 0) {
+
return Astro.redirect("/not-found");
+
}
---
<Layout>
-
{works.map(({ Works, Users }) => (
-
<h1>{Works.title}</h1>
-
<h2>{Users.userDid}</h2>
-
<time datetime={Works.createdAt.toISOString()}>{Works.createdAt}</time>
-
<ul>
-
{(Works.tags as unknown as Tag[]).map(tag => (
-
<li><a href={tag.url}>{tag.label}</a></li>
+
{work.map(({ Works, Users }) => (
+
<>
+
<h1>{Works.title}</h1>
+
<h2>{Users.userDid}</h2>
+
<time datetime={Works.createdAt.toISOString()}>{Works.createdAt}</time>
+
{(Works.tags as Tag[]).map(tag => (
+
<a href={tag.url}>{tag.label}</a>
))}
-
</ul>
-
<Fragment set:html={Works.content} />
+
<Fragment set:html={Works.content} />
+
</>
))}
</Layout>
+1 -1
src/pages/works/index.astro
···
{works.map(({ Works, Users }) => (
<article>
-
<h2>{Works.title}</h2>
+
<h2><a href={`/works/${Works.slug}`}>{Works.title}</a></h2>
<h3>{Users.userDid}</h3>
<time datetime={Works.createdAt.toISOString()}>{Works.createdAt}</time>
{(Works.tags as Tag[]).map((tag: Tag) => (