Skip to content

Commit

Permalink
account settings + layout overhaul
Browse files Browse the repository at this point in the history
  • Loading branch information
oscartbeaumont committed Sep 18, 2024
1 parent fdbec8f commit 5524eb7
Show file tree
Hide file tree
Showing 28 changed files with 366 additions and 2,092 deletions.
1 change: 0 additions & 1 deletion apps/landing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"@mattrax/ui": "workspace:*",
"@solidjs/router": "0.14.5",
"@solidjs/start": "1.0.6",
"@tanstack/solid-form": "^0.32.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"h3": "^1.12.0",
Expand Down
38 changes: 16 additions & 22 deletions apps/landing/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@ import {
SelectTrigger,
SelectValue,
} from "@mattrax/ui";
import {
Form,
InputField,
SelectField,
createZodForm,
} from "@mattrax/ui/forms/legacy";
import { Form, InputField, SelectField, createForm } from "@mattrax/ui/forms";
import { useController } from "@mattrax/ui/lib";
import type { JSX } from "solid-js";
import { createEffect, type JSX } from "solid-js";
import { z } from "zod";
import Bento from "~/components/Bento";
import Cta from "~/components/Cta";
Expand Down Expand Up @@ -159,19 +154,19 @@ const deploymentMethod = {
other: "Other",
} as const;

const schema = z.object({
email: z.string().email(),
name: z.string().optional(),
interest: zodEnumFromObjectKeys(interestReasons).default("personal"),
deployment: zodEnumFromObjectKeys(deploymentMethod).default("managed-cloud"),
});

function DropdownBody() {
const controller = useController();

const schema = z.object({
email: z.string().email(),
name: z.string().optional(),
interest: zodEnumFromObjectKeys(interestReasons),
deployment: zodEnumFromObjectKeys(deploymentMethod),
});

const form = createZodForm(() => ({
schema,
onSubmit: async ({ value }) => {
const form = createForm({
schema: () => schema,
onSubmit: async (value) => {
// This endpoint is defined in Nitro and proxies to `cloud.mattrax.app` so we can avoid CORS
const resp = await fetch("/api/waitlist", {
method: "POST",
Expand All @@ -194,11 +189,10 @@ function DropdownBody() {

controller.setOpen(false);
},
}));
});

// `state().isValid` seems to be always `true` (probs cause `createZodForm` only does validation on submit) // TODO: Maybe fix this properly?
const isFormValid = form.useStore(
(state) => schema.safeParse(state.values).success,
createEffect(() =>
console.log(Object.values(form.fields).map((v) => v.meta.error)),
);

return (
Expand Down Expand Up @@ -251,7 +245,7 @@ function DropdownBody() {
<SelectContent />
</SelectField>

<Button type="submit" disabled={!isFormValid()}>
<Button type="submit" disabled={!form.isValid}>
Submit
</Button>
</Form>
Expand Down
1 change: 0 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
"@solidjs/router": "0.14.5",
"@solidjs/start": "1.0.6",
"@t3-oss/env-core": "^0.11.1",
"@tanstack/solid-form": "^0.32.0",
"@tanstack/solid-query": "^5.56.2",
"@tanstack/solid-query-devtools": "^5.56.2",
"@tanstack/solid-query-persist-client": "^5.56.2",
Expand Down
11 changes: 2 additions & 9 deletions apps/web/src/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,7 @@ export const lucia = withAuth((domain) => {
email: data.email,
name: data.name,
}),
getSessionAttributes: (data) => ({
userAgent: data.userAgent,
location: data.location,
}),
getSessionAttributes: (data) => ({}),
});
});

Expand All @@ -53,11 +50,7 @@ export interface DatabaseUserAttributes {
name: string;
}

interface DatabaseSessionAttributes {
// Web or CLI session
userAgent: `${"w" | "c"}${string}`;
location: string;
}
type DatabaseSessionAttributes = Record<string, never>;

export const checkAuth = cache(async () => {
"use server";
Expand Down
31 changes: 20 additions & 11 deletions apps/web/src/api/trpc/routers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
getTenantList,
publicProcedure,
} from "../helpers";
import { sendDiscordMessage } from "./meta";
import { env } from "~/env";

async function mapAccount(account: DatabaseUserAttributes) {
// await new Promise((resolve) => setTimeout(resolve, 10000)); // TODO
Expand Down Expand Up @@ -171,6 +173,7 @@ export const authRouter = createTRPCRouter({
}
}),

// `authedProcedure` implies `flushResponse` and we need manual control for the cookies!
logout: publicProcedure.mutation(async () => {
const data = await checkAuth();
if (!data) throw new TRPCError({ code: "UNAUTHORIZED" });
Expand All @@ -183,22 +186,28 @@ export const authRouter = createTRPCRouter({
flushResponse();
}),

// delete: authedProcedure.mutation(async ({ ctx }) => {
// const session = ctx.session.data;
// `authedProcedure` implies `flushResponse` and we need manual control for the cookies!
delete: publicProcedure.mutation(async ({ ctx }) => {
const data = await checkAuth();
if (!data) throw new TRPCError({ code: "UNAUTHORIZED" });

// // TODO: Require the user to leave/delete all tenant's first
// TODO: Require the user to leave/delete all tenant's first

// await ctx.db.delete(accounts).where(eq(accounts.id, session.id));
// await ctx.session.clear();
// return {};
// }),
// TODO: Implement this properly
await sendDiscordMessage(
`User \`${data.account.id}\` with email \`${data.account.email}\` requested account deletion!`,
env.DO_THE_THING_WEBHOOK_URL,
);

deleteCookie(lucia.sessionCookieName);
deleteCookie("isLoggedIn");
await lucia.invalidateSession(data.session.id);
flushResponse();
}),
});

export async function handleLoginSuccess(accountId: string) {
const session = await lucia.createSession(accountId, {
userAgent: `w${"web"}`, // TODO
location: "earth", // TODO
});
const session = await lucia.createSession(accountId, {});

appendResponseHeader(
"Set-Cookie",
Expand Down
18 changes: 16 additions & 2 deletions apps/web/src/app/(dash).tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getPlatformShortcut, Kbd } from "@mattrax/ui";
import { Navigate } from "@solidjs/router";
import { parse } from "cookie-es";
import { Suspense, type ParentProps, ErrorBoundary } from "solid-js";
Expand Down Expand Up @@ -47,7 +48,6 @@ export default function (props: ParentProps) {
</SidebarHeader>
<SidebarContent>
<SidebarItem>
{/* <SidebarLabel>Platform</SidebarLabel> */}
<Navigation
tenantId={params.tenantId}
disabled={
Expand All @@ -58,13 +58,27 @@ export default function (props: ParentProps) {
<SidebarItem class="mt-auto">
<SidebarLabel>Other</SidebarLabel>
<OtherNavigation />
<div class="relative flex items-center">
<button
type="button"
class="bg-zinc-50 min-w-8 flex h-8 flex-1 items-center gap-2 overflow-hidden rounded-md px-1.5 text-sm text-zinc-500 font-medium outline-none ring-zinc-950 transition-all hover:bg-zinc-100 hover:text-zinc-900 focus-visible:ring-2 dark:ring-zinc-300 dark:hover:bg-zinc-800 dark:hover:text-zinc-50 disabled:pointer-events-none disabled:opacity-50"
disabled
>
<IconPhMagnifyingGlass class="h-4 w-4 shrink-0" />
<div class="flex flex-1 overflow-hidden">
<div class="line-clamp-1 pr-6">Search</div>
</div>

<Kbd variant="light">{getPlatformShortcut()} K</Kbd>
</button>
</div>
</SidebarItem>
</SidebarContent>
<SidebarFooter>
<Footer />
</SidebarFooter>
</Sidebar>
<div class="p-4 min-h-screen h-screen overflow-auto">
<div class="min-h-screen h-screen overflow-auto">
<ErrorBoundary fallback={ErrorScreen}>
<Suspense>{props.children}</Suspense>
</ErrorBoundary>
Expand Down
168 changes: 106 additions & 62 deletions apps/web/src/app/(dash)/account.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,117 @@
import { BreadcrumbItem } from "@mattrax/ui";
import {
BreadcrumbItem,
Button,
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@mattrax/ui";
import { createForm, Form, InputField } from "@mattrax/ui/forms";
import { z } from "zod";
import { ConfirmDialog } from "~/components/ConfirmDialog";
import { Page } from "~/components/Page";
import { trpc } from "~/lib";
import { useAccount } from "~/lib/data";

export default function () {
const account = useAccount();

const ctx = trpc.useContext();
const updateAccount = trpc.auth.update.createMutation(() => ({
// TODO: dependant queries
onSuccess: () => ctx.auth.me.invalidate(),
}));
const deleteAccount = trpc.auth.delete.createMutation(() => ({
onSuccess: () => {
localStorage.clear();
window.location.assign("/login");
},
}));

const form = createForm({
schema: () =>
z.object({
name: z.string().default(account.data?.name || ""),
email: z.string().default(account.data?.email || ""),
}),
onSubmit: (data) => updateAccount.mutateAsync(data),
});

return (
<Page
title="Account"
breadcrumbs={[<BreadcrumbItem>Settings</BreadcrumbItem>]}
class="max-w-4xl flex flex-col space-y-6"
// hideSearch
>
TODO
<Card>
<CardHeader>
<CardTitle>Account settings</CardTitle>
<CardDescription>
Manage your account settings and preferences
</CardDescription>
</CardHeader>
<CardContent>
<Form form={form} fieldsetClass="flex flex-col space-y-6">
<InputField form={form} name="name" label="Name" />
<InputField form={form} name="email" label="Email" disabled />
</Form>
</CardContent>
<CardFooter class="!px-6 !py-3 border-t">
<Button disabled={form.isSubmitting} onClick={() => form.onSubmit()}>
Save
</Button>
</CardFooter>
</Card>

<Card class="!border-red-200">
<CardHeader>
<CardTitle>Delete account</CardTitle>
<CardDescription>
Permanently remove your account and all related data! This action is
not reversible, so please take care.
</CardDescription>
</CardHeader>
<CardFooter class="flex items-center justify-center !px-6 !py-3 rounded-b-xl !bg-red-100/50">
<div class="flex-1" />
<ConfirmDialog>
{(confirm) => (
<Button
variant="destructive"
disabled={account.isLoading}
onClick={() =>
confirm({
title: "Delete account?",
description: () => (
<>
This will delete all of your account data, along with
any orphaned tenants.{" "}
<span class="text-red-400">
Please be careful as this action is not reversible!
</span>
<br />
<br />
{/* // TODO: Remove this once the backend is implemented properly */}
Be aware it can take up to 24 hours for your account to
be fully deleted. Please avoid logging in during this
time.
</>
),
action: "Delete",
closeButton: null,
inputText: account.data?.email,
onConfirm: async () => deleteAccount.mutateAsync(),
})
}
>
Delete
</Button>
)}
</ConfirmDialog>
</CardFooter>
</Card>
</Page>
);
}

// function ManageAccountDialogContent() {
// const me = trpc.auth.me.createQuery();

// // TODO: Rollback form on failure
// // TODO: Optimistic UI?
// const updateAccount = trpc.auth.update.createMutation(() => ({
// onSuccess: () =>
// toast.success("Account updated", {
// id: "account-updated",
// }),
// // ...withDependantQueries(me), // TODO: Implement
// }));

// // const form = createZodForm(() => ({
// // schema: z.object({ name: z.string(), email: z.string().email() }),
// // // TODO: We should use a function for this so it updates from the server data when the fields aren't dirty.
// // // TODO: Right now this breaks the field focus
// // defaultValues: {
// // name: me.data?.name || "",
// // email: me.data?.email || "",
// // },
// // onSubmit: ({ value }) =>
// // updateAccount.mutateAsync({
// // name: value.name,
// // }),
// // }));

// // const triggerSave = debounce(() => {
// // // TODO: This should probs use the form but it disabled the field which is annoying AF.
// // updateAccount.mutateAsync({
// // name: form.getFieldValue("name"),
// // });
// // }, 500);

// return (
// <DialogContent>
// <DialogHeader>
// <DialogTitle>Manage account</DialogTitle>
// {/* <DialogDescription>
// This action cannot be undone. This will permanently delete your
// account and remove your data from our servers.
// </DialogDescription> */}

// {/* <Input></Input> */}
// {/* <InputField
// fieldClass="col-span-1"
// form={form}
// name="name"
// label="Name"
// onInput={() => triggerSave()}
// /> */}

// {/* // TODO: Change name */}
// {/* // TODO: Change email */}
// {/* // TODO: Delete account */}
// </DialogHeader>
// </DialogContent>
// );
// }
Loading

0 comments on commit 5524eb7

Please sign in to comment.