From f9f0f3e73a19842ee476bbcd6d101a6744275898 Mon Sep 17 00:00:00 2001 From: bricked Date: Tue, 8 Oct 2024 23:29:08 +0200 Subject: [PATCH] feat: add account settings --- .npmrc | 1 + src/actions/index.ts | 4 ++++ src/actions/logout.ts | 14 +++++++++++ src/actions/settings.ts | 33 ++++++++++++++++++++++++++ src/actions/signup.ts | 14 +---------- src/comps/forms/Button.astro | 1 + src/comps/forms/Input.astro | 8 +------ src/lib/schema/index.ts | 2 ++ src/pages/account/index.astro | 7 ++---- src/pages/account/settings.astro | 40 ++++++++++++++++++++++++++++++++ 10 files changed, 99 insertions(+), 25 deletions(-) create mode 100644 .npmrc create mode 100644 src/actions/logout.ts create mode 100644 src/actions/settings.ts create mode 100644 src/pages/account/settings.astro diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..21cf150 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +npm config set legacy-peer-deps true diff --git a/src/actions/index.ts b/src/actions/index.ts index f03101c..602f7fd 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -1,7 +1,11 @@ import { login } from "./login"; import { signup } from "./signup"; +import { settings } from "./settings"; +import { logout } from "./logout"; export const server = { login, signup, + settings, + logout, }; diff --git a/src/actions/logout.ts b/src/actions/logout.ts new file mode 100644 index 0000000..8a29007 --- /dev/null +++ b/src/actions/logout.ts @@ -0,0 +1,14 @@ +import { ActionError, defineAction } from "astro:actions"; +import { lucia } from "@lib/auth/index"; + +export const logout = defineAction({ + accept: "form", + handler: async (_, { locals: {user, session}}) => { + if (!user || !session) throw new ActionError({ + code: "UNAUTHORIZED", + message: "You must be logged in to log out." + }) + + await lucia.invalidateSession(session.id); + }, +}); diff --git a/src/actions/settings.ts b/src/actions/settings.ts new file mode 100644 index 0000000..93806aa --- /dev/null +++ b/src/actions/settings.ts @@ -0,0 +1,33 @@ +import { ActionError, defineAction } from "astro:actions"; +import { z } from "astro:schema"; +import { emptyString, slug } from "@lib/schema"; +import { db, users } from "@lib/db"; +import { hashOptions } from "@lib/auth/index"; +import { hash } from "@node-rs/argon2"; +import { eq } from "drizzle-orm"; + +export const settings = defineAction({ + accept: "form", + input: z.object({ + name: emptyString().nullable().or(z.string()), + slug: emptyString().nullable().or(slug()), + password: emptyString().nullable().or(z.string()), + }), + handler: async ({name, slug, password}, {locals: {user}}) => { + if (!user) + throw new ActionError({ + code: "UNAUTHORIZED", + message: "You need to be logged in to edit your preferences." + }) + + if (name == user.name) name = null; + if (slug == user.slug) slug = null; + if (!(name || slug || password)) return; + + await db.update(users).set({ + name: name ?? undefined, + slug: slug ?? undefined, + password: password ? await hash(password, hashOptions) : undefined, + }).where(eq(users.id, user.id)) + }, +}); diff --git a/src/actions/signup.ts b/src/actions/signup.ts index 87669f8..2504d18 100644 --- a/src/actions/signup.ts +++ b/src/actions/signup.ts @@ -1,11 +1,10 @@ -import { ActionError, defineAction } from "astro:actions"; +import { defineAction } from "astro:actions"; import { z } from "astro:schema"; import { slug } from "@lib/schema"; import { db, users } from "@lib/db"; import { lucia, hashOptions } from "@lib/auth/index"; import { hash } from "@node-rs/argon2"; import { generateIdFromEntropySize } from "lucia"; -import { eq } from "drizzle-orm"; export const signup = defineAction({ accept: "form", @@ -15,17 +14,6 @@ export const signup = defineAction({ password: z.string(), }), handler: async ({name, slug, password}, {cookies}) => { - const [existingUser] = await db - .select() - .from(users) - .where(eq(users.slug, slug)); - - if (existingUser) - throw new ActionError({ - code: "BAD_REQUEST", - message: "The provided slug already exists.", - }); - const id = generateIdFromEntropySize(10); await db.insert(users).values({ id, diff --git a/src/comps/forms/Button.astro b/src/comps/forms/Button.astro index 7022cb6..0cdcbd0 100644 --- a/src/comps/forms/Button.astro +++ b/src/comps/forms/Button.astro @@ -11,6 +11,7 @@ type Props = astroHTML.JSX.ButtonHTMLAttributes; background: #eee; border-radius: 0.5em; text-align: center; + transition: background 0.2s; } button:hover { cursor: pointer; diff --git a/src/comps/forms/Input.astro b/src/comps/forms/Input.astro index c4b1303..3f190a9 100644 --- a/src/comps/forms/Input.astro +++ b/src/comps/forms/Input.astro @@ -5,13 +5,7 @@ interface Props extends astroHTML.JSX.InputHTMLAttributes { id?: string; } -const { - name, - id = `${name}-input`, - label, - placeholder = "Click to type...", - type, -} = Astro.props; +const { name, id = `${name}-input`, label, placeholder, type } = Astro.props; ---
diff --git a/src/lib/schema/index.ts b/src/lib/schema/index.ts index 60d90f7..f3147b6 100644 --- a/src/lib/schema/index.ts +++ b/src/lib/schema/index.ts @@ -2,6 +2,8 @@ import { z } from "astro:schema"; import slugify from "@sindresorhus/slugify"; import zxcvbn from "zxcvbn"; +export const emptyString = () => z.literal("").transform(() => null); + export const slug = () => z.preprocess( (val) => slugify(z.string().parse(val)), diff --git a/src/pages/account/index.astro b/src/pages/account/index.astro index 93b4321..b630e3f 100644 --- a/src/pages/account/index.astro +++ b/src/pages/account/index.astro @@ -1,8 +1,5 @@ --- -import Layout from "@layouts/Layout.astro"; - const { user } = Astro.locals; -if (!user) return Astro.redirect("/account/signup"); +if (user) return Astro.redirect("/account/settings"); +else return Astro.redirect("/account/signup"); --- - -Welcome, {user.name}! diff --git a/src/pages/account/settings.astro b/src/pages/account/settings.astro new file mode 100644 index 0000000..f304477 --- /dev/null +++ b/src/pages/account/settings.astro @@ -0,0 +1,40 @@ +--- +import Layout from "@layouts/Layout.astro"; +import Form from "@comps/forms/Form.astro"; +import Input from "@comps/forms/Input.astro"; +import Button from "@comps/forms/Button.astro"; +import { actions } from "astro:actions"; + +const { user } = Astro.locals; +if (!user) return Astro.redirect("/account/signup"); +--- + + +
+ + + + +
+

Account Management

+
+ +
+
+ +