Skip to content

Commit

Permalink
♻️ Clean up trpc code and add global client error handling (#1549)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukevella authored Feb 9, 2025
1 parent 4e603d7 commit 5437b91
Show file tree
Hide file tree
Showing 17 changed files with 164 additions and 160 deletions.
1 change: 0 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
"@tanstack/react-query": "^4.0.0",
"@tanstack/react-table": "^8.9.1",
"@trpc/client": "^10.13.0",
"@trpc/next": "^10.13.0",
"@trpc/react-query": "^10.13.0",
"@trpc/server": "^10.13.0",
"@upstash/qstash": "^2.7.17",
Expand Down
8 changes: 3 additions & 5 deletions apps/web/public/locales/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"emailNotAllowed": "This email is not allowed.",
"emailPlaceholder": "jessie.smith@example.com",
"exportToCsv": "Export to CSV",
"forgetMe": "Forget me",
"guest": "Guest",
"ifNeedBe": "If need be",
"location": "Location",
Expand Down Expand Up @@ -199,9 +198,6 @@
"pollStatusFinalized": "Finalized",
"share": "Share",
"noParticipants": "No participants",
"userId": "User ID",
"aboutGuest": "Guest User",
"aboutGuestDescription": "Profile settings are not available for guest users. <0>Sign in</0> to your existing account or <1>create a new account</1> to customize your profile.",
"logoutDescription": "Sign out of your existing session",
"events": "Events",
"inviteParticipantsDescription": "Copy and share the invite link to start gathering responses from your participants.",
Expand Down Expand Up @@ -305,5 +301,7 @@
"registerVerifyDescription": "Check your email for the verification code",
"loginVerifyTitle": "Finish Logging In",
"loginVerifyDescription": "Check your email for the verification code",
"createAccount": "Create Account"
"createAccount": "Create Account",
"tooManyRequests": "Too many requests",
"tooManyRequestsDescription": "Please try again later."
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
import { useRouter } from "next/navigation";

import { DuplicateForm } from "@/app/[locale]/poll/[urlId]/duplicate-form";
import { trpc } from "@/app/providers";
import { Trans } from "@/components/trans";
import { trpc } from "@/trpc/client";

const formName = "duplicate-form";
export function DuplicateDialog({
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/app/api/trpc/[trpc]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const handler = (req: NextRequest) => {
return {
user,
locale,
ip: ipAddress(req) ?? undefined,
ip:
process.env.NODE_ENV === "development" ? "127.0.0.1" : ipAddress(req),
} satisfies TRPCContext;
},
onError({ error }) {
Expand Down
45 changes: 13 additions & 32 deletions apps/web/src/app/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,32 @@
"use client";
import { PostHogProvider } from "@rallly/posthog/client";
import { TooltipProvider } from "@rallly/ui/tooltip";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createTRPCReact } from "@trpc/react-query";
import { domMax, LazyMotion } from "framer-motion";
import { useState } from "react";

import { UserProvider } from "@/components/user-provider";
import { I18nProvider } from "@/i18n/client";
import { trpcConfig } from "@/trpc/client/config";
import type { AppRouter } from "@/trpc/routers";
import { TRPCProvider } from "@/trpc/client/provider";
import { ConnectedDayjsProvider } from "@/utils/dayjs";

import { PostHogPageView } from "./posthog-page-view";

export const trpc = createTRPCReact<AppRouter>({
overrides: {
useMutation: {
async onSuccess(opts) {
await opts.originalFn();
await opts.queryClient.invalidateQueries();
},
},
},
});

export function Providers(props: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() => trpc.createClient(trpcConfig));
return (
<LazyMotion features={domMax}>
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
<I18nProvider>
<I18nProvider>
<PostHogProvider>
<PostHogPageView />
<TRPCProvider>
<TooltipProvider>
<PostHogProvider>
<PostHogPageView />
<UserProvider>
<ConnectedDayjsProvider>
{props.children}
</ConnectedDayjsProvider>
</UserProvider>
</PostHogProvider>
<UserProvider>
<ConnectedDayjsProvider>
{props.children}
</ConnectedDayjsProvider>
</UserProvider>
</TooltipProvider>
</I18nProvider>
</QueryClientProvider>
</trpc.Provider>
</TRPCProvider>
</PostHogProvider>
</I18nProvider>
</LazyMotion>
);
}
2 changes: 1 addition & 1 deletion apps/web/src/components/poll/manage-poll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ import Link from "next/link";
import * as React from "react";

import { DuplicateDialog } from "@/app/[locale]/poll/[urlId]/duplicate-dialog";
import { trpc } from "@/app/providers";
import { PayWallDialog } from "@/components/pay-wall-dialog";
import { FinalizePollDialog } from "@/components/poll/manage-poll/finalize-poll-dialog";
import { ProFeatureBadge } from "@/components/pro-feature-badge";
import { Trans } from "@/components/trans";
import { usePlan } from "@/contexts/plan";
import { usePoll } from "@/contexts/poll";
import { trpc } from "@/trpc/client";

import { DeletePollDialog } from "./manage-poll/delete-poll-dialog";
import { useCsvExporter } from "./manage-poll/use-csv-exporter";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ import React from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";

import { trpc } from "@/app/providers";
import { DateIconInner } from "@/components/date-icon";
import { useParticipants } from "@/components/participants-provider";
import { ConnectedScoreSummary } from "@/components/poll/score-summary";
import { VoteSummaryProgressBar } from "@/components/vote-summary-progress-bar";
import { usePoll } from "@/contexts/poll";
import { trpc } from "@/trpc/client";
import { useDayjs } from "@/utils/dayjs";

const formSchema = z.object({
Expand Down
10 changes: 3 additions & 7 deletions apps/web/src/trpc/client.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { createTRPCNext } from "@trpc/next";
import { createTRPCReact } from "@trpc/react-query";

import { trpcConfig } from "@/trpc/client/config";
import type { AppRouter } from "@/trpc/routers";

export const trpc = createTRPCNext<AppRouter>({
config() {
return trpcConfig;
},
unstable_overrides: {
export const trpc = createTRPCReact<AppRouter>({
overrides: {
useMutation: {
async onSuccess(opts) {
await opts.originalFn();
Expand Down
51 changes: 0 additions & 51 deletions apps/web/src/trpc/client/config.ts

This file was deleted.

79 changes: 79 additions & 0 deletions apps/web/src/trpc/client/provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use client";
import { usePostHog } from "@rallly/posthog/client";
import { useToast } from "@rallly/ui/hooks/use-toast";
import {
MutationCache,
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
import { httpBatchLink, TRPCClientError } from "@trpc/client";
import { useState } from "react";
import superjson from "superjson";

import { useTranslation } from "@/i18n/client";

import { trpc } from "../client";

export function TRPCProvider(props: { children: React.ReactNode }) {
const posthog = usePostHog();
const { toast } = useToast();
const { t } = useTranslation();
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
retry: false,
cacheTime: Infinity,
staleTime: 1000 * 60,
},
},
mutationCache: new MutationCache({
onError(error) {
if (error instanceof TRPCClientError) {
posthog.capture("failed api request", {
path: error.data.path,
code: error.data.code,
message: error.message,
});
switch (error.data.code) {
case "UNAUTHORIZED":
window.location.href = "/login";
break;
case "TOO_MANY_REQUESTS":
toast({
title: t("tooManyRequests", {
defaultValue: "Too many requests",
}),
description: t("tooManyRequestsDescription", {
defaultValue: "Please try again later.",
}),
});
break;
default:
console.error(error);
break;
}
}
},
}),
}),
);
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: "/api/trpc",
}),
],
transformer: superjson,
}),
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{props.children}
</QueryClientProvider>
</trpc.Provider>
);
}
4 changes: 2 additions & 2 deletions apps/web/src/trpc/routers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { mergeGuestsIntoUser } from "@/auth/merge-user";
import { getEmailClient } from "@/utils/emails";
import { createToken, decryptToken } from "@/utils/session";

import { publicProcedure, rateLimitMiddleware, router } from "../trpc";
import { createRateLimitMiddleware, publicProcedure, router } from "../trpc";
import type { RegistrationTokenPayload } from "../types";

export const auth = router({
Expand All @@ -29,7 +29,7 @@ export const auth = router({
return { isRegistered: count > 0 };
}),
requestRegistration: publicProcedure
.use(rateLimitMiddleware)
.use(createRateLimitMiddleware(5, "1 m"))
.input(
z.object({
name: z.string().min(1).max(100),
Expand Down
6 changes: 4 additions & 2 deletions apps/web/src/trpc/routers/polls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import { getEmailClient } from "@/utils/emails";

import { getTimeZoneAbbreviation } from "../../utils/date";
import {
createRateLimitMiddleware,
possiblyPublicProcedure,
privateProcedure,
proProcedure,
publicProcedure,
rateLimitMiddleware,
requireUserMiddleware,
router,
} from "../trpc";
Expand Down Expand Up @@ -130,7 +130,7 @@ export const polls = router({

// START LEGACY ROUTES
create: possiblyPublicProcedure
.use(rateLimitMiddleware)
.use(createRateLimitMiddleware(20, "1 h"))
.use(requireUserMiddleware)
.input(
z.object({
Expand Down Expand Up @@ -233,6 +233,7 @@ export const polls = router({
return { id: poll.id };
}),
update: possiblyPublicProcedure
.use(createRateLimitMiddleware(60, "1 h"))
.input(
z.object({
urlId: z.string(),
Expand Down Expand Up @@ -305,6 +306,7 @@ export const polls = router({
});
}),
delete: possiblyPublicProcedure
.use(createRateLimitMiddleware(30, "1 h"))
.input(
z.object({
urlId: z.string(),
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/trpc/routers/polls/comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { getEmailClient } from "@/utils/emails";
import { createToken } from "@/utils/session";

import {
createRateLimitMiddleware,
publicProcedure,
rateLimitMiddleware,
requireUserMiddleware,
router,
} from "../../trpc";
Expand Down Expand Up @@ -72,7 +72,7 @@ export const comments = router({
});
}),
add: publicProcedure
.use(rateLimitMiddleware)
.use(createRateLimitMiddleware(5, "1 m"))
.use(requireUserMiddleware)
.input(
z.object({
Expand Down
Loading

0 comments on commit 5437b91

Please sign in to comment.