Skip to content

Commit

Permalink
feat: add default toast on API error
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbrusegard committed Nov 10, 2024
1 parent 998660a commit b36724f
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 1 deletion.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"react-day-picker": "^9.2.1",
"react-dom": "^19.0.0-rc-fb9a90fa48-20240614",
"reading-time": "^1.5.0",
"sonner": "^1.7.0",
"superjson": "^2.2.1",
"tailwind-merge": "^2.5.4",
"tsparticles": "^3.5.0",
Expand Down
2 changes: 2 additions & 0 deletions src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { RootProviders } from '@/components/providers/RootProviders';
import { Toaster } from '@/components/ui/Toaster';
import { routing } from '@/lib/locale';
import { cx } from '@/lib/utils';
import { getTranslations, setRequestLocale } from 'next-intl/server';
Expand Down Expand Up @@ -84,6 +85,7 @@ export default async function LocaleLayout(props: LocaleLayoutProps) {
<RootProviders locale={locale}>
<div className='scrollbar-thin scrollbar-track-background scrollbar-thumb-primary/40 scrollbar-corner-background scrollbar-thumb-rounded-lg hover:scrollbar-thumb-primary/80 fixed top-0 bottom-0 flex h-full w-full flex-col overflow-y-scroll scroll-smooth'>
{children}
<Toaster />
</div>
</RootProviders>
</body>
Expand Down
49 changes: 49 additions & 0 deletions src/components/ui/Toaster.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
'use client';

import {
CircleCheckIcon,
CircleXIcon,
InfoIcon,
LoaderIcon,
TriangleAlertIcon,
} from 'lucide-react';
import { useTheme } from 'next-themes';
import { Toaster as Sonner, toast } from 'sonner';

import { useMediaQuery } from '@/lib/hooks/useMediaQuery';

type ToasterProps = React.ComponentProps<typeof Sonner>;

const Toaster = ({ ...props }: ToasterProps) => {
const { theme = 'system' } = useTheme();
const isDesktop = useMediaQuery('(min-width: 768px)');

return (
<Sonner
theme={theme as ToasterProps['theme']}
className='toaster group'
position={isDesktop ? 'bottom-right' : 'top-center'}
toastOptions={{
classNames: {
toast:
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
description: 'group-[.toast]:text-muted-foreground',
actionButton:
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
cancelButton:
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
},
}}
icons={{
success: <CircleCheckIcon />,
info: <InfoIcon />,
warning: <TriangleAlertIcon />,
error: <CircleXIcon />,
loading: <LoaderIcon />,
}}
{...props}
/>
);
};

export { Toaster, toast };
27 changes: 27 additions & 0 deletions src/lib/api/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { toast } from '@/components/ui/Toaster';
import type { TRPCClientError } from '@/lib/api/types';

function handleGlobalError(error: Error) {
const TRPCError = error as TRPCClientError;

if (TRPCError.toast) {
switch (TRPCError.toast) {
case 'success':
toast.success(TRPCError.message, { richColors: true });
break;
case 'info':
toast.info(TRPCError.message, { richColors: true });
break;
case 'warning':
toast.warning(TRPCError.message, { richColors: true });
break;
default:
toast.error(TRPCError.message, { richColors: true });
break;
}
} else {
throw error;
}
}

export { handleGlobalError };
10 changes: 10 additions & 0 deletions src/lib/api/queryClient.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import {
MutationCache,
QueryCache,
QueryClient,
defaultShouldDehydrateQuery,
} from '@tanstack/react-query';
import SuperJSON from 'superjson';

import { handleGlobalError } from '@/lib/api/error';

function createQueryClient() {
return new QueryClient({
queryCache: new QueryCache({
onError: handleGlobalError,
}),
mutationCache: new MutationCache({
onError: handleGlobalError,
}),
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
Expand Down
8 changes: 8 additions & 0 deletions src/lib/api/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { router } from '@/server/api';
import type { TRPCClientError as BaseTRPCClientError } from '@trpc/client';

type TRPCClientError = BaseTRPCClientError<typeof router> & {
toast?: 'success' | 'info' | 'warning' | 'error';
};

export type { TRPCClientError };
3 changes: 2 additions & 1 deletion src/server/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { testRouter } from '@/server/api/routers/test';
import { authRouter, testRouter } from '@/server/api/routers';
import { createCallerFactory, createRouter } from '@/server/api/trpc';

const router = createRouter({
test: testRouter,
auth: authRouter,
});

const createCaller = createCallerFactory(router);
Expand Down
26 changes: 26 additions & 0 deletions src/server/api/routers/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { publicProcedure } from '@/server/api/procedures';
import { RefillingTokenBucket } from '@/server/api/rate-limit/refillingTokenBucket';
import { createRouter } from '@/server/api/trpc';
import { getFeideAuthorizationURL } from '@/server/auth/feide';

import { TRPCError } from '@trpc/server';
import { headers } from 'next/headers';

const ipBucket = new RefillingTokenBucket<string>(5, 60);

const authRouter = createRouter({
getFeideAuthorizationURL: publicProcedure.query(async () => {
const headerStore = await headers();
const clientIP = headerStore.get('X-Forwarded-For');

if (clientIP !== null && !ipBucket.check(clientIP, 1)) {
throw new TRPCError({
code: 'TOO_MANY_REQUESTS',
message: 'Rate limit exceeded. Please try again later.',
});
}
return getFeideAuthorizationURL();
}),
});

export { authRouter };
2 changes: 2 additions & 0 deletions src/server/api/routers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './test';
export * from './auth';

0 comments on commit b36724f

Please sign in to comment.