diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx index f92793cd7..1a8a8ba6c 100644 --- a/frontend/src/components/ErrorBoundary.tsx +++ b/frontend/src/components/ErrorBoundary.tsx @@ -52,9 +52,10 @@ export default class ErrorBoundary extends PureComponent { public render(): ReactNode { if (this.state.error) { + // We ask the child components not to suspend, as this error boundary won't be in a Suspense boundary. return ( - - + + ); } diff --git a/frontend/src/components/Footer/Footer.tsx b/frontend/src/components/Footer/Footer.tsx index d5fa8e849..b92d1cbba 100644 --- a/frontend/src/components/Footer/Footer.tsx +++ b/frontend/src/components/Footer/Footer.tsx @@ -13,7 +13,7 @@ // limitations under the License. import { Link } from "@vector-im/compound-web"; -import { useTranslation } from "react-i18next"; +import { Translation } from "react-i18next"; import styles from "./Footer.module.css"; @@ -21,37 +21,58 @@ type Props = { policyUri?: string; tosUri?: string; imprint?: string; + dontSuspend?: boolean; }; -const Footer: React.FC = ({ policyUri, tosUri, imprint }) => { - const { t } = useTranslation(); - return ( -
- {(policyUri || tosUri) && ( - + )} - {imprint &&

{imprint}

} -
- ); -}; + {imprint &&

{imprint}

} + + )} + +); export default Footer; diff --git a/frontend/src/components/GenericError.module.css b/frontend/src/components/GenericError.module.css index f17e258ba..22a34c9c7 100644 --- a/frontend/src/components/GenericError.module.css +++ b/frontend/src/components/GenericError.module.css @@ -13,33 +13,6 @@ * limitations under the License. */ -.error { - display: flex; - flex-direction: column; - gap: var(--cpd-space-4x); -} - -.message { - display: flex; - flex-direction: column; - gap: var(--cpd-space-2x); - text-align: center; - - &>p { - color: var(--cpd-color-text-secondary); - } -} - -.icon { - align-self: center; - height: var(--cpd-space-16x); - width: var(--cpd-space-16x); - padding: var(--cpd-space-3x); - border-radius: var(--cpd-space-2x); - background-color: var(--cpd-color-bg-critical-subtle); - color: var(--cpd-color-icon-critical-primary); -} - .details { font: var(--cpd-font-body-sm-regular); background: var(--cpd-color-bg-critical-subtle); diff --git a/frontend/src/components/GenericError.tsx b/frontend/src/components/GenericError.tsx index 4b00ca425..9977a2a57 100644 --- a/frontend/src/components/GenericError.tsx +++ b/frontend/src/components/GenericError.tsx @@ -13,33 +13,50 @@ // limitations under the License. import IconError from "@vector-im/compound-design-tokens/icons/error.svg?react"; -import { Button, H2, Text } from "@vector-im/compound-web"; +import { Button } from "@vector-im/compound-web"; import { useState } from "react"; -import { useTranslation } from "react-i18next"; +import { Translation } from "react-i18next"; +import BlockList from "./BlockList"; import styles from "./GenericError.module.css"; +import PageHeading from "./PageHeading"; -const GenericError: React.FC<{ error: unknown }> = ({ error }) => { - const { t } = useTranslation(); +const GenericError: React.FC<{ error: unknown; dontSuspend?: boolean }> = ({ + error, + dontSuspend, +}) => { const [open, setOpen] = useState(false); return ( -
- -
-

{t("frontend.error.title")}

- {t("frontend.error.subtitle")} -
- - {open && ( -
-          {String(error)}
-        
+ + {(t) => ( + + + + {open && ( +
+              {String(error)}
+            
+ )} +
)} -
+ ); }; diff --git a/frontend/src/components/Layout/Layout.tsx b/frontend/src/components/Layout/Layout.tsx index 87917f6e4..5ae6bb5bb 100644 --- a/frontend/src/components/Layout/Layout.tsx +++ b/frontend/src/components/Layout/Layout.tsx @@ -19,18 +19,18 @@ import styles from "./Layout.module.css"; const Layout: React.FC<{ children?: React.ReactNode; -}> = ({ children }) => { - return ( -
- {children} + dontSuspend?: boolean; +}> = ({ children, dontSuspend }) => ( +
+ {children} -
-
- ); -}; +
+
+); export default Layout; diff --git a/frontend/src/components/LoadingSpinner/LoadingSpinner.tsx b/frontend/src/components/LoadingSpinner/LoadingSpinner.tsx index 745d46311..c613ae9c4 100644 --- a/frontend/src/components/LoadingSpinner/LoadingSpinner.tsx +++ b/frontend/src/components/LoadingSpinner/LoadingSpinner.tsx @@ -35,7 +35,9 @@ const LoadingSpinner: React.FC<{ inline?: boolean; className?: string }> = ({ /> - {(t): ReactNode => t("common.loading")} + + {(t): ReactNode => t("common.loading", { defaultValue: "Loading" })} + ); diff --git a/frontend/src/components/PageHeading/PageHeading.module.css b/frontend/src/components/PageHeading/PageHeading.module.css new file mode 100644 index 000000000..04b529b54 --- /dev/null +++ b/frontend/src/components/PageHeading/PageHeading.module.css @@ -0,0 +1,84 @@ +/* Copyright 2024 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.page-heading { + display: flex; + flex-direction: column; + gap: var(--cpd-space-4x); + + /* Layout already has 6x padding, and we need 10x */ + margin-block-start: var(--cpd-space-4x); + + & .icon { + display: flex; + align-items: center; + justify-content: center; + + align-self: center; + height: var(--cpd-space-16x); + width: var(--cpd-space-16x); + padding: var(--cpd-space-2x); + background-color: var(--cpd-color-bg-subtle-secondary); + border-radius: var(--cpd-space-2x); + + &.invalid { + background-color: var(--cpd-color-bg-critical-subtle); + + & svg { + color: var(--cpd-color-icon-critical-primary); + } + } + + &.success { + background-color: var(--cpd-color-bg-success-subtle); + + & svg { + color: var(--cpd-color-icon-success-primary); + } + } + + + & svg { + height: var(--cpd-space-10x); + width: var(--cpd-space-10x); + color: var(--cpd-color-icon-secondary); + } + } + + & .header { + display: flex; + flex-direction: column; + gap: var(--cpd-space-2x); + text-align: center; + + & .title { + font: var(--cpd-font-heading-lg-semibold); + letter-spacing: var(--cpd-font-letter-spacing-heading-xl); + color: var(--cpd-color-text-primary); + } + + & .text { + font: var(--cpd-font-body-lg-regular); + letter-spacing: var(--cpd-font-letter-spacing-body-lg); + color: var(--cpd-color-text-secondary); + text-wrap: balance; + + & em { + font-style: normal; + color: var(--cpd-color-text-primary); + } + } + } +} diff --git a/frontend/src/components/PageHeading/PageHeading.tsx b/frontend/src/components/PageHeading/PageHeading.tsx new file mode 100644 index 000000000..a837e15f1 --- /dev/null +++ b/frontend/src/components/PageHeading/PageHeading.tsx @@ -0,0 +1,52 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import cx from "classnames"; + +import styles from "./PageHeading.module.css"; + +type Props = { + Icon: React.ComponentType>; + invalid?: boolean; + success?: boolean; + title: string; + subtitle?: string; +}; + +const PageHeading: React.FC = ({ + Icon, + invalid, + success, + title, + subtitle, +}) => ( +
+
+ +
+ +
+

{title}

+ {subtitle &&

{subtitle}

} +
+
+); + +export default PageHeading; diff --git a/frontend/src/components/PageHeading/index.ts b/frontend/src/components/PageHeading/index.ts new file mode 100644 index 000000000..937932868 --- /dev/null +++ b/frontend/src/components/PageHeading/index.ts @@ -0,0 +1,15 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export { default } from "./PageHeading"; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 78e20607d..cc43e3606 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -21,6 +21,7 @@ import { Provider as UrqlProvider } from "urql"; import ErrorBoundary from "./components/ErrorBoundary"; import GenericError from "./components/GenericError"; +import Layout from "./components/Layout"; import LoadingScreen from "./components/LoadingScreen"; import config from "./config"; import { client } from "./graphql"; @@ -46,15 +47,17 @@ declare module "@tanstack/react-router" { createRoot(document.getElementById("root") as HTMLElement).render( - - }> + }> + - + + + - - + + , ); diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index 60edcc763..d24c00d20 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -85,9 +85,7 @@ export const Route = createRootRouteWithContext<{ component: () => ( <> - - - + {import.meta.env.DEV && } ), diff --git a/frontend/src/templates.css b/frontend/src/templates.css index 1ff095548..820cd4856 100644 --- a/frontend/src/templates.css +++ b/frontend/src/templates.css @@ -1,4 +1,4 @@ -/* Copyright 2022 The Matrix.org Foundation C.I.C. +/* Copyright 2022-2024 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ @import url("./components/Layout/Layout.module.css"); @import url("./components/Footer/Footer.module.css"); +@import url("./components/PageHeading/PageHeading.module.css"); @config "../tailwind.templates.config.cjs"; @@ -202,73 +203,3 @@ color: var(--cpd-color-text-primary); } } - -.page-heading { - display: flex; - flex-direction: column; - gap: var(--cpd-space-4x); - - /* Layout already has 6x padding, and we need 10x */ - margin-block-start: var(--cpd-space-4x); - - & .icon { - display: flex; - align-items: center; - justify-content: center; - - align-self: center; - height: var(--cpd-space-16x); - width: var(--cpd-space-16x); - padding: var(--cpd-space-2x); - background-color: var(--cpd-color-bg-subtle-secondary); - border-radius: var(--cpd-space-2x); - - &.invalid { - background-color: var(--cpd-color-bg-critical-subtle); - - & svg { - color: var(--cpd-color-icon-critical-primary); - } - } - - &.success { - background-color: var(--cpd-color-bg-success-subtle); - - & svg { - color: var(--cpd-color-icon-success-primary); - } - } - - - & svg { - height: var(--cpd-space-10x); - width: var(--cpd-space-10x); - color: var(--cpd-color-icon-secondary); - } - } - - & .header { - display: flex; - flex-direction: column; - gap: var(--cpd-space-2x); - text-align: center; - - & .title { - font: var(--cpd-font-heading-lg-semibold); - letter-spacing: var(--cpd-font-letter-spacing-heading-xl); - color: var(--cpd-color-text-primary); - } - - & .text { - font: var(--cpd-font-body-lg-regular); - letter-spacing: var(--cpd-font-letter-spacing-body-lg); - color: var(--cpd-color-text-secondary); - text-wrap: balance; - - & em { - font-style: normal; - color: var(--cpd-color-text-primary); - } - } - } -}