feat: auto toast errors
This commit is contained in:
parent
60f56e7fc7
commit
ab365e4a6c
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 />;
|
||||
}
|
||||
|
|
|
@ -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() }));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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)),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue