瀏覽代碼

refactor: adapt frontend error handling to support translated error variables

Nicolas Meienberger 2 年之前
父節點
當前提交
7dfaf56887

+ 6 - 0
src/client/mocks/getTrpcMock.ts

@@ -20,6 +20,9 @@ export type RpcErrorResponse = {
         stack: string;
         path: string; // TQuery
         zodError?: Record<string, string>;
+        tError: {
+          message: string;
+        };
       };
     };
   };
@@ -45,6 +48,9 @@ const jsonRpcErrorResponse = (path: string, status: number, message: string, zod
         stack: 'Error: Internal Server Error',
         path,
         zodError,
+        tError: {
+          message,
+        },
       },
     },
   },

+ 6 - 7
src/client/modules/Apps/containers/AppDetailsContainer/AppDetailsContainer.tsx

@@ -47,8 +47,7 @@ export const AppDetailsContainer: React.FC<IProps> = ({ app }) => {
     },
     onError: (e) => {
       invalidate();
-      const toastMessage = t(e.data?.translatedError || (e.message as MessageKey));
-      toast.error(toastMessage);
+      toast.error(t(e.data?.tError.message as MessageKey, { ...e.data?.tError?.variables }));
     },
   });
 
@@ -61,7 +60,7 @@ export const AppDetailsContainer: React.FC<IProps> = ({ app }) => {
       invalidate();
       toast.success(t('apps.app-details.uninstall-success'));
     },
-    onError: (e) => toast.error(t(e.data?.translatedError || (e.message as MessageKey))),
+    onError: (e) => toast.error(t(e.data?.tError.message as MessageKey, { ...e.data?.tError?.variables })),
   });
 
   const stop = trpc.app.stopApp.useMutation({
@@ -73,7 +72,7 @@ export const AppDetailsContainer: React.FC<IProps> = ({ app }) => {
       invalidate();
       toast.success(t('apps.app-details.stop-success'));
     },
-    onError: (e) => toast.error(t(e.data?.translatedError || (e.message as MessageKey))),
+    onError: (e) => toast.error(t(e.data?.tError.message as MessageKey, { ...e.data?.tError?.variables })),
   });
 
   const update = trpc.app.updateApp.useMutation({
@@ -85,7 +84,7 @@ export const AppDetailsContainer: React.FC<IProps> = ({ app }) => {
       invalidate();
       toast.success(t('apps.app-details.update-success'));
     },
-    onError: (e) => toast.error(t(e.data?.translatedError || (e.message as MessageKey))),
+    onError: (e) => toast.error(t(e.data?.tError.message as MessageKey, { ...e.data?.tError?.variables })),
   });
 
   const start = trpc.app.startApp.useMutation({
@@ -96,7 +95,7 @@ export const AppDetailsContainer: React.FC<IProps> = ({ app }) => {
       invalidate();
       toast.success(t('apps.app-details.start-success'));
     },
-    onError: (e) => toast.error(t(e.data?.translatedError || (e.message as MessageKey))),
+    onError: (e) => toast.error(t(e.data?.tError.message as MessageKey, { ...e.data?.tError?.variables })),
   });
 
   const updateConfig = trpc.app.updateAppConfig.useMutation({
@@ -105,7 +104,7 @@ export const AppDetailsContainer: React.FC<IProps> = ({ app }) => {
       invalidate();
       toast.success(t('apps.app-details.update-config-success'));
     },
-    onError: (e) => toast.error(t(e.data?.translatedError || (e.message as MessageKey))),
+    onError: (e) => toast.error(t(e.data?.tError.message as MessageKey, { ...e.data?.tError?.variables })),
   });
 
   const updateAvailable = Number(app.version || 0) < Number(app?.latestVersion || 0);

+ 2 - 2
src/client/modules/Apps/pages/AppDetailsPage/AppDetailsPage.tsx

@@ -1,8 +1,8 @@
 import { NextPage } from 'next';
 import React from 'react';
 import { useRouter } from 'next/router';
-import type { MessageKey } from '@/server/utils/errors';
 import { useTranslations } from 'next-intl';
+import type { MessageKey } from '@/server/utils/errors';
 import { Layout } from '../../../../components/Layout';
 import { ErrorPage } from '../../../../components/ui/ErrorPage';
 import { trpc } from '../../../../utils/trpc';
@@ -36,7 +36,7 @@ export const AppDetailsPage: NextPage<IProps> = ({ appId }) => {
   return (
     <Layout title={data?.info.name || ''} breadcrumbs={breadcrumb}>
       {data?.info && <AppDetailsContainer app={data} />}
-      {error && <ErrorPage error={t(error.data?.translatedError || (error.message as MessageKey))} />}
+      {error && <ErrorPage error={t(error.data?.tError.message as MessageKey, { ...error.data?.tError.variables })} />}
     </Layout>
   );
 };

+ 2 - 2
src/client/modules/Apps/pages/AppsPage/AppsPage.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 import { useRouter } from 'next/router';
 import { NextPage } from 'next';
 import { useTranslations } from 'next-intl';
-import { MessageKey } from '@/server/utils/errors';
+import type { MessageKey } from '@/server/utils/errors';
 import { AppTile } from '../../../../components/AppTile';
 import { Layout } from '../../../../components/Layout';
 import { EmptyPage } from '../../../../components/ui/EmptyPage';
@@ -35,7 +35,7 @@ export const AppsPage: NextPage = () => {
         {!isLoading && data?.length === 0 && (
           <EmptyPage title={t('apps.my-apps.empty-title')} subtitle={t('apps.my-apps.empty-subtitle')} onAction={() => router.push('/app-store')} actionLabel={t('apps.my-apps.empty-action')} />
         )}
-        {error && <ErrorPage error={t(error.data?.translatedError || (error.message as MessageKey))} />}
+        {error && <ErrorPage error={t(error.data?.tError.message as MessageKey, { ...error.data?.tError?.variables })} />}
       </div>
     </Layout>
   );

+ 2 - 8
src/client/modules/Auth/containers/LoginContainer/LoginContainer.tsx

@@ -16,10 +16,7 @@ export const LoginContainer: React.FC = () => {
   const router = useRouter();
   const utils = trpc.useContext();
   const login = trpc.auth.login.useMutation({
-    onError: (e) => {
-      const toastMessage = t(e.data?.translatedError || (e.message as MessageKey));
-      toast.error(toastMessage);
-    },
+    onError: (e) => toast.error(t(e.data?.tError.message as MessageKey, { ...e.data?.tError?.variables })),
     onSuccess: (data) => {
       if (data.totpSessionId) {
         setTotpSessionId(data.totpSessionId);
@@ -31,10 +28,7 @@ export const LoginContainer: React.FC = () => {
   });
 
   const verifyTotp = trpc.auth.verifyTotp.useMutation({
-    onError: (e) => {
-      const toastMessage = t(e.data?.translatedError || (e.message as MessageKey));
-      toast.error(toastMessage);
-    },
+    onError: (e) => toast.error(t(e.data?.tError.message as MessageKey, { ...e.data?.tError?.variables })),
     onSuccess: () => {
       utils.auth.me.invalidate();
       router.push('/');

+ 1 - 4
src/client/modules/Auth/containers/RegisterContainer/RegisterContainer.tsx

@@ -16,10 +16,7 @@ export const RegisterContainer: React.FC = () => {
   const router = useRouter();
   const utils = trpc.useContext();
   const register = trpc.auth.register.useMutation({
-    onError: (e) => {
-      const toastMessage = t(e.data?.translatedError || (e.message as MessageKey));
-      toast.error(toastMessage);
-    },
+    onError: (e) => toast.error(t(e.data?.tError.message as MessageKey, { ...e.data?.tError?.variables })),
     onSuccess: () => {
       utils.auth.me.invalidate();
       router.push('/');

+ 1 - 4
src/client/modules/Auth/containers/ResetPasswordContainer/ResetPasswordContainer.tsx

@@ -22,10 +22,7 @@ export const ResetPasswordContainer: React.FC<Props> = ({ isRequested }) => {
     onSuccess: () => {
       utils.auth.checkPasswordChangeRequest.invalidate();
     },
-    onError: (e) => {
-      const toastMessage = t(e.data?.translatedError || (e.message as MessageKey));
-      toast.error(toastMessage);
-    },
+    onError: (e) => toast.error(t(e.data?.tError.message as MessageKey, { ...e.data?.tError?.variables })),
   });
   const cancelRequest = trpc.auth.cancelPasswordChangeRequest.useMutation({
     onSuccess: () => {

+ 1 - 1
src/client/modules/Dashboard/pages/DashboardPage/DashboardPage.tsx

@@ -14,7 +14,7 @@ export const DashboardPage: NextPage = () => {
   return (
     <Layout title={t('dashboard.title')}>
       {data && <DashboardContainer data={data} />}
-      {error && <ErrorPage error={t(error.data?.translatedError || (error.message as MessageKey))} />}
+      {error && <ErrorPage error={t(error.data?.tError.message as MessageKey, { ...error.data?.tError.variables })} />}
     </Layout>
   );
 };

+ 3 - 2
src/server/trpc.ts

@@ -5,7 +5,7 @@ import { Locale } from '@/shared/internationalization/locales';
 import { type Context } from './context';
 import { AuthQueries } from './queries/auth/auth.queries';
 import { db } from './db';
-import { MessageKey, TranslatedError } from './utils/errors';
+import { type MessageKey, TranslatedError } from './utils/errors';
 
 const authQueries = new AuthQueries(db);
 
@@ -35,7 +35,8 @@ const t = initTRPC.context<Context>().create({
       data: {
         ...shape.data,
         zodError: error.code === 'BAD_REQUEST' && error.cause instanceof ZodError ? zodErrorsToRecord(error.cause.flatten()) : null,
-        translatedError: error.cause instanceof TranslatedError ? (error.cause.message as MessageKey) : null,
+        tError:
+          error.cause instanceof TranslatedError ? { message: error.cause.message as MessageKey, variables: error.cause.variableValues } : { message: error.message as MessageKey, variables: {} },
       },
     };
   },