Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/nestjs-backend/src/types/i18n.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2971,6 +2971,7 @@ export type I18nTranslations = {
"system": {
"notFound": {
"title": string;
"description": string;
};
"links": {
"backToHome": string;
Expand All @@ -2983,6 +2984,10 @@ export type I18nTranslations = {
"title": string;
"description": string;
};
"error": {
"title": string;
"description": string;
};
};
"table": {
"toolbar": {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 29 additions & 17 deletions apps/nextjs-app/src/features/system/pages/ErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Head from 'next/head';
import { useTranslation } from 'next-i18next';
import type { FC } from 'react';
import { systemConfig } from '@/features/i18n/system.config';
import { IllustrationPage } from './IllustrationPage';

type Props = {
statusCode?: number | null;
Expand All @@ -11,26 +13,36 @@ type Props = {

export const ErrorPage: FC<Props> = (props) => {
const { error, errorId, message, statusCode } = props;
const { t } = useTranslation(systemConfig.i18nNamespaces);

return (
<>
<Head>
<title>Error {statusCode}</title>
</Head>
<div className="container bg-white text-2xl md:text-xl">
<div className="size-screen flex flex-col items-center justify-center">
<h1 className="m-5 text-5xl text-black md:text-4xl">Woops !</h1>
<p className="text-2xl text-black md:text-2xl">
Something went wrong. Please try again later.
</p>
<div className="relative">
<IllustrationPage
imageLightSrc="/images/layout/error-light.png"
imageDarkSrc="/images/layout/error-dark.png"
imageAlt="Error"
title={t('system:error.title')}
description={t('system:error.description')}
button={{ label: t('system:links.backToHome'), href: '/' }}
/>
<div className="absolute bottom-0 right-0 m-5 flex flex-col gap-1 rounded-lg border bg-background p-4 text-left text-sm">
<div className="flex gap-2" data-testid="error-status-code">
<span className="text-muted-foreground">Code: </span>
<span className="text-foreground">{statusCode}</span>
</div>
<div className="absolute bottom-0 right-0 m-5 rounded-lg border-2 border-solid border-indigo-400 p-5 text-left text-sm text-gray-700">
<p data-testid="error-status-code">Code: {statusCode}</p>
<p>Message: {message}</p>
<p>Error id: {errorId}</p>
<p>ErrorMessage: {error?.message}</p>
<div className="flex gap-2">
<span className="text-muted-foreground">Message: </span>
<span className="text-foreground">{message}</span>
</div>
<div className="flex gap-2">
<span className="text-muted-foreground">Error id: </span>
<span className="text-foreground">{errorId}</span>
</div>
<div className="flex gap-2">
<span className="text-muted-foreground">ErrorMessage: </span>
<span className="text-foreground">{error?.message}</span>
</div>
</div>
</>
</div>
);
};
34 changes: 14 additions & 20 deletions apps/nextjs-app/src/features/system/pages/ForbiddenPage.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
import { Button } from '@teable/ui-lib/shadcn';
import Head from 'next/head';
import { useTranslation } from 'next-i18next';
import type { FC } from 'react';
import { systemConfig } from '@/features/i18n/system.config';
import type { IButtonConfig } from './IllustrationPage';
import { IllustrationPage } from './IllustrationPage';

type Props = {
type ForbiddenPageProps = {
title?: string;
children?: never;
description?: string;
button?: IButtonConfig;
};

export const ForbiddenPage: FC<Props> = (props) => {
export const ForbiddenPage: FC<ForbiddenPageProps> = ({ title, description, button }) => {
const { t } = useTranslation(systemConfig.i18nNamespaces);
const title = props.title ?? t('system:forbidden.title');

return (
<>
<Head>
<title>{title}</title>
</Head>
<div className="flex h-screen flex-col items-center justify-center gap-y-6 text-center">
<h1 data-testid="not-found-title" className="text-5xl md:text-4xl lg:text-5xl">
{title}
</h1>
<p className="text-2xl md:text-2xl lg:text-3xl">{t('system:forbidden.description')}</p>
<Button className="text-center text-xl no-underline hover:underline">
<a href={'/'}>{t('system:links.backToHome')}</a>
</Button>
</div>
</>
<IllustrationPage
imageLightSrc="/images/layout/permission-light.png"
imageDarkSrc="/images/layout/permission-dark.png"
imageAlt="Permission Denied"
title={title ?? t('system:forbidden.title')}
description={description ?? t('system:forbidden.description')}
button={button ?? { label: t('system:links.backToHome'), href: '/' }}
/>
);
};
63 changes: 63 additions & 0 deletions apps/nextjs-app/src/features/system/pages/IllustrationPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useTheme } from '@teable/next-themes';
import { Button } from '@teable/ui-lib/shadcn';
import Head from 'next/head';
import Image from 'next/image';
import type { FC } from 'react';

export interface IButtonConfig {
label: string;
href: string;
variant?: 'default' | 'secondary' | 'outline' | 'ghost' | 'link' | 'destructive';
}

export interface IIllustrationPageProps {
/** Light theme image path */
imageLightSrc: string;
/** Dark theme image path */
imageDarkSrc: string;
/** Image alt text */
imageAlt?: string;
/** Page title (also used for document title) */
title: string;
/** Page description */
description?: string;
/** Button config */
button: IButtonConfig;
}

export const IllustrationPage: FC<IIllustrationPageProps> = ({
imageLightSrc,
imageDarkSrc,
imageAlt = 'Illustration',
title,
description,
button,
}) => {
const { resolvedTheme } = useTheme();

const imageSrc = resolvedTheme === 'dark' ? imageDarkSrc : imageLightSrc;

return (
<>
<Head>
<title>{title}</title>
</Head>
<div className="flex h-screen flex-col items-center justify-center px-4 text-center">
<Image src={imageSrc} alt={imageAlt} width={240} height={240} priority />
<div className="mb-6 mt-4 flex flex-col items-center justify-center gap-2">
<h1 data-testid="not-found-title" className="text-3xl font-semibold md:text-2xl">
{title}
</h1>
{description && (
<p className="max-w-md whitespace-pre-line text-base text-muted-foreground">
{description}
</p>
)}
</div>
<Button asChild variant={button.variant ?? 'default'}>
<a href={button.href}>{button.label}</a>
</Button>
</div>
</>
);
};
34 changes: 14 additions & 20 deletions apps/nextjs-app/src/features/system/pages/NotFoundPage.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
import { Button } from '@teable/ui-lib/shadcn';
import Head from 'next/head';
import { useTranslation } from 'next-i18next';
import type { FC } from 'react';

import { systemConfig } from '@/features/i18n/system.config';
import type { IButtonConfig } from './IllustrationPage';
import { IllustrationPage } from './IllustrationPage';

type Props = {
type NotFoundPageProps = {
title?: string;
children?: never;
description?: string;
button?: IButtonConfig;
};

export const NotFoundPage: FC<Props> = (props) => {
export const NotFoundPage: FC<NotFoundPageProps> = ({ title, description, button }) => {
const { t } = useTranslation(systemConfig.i18nNamespaces);
const title = props.title ?? t('system:notFound.title');

return (
<>
<Head>
<title>{title}</title>
</Head>
<div className="flex h-screen flex-col items-center justify-center gap-y-6 bg-white text-center">
<h1 data-testid="not-found-title" className="text-5xl text-black md:text-4xl lg:text-5xl">
{title}
</h1>
<Button className="text-center text-xl no-underline hover:underline">
<a href={'/'}>{t('system:links.backToHome')}</a>
</Button>
</div>
</>
<IllustrationPage
imageLightSrc="/images/layout/not-found-light.png"
imageDarkSrc="/images/layout/not-found-dark.png"
imageAlt="Not Found"
title={title ?? t('system:notFound.title')}
description={description ?? t('system:notFound.description')}
button={button ?? { label: t('system:links.backToHome'), href: '/' }}
/>
);
};
42 changes: 19 additions & 23 deletions apps/nextjs-app/src/features/system/pages/PaymentRequired.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,30 @@
import { Button } from '@teable/ui-lib/shadcn';
import Head from 'next/head';
import { Trans, useTranslation } from 'next-i18next';
import { useTranslation } from 'next-i18next';
import type { FC } from 'react';
import { systemConfig } from '@/features/i18n/system.config';
import type { IButtonConfig } from './IllustrationPage';
import { IllustrationPage } from './IllustrationPage';

type Props = {
type PaymentRequiredPageProps = {
title?: string;
children?: never;
description?: string;
button?: IButtonConfig;
};

export const PaymentRequiredPage: FC<Props> = (props) => {
export const PaymentRequiredPage: FC<PaymentRequiredPageProps> = ({
title,
description,
button,
}) => {
const { t } = useTranslation(systemConfig.i18nNamespaces);
const title = props.title ?? t('system:paymentRequired.title');

return (
<>
<Head>
<title>{title}</title>
</Head>
<div className="flex h-screen flex-col items-center justify-center gap-y-6 text-center">
<h1 data-testid="not-found-title" className="text-5xl md:text-4xl lg:text-5xl">
{title}
</h1>
<p className="text-2xl md:text-2xl lg:text-3xl">
<Trans ns="system" i18nKey="paymentRequired.description" components={{ br: <br /> }} />
</p>
<Button className="text-center text-xl no-underline hover:underline">
<a href={'/'}>{t('system:links.backToHome')}</a>
</Button>
</div>
</>
<IllustrationPage
imageLightSrc="/images/layout/upgrade-light.png"
imageDarkSrc="/images/layout/upgrade-dark.png"
imageAlt="Payment Required"
title={title ?? t('system:paymentRequired.title')}
description={description ?? t('system:paymentRequired.description')}
button={button ?? { label: t('system:links.backToHome'), href: '/' }}
/>
);
};
2 changes: 2 additions & 0 deletions apps/nextjs-app/src/features/system/pages/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { IllustrationPage } from './IllustrationPage';
export type { IButtonConfig, IIllustrationPageProps } from './IllustrationPage';
export { NotFoundPage } from './NotFoundPage';
export { ErrorPage } from './ErrorPage';
export { ForbiddenPage } from './ForbiddenPage';
Expand Down
17 changes: 11 additions & 6 deletions packages/common-i18n/src/locales/de/system.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
{
"notFound": {
"title": "404 - Seite wurde nicht gefunden"
"title": "Seite nicht gefunden",
"description": "Der Link, dem Sie gefolgt sind, ist möglicherweise fehlerhaft oder die Seite wurde verschoben."
},
"links": {
"backToHome": "Zurück zum Anfang"
"backToHome": "Zurück zur Startseite"
},
"forbidden": {
"title": "403 - Verboten",
"description": "Sie haben keine Berechtigung, auf diese Seite zuzugreifen."
"title": "Zugriff eingeschränkt",
"description": "Sie benötigen eine Berechtigung, um auf diese Ressource zuzugreifen.\nBitte wenden Sie sich an Ihren Administrator."
},
"paymentRequired": {
"title": "402 - Upgrade des Abonnements erforderlich",
"description": "Your current subscription does not support access to this feature. <br></br>Please upgrade your subscription to continue."
"title": "Premium-Funktion freischalten",
"description": "Diese Funktion ist in erweiterten Plänen verfügbar.\nUpgraden Sie, um Ihre Möglichkeiten zu erweitern."
},
"error": {
"title": "Etwas ist schief gelaufen",
"description": "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut."
}
}
15 changes: 10 additions & 5 deletions packages/common-i18n/src/locales/en/system.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
{
"notFound": {
"title": "404 - Page not found"
"title": "Page not found",
"description": "The link you followed may be broken, or the page has been moved."
},
"links": {
"backToHome": "Back to home"
},
"forbidden": {
"title": "403 - Forbidden",
"description": "You don't have permission to access this page."
"title": "Access restricted",
"description": "You need permission to access this resource.\nPlease contact your admin."
},
"paymentRequired": {
"title": "402 - Subscription upgrade required",
"description": "Your current subscription does not support access to this feature. <br></br>Please upgrade your subscription to continue."
"title": "Unlock premium feature",
"description": "This feature is available on advanced plans.\nUpgrade to expand your capabilities."
},
"error": {
"title": "Something went wrong",
"description": "An unexpected issue occurred. Please try again later."
}
}
15 changes: 10 additions & 5 deletions packages/common-i18n/src/locales/es/system.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
{
"notFound": {
"title": "404 - Página No Encontrada"
"title": "Página no encontrada",
"description": "El enlace que seguiste puede estar roto o la página ha sido movida."
},
"links": {
"backToHome": "Volver al inicio"
},
"forbidden": {
"title": "403 - Acceso Denegado",
"description": "No tienes permiso para acceder a esta página."
"title": "Acceso restringido",
"description": "Necesitas permiso para acceder a este recurso.\nPor favor, contacta con tu administrador."
},
"paymentRequired": {
"title": "402 - Se Requiere Actualizar la Suscripción",
"description": "Tu suscripción actual no permite acceder a esta función. <br></br>Por favor, actualiza tu suscripción para continuar."
"title": "Desbloquear función Premium",
"description": "Esta función está disponible en planes avanzados.\nActualiza para ampliar tus capacidades."
},
"error": {
"title": "Algo salió mal",
"description": "Ocurrió un error inesperado. Por favor, inténtalo de nuevo más tarde."
}
}
Loading
Loading