feat: auto toast errors

This commit is contained in:
Derock 2024-01-04 19:45:39 -05:00
parent 60f56e7fc7
commit ab365e4a6c
No known key found for this signature in database
7 changed files with 76 additions and 78 deletions

View file

@ -76,7 +76,10 @@ export function CreateService() {
<Form {...form}>
<form
onSubmit={form.handleSubmit((data) =>
mutate.mutate({ name: data.name, projectId: project.id }),
mutate.mutate({
name: data.name,
projectId: project.internalName,
}),
)}
>
<FormField

View file

@ -29,9 +29,10 @@ export default function LoginForm() {
router.push("/#");
},
onError: (error) => {
console.error(error);
toast.error(error.message, { id: toastLoading });
trpc: {
context: {
toastId: toastLoading,
},
},
});
@ -95,7 +96,7 @@ export default function LoginForm() {
className="w-full"
type="submit"
form="loginform"
isLoading={login.isLoading}
isLoading={login.isPending}
>
Log In
</Button>

View file

@ -1,5 +1,10 @@
import { redirect } from "next/navigation";
import { api } from "~/trpc/server";
import LoginForm from "./Login";
export default function LoginPage() {
export default async function LoginPage() {
const loggedIn = await api.auth.me.query().catch(() => null);
if (loggedIn) return redirect("/");
return <LoginForm />;
}

View file

@ -176,6 +176,8 @@ export const authenticatedProcedure = t.procedure.use(
if (!ctx.session) {
throw new TRPCError({
code: "UNAUTHORIZED",
// NOTE: if you change this, you must also change it in ~/trpc/react.ts
cause: new Error("User is not logged in."),
message: "You must be logged in to perform this action.",
});
}
@ -187,57 +189,3 @@ export const authenticatedProcedure = t.procedure.use(
});
}),
);
/**
* Project-related procedures
*
* Abstracts away finding a project by ID or internal name, and returns the project object.
* REMINDER: if you override `input`, you must include `projectId` in the new input object.
*/
// export const projectAuthenticatedProcedure = authenticatedProcedure
// .use(
// t.middleware(async ({ ctx, input, next }) => {
// if (
// !input ||
// typeof input != "object" ||
// !("projectId" in input) ||
// typeof input.projectId != "string"
// ) {
// console.log(input);
// throw new TRPCError({
// code: "NOT_FOUND",
// message: "Expected a project ID or internal name.",
// });
// }
// const [project] = await ctx.db
// .select({
// id: projects.id,
// friendlyName: projects.friendlyName,
// internalName: projects.internalName,
// createdAt: projects.createdAt,
// })
// .from(projects)
// .where(
// or(
// eq(projects.id, input.projectId),
// eq(projects.internalName, input.projectId),
// ),
// )
// .limit(1);
// if (!project)
// throw new TRPCError({
// code: "NOT_FOUND",
// message: "Project not found or insufficient permissions.",
// });
// return next({
// ctx: {
// project: project,
// },
// });
// }),
// )
// .input(z.object({ projectId: z.string() }));

View file

@ -1,8 +1,7 @@
import { drizzle } from "drizzle-orm/better-sqlite3";
import SQLite3 from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import { join } from "path";
import { env } from "~/env";
import logger from "../utils/logger";
const globalForDB = globalThis as unknown as {
db: ReturnType<typeof createDatabaseInstance>;
@ -24,6 +23,7 @@ function createDatabaseInstance() {
"./exts/sqlite-uuidv7",
),
);
return drizzle(sqlite);
}

View file

@ -1,7 +1,8 @@
import { type TRPCLink } from "@trpc/client";
import { observable } from "@trpc/server/observable";
import { type AppRouter } from "~/server/api/root";
import { httpLink } from "@trpc/client/links/httpLink";
import { observable, tap } from "@trpc/server/observable";
import { toast } from "sonner";
import { type AppRouter } from "~/server/api/root";
type LinkOptions = Parameters<typeof httpLink>[0];
@ -23,3 +24,27 @@ export const authLink: (opts: LinkOptions) => TRPCLink<AppRouter> = (opts) => {
});
};
};
/**
* Logs errors to a toast
*/
export const toastLink: TRPCLink<AppRouter> = () => {
return ({ next, op }) => {
return observable((observer) => {
return next(op)
.pipe(
tap({
// next(result) {},
error(result) {
if (op.context?.hideErrors) return;
toast.error(result.message, {
id: op.context?.toastId as string | undefined,
});
},
}),
)
.subscribe(observer);
});
};
};

View file

@ -2,6 +2,7 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {
TRPCClientError,
createWSClient,
loggerLink,
splitLink,
@ -12,8 +13,9 @@ import {
import { createTRPCReact } from "@trpc/react-query";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { type AppRouter } from "~/server/api/root";
import { authLink } from "./links";
import { authLink, toastLink } from "./links";
import { getUrl, transformer } from "./shared";
export const api = createTRPCReact<AppRouter>();
@ -40,7 +42,32 @@ export function TRPCReactProvider(props: {
children: React.ReactNode;
cookies: string;
}) {
const [queryClient] = useState(() => new QueryClient());
const router = useRouter();
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
retry: (failureCount, error) => {
if (!(error instanceof TRPCClientError)) return false;
// check if is an unauthorized error
if (
error.cause &&
error.cause.message === "User is not logged in."
) {
router.push("/login");
return false;
}
// retry 3 times, then give up
return failureCount < 3;
},
},
},
}),
);
const [trpcClient] = useState(() =>
api.createClient({
@ -52,6 +79,7 @@ export function TRPCReactProvider(props: {
(op.direction === "down" && op.result instanceof Error),
}),
toastLink,
authLink(sharedLinkOptions(props.cookies)),
splitLink({
@ -59,9 +87,6 @@ export function TRPCReactProvider(props: {
return op.type === "subscription";
},
// true: httpLink(sharedLinkOptions(props.cookies)),
// false: unstable_httpBatchStreamLink(sharedLinkOptions(props.cookies)),
true: wsLink({
client: createWSClient({
url: getUrl().replace("http", "ws"),
@ -74,15 +99,6 @@ export function TRPCReactProvider(props: {
false: unstable_httpBatchStreamLink(sharedLinkOptions(props.cookies)),
}),
// authLink(sharedLinkOptions(props.cookies)),
// wsLink({
// client: createWSClient({
// url: getUrl().replace("http", "ws"),
// }),
// }),
// wsLink(sharedLinkOptions(props.cookies)),
// unstable_httpBatchStreamLink(sharedLinkOptions(props.cookies)),
],
}),
);