diff --git a/apps/admin-ui/public/images/deactivated-account.png b/apps/admin-ui/public/images/deactivated-account.png
new file mode 100644
index 0000000000..ffb0aca1f4
Binary files /dev/null and b/apps/admin-ui/public/images/deactivated-account.png differ
diff --git a/apps/admin-ui/src/App.tsx b/apps/admin-ui/src/App.tsx
index c5df9663bc..4e6f7ce1c9 100644
--- a/apps/admin-ui/src/App.tsx
+++ b/apps/admin-ui/src/App.tsx
@@ -14,6 +14,7 @@ import NotificationsRouter from "./spa/notifications/router";
import Error404 from "./common/Error404";
import { UserPermissionChoice } from "@gql/gql-types";
import { UnitsRouter } from "./spa/unit/router";
+import DeactivatedAccount from "common/src/components/DeactivatedAccount";
const Units = dynamic(() => import("./spa/unit"));
@@ -151,6 +152,15 @@ function ClientApp({
UserPermissionChoice.CanManageNotifications
)}
/>
+
+ }
+ />
diff --git a/apps/admin-ui/src/i18n/index.ts b/apps/admin-ui/src/i18n/index.ts
index 9f5cda01d1..895e558ce2 100644
--- a/apps/admin-ui/src/i18n/index.ts
+++ b/apps/admin-ui/src/i18n/index.ts
@@ -74,4 +74,14 @@ i18n.addResourceBundle("fi", "forms", {
invalidEmail: "Sähköpostin tulee olla oikeassa muodossa (sisältäen @-merkin)",
});
+i18n.addResourceBundle("fi", "errors", {
+ deactivatedAccount: {
+ heading: "Käyttäjätunnuksesi ei ole voimassa.",
+ subHeadingA: "Ota yhteyttä asiakaspalveluun sähköpostitse",
+ subHeadingB: "tai Ota yhteyttä-lomakkeella.",
+ email: "varaamo@hel.fi",
+ button: "Ota yhteyttä",
+ },
+});
+
export default i18n;
diff --git a/apps/ui/pages/deactivated-account.tsx b/apps/ui/pages/deactivated-account.tsx
new file mode 100644
index 0000000000..26ea670c1b
--- /dev/null
+++ b/apps/ui/pages/deactivated-account.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+import { getCommonServerSideProps } from "@/modules/serverUtils";
+import { GetServerSidePropsContext } from "next";
+import { serverSideTranslations } from "next-i18next/serverSideTranslations";
+import DeactivatedAccount from "common/src/components/DeactivatedAccount";
+
+export async function getServerSideProps({
+ locale,
+}: GetServerSidePropsContext) {
+ return {
+ props: {
+ ...getCommonServerSideProps(),
+ ...(await serverSideTranslations(locale ?? "fi")),
+ },
+ };
+}
+
+const DeactivatedAccountPage = ({ feedbackUrl }: { feedbackUrl: string }) => {
+ return (
+
+ );
+};
+
+export default DeactivatedAccountPage;
diff --git a/apps/ui/public/images/deactivated-account.png b/apps/ui/public/images/deactivated-account.png
new file mode 100644
index 0000000000..ffb0aca1f4
Binary files /dev/null and b/apps/ui/public/images/deactivated-account.png differ
diff --git a/apps/ui/public/locales/en/errors.json b/apps/ui/public/locales/en/errors.json
index 1d9e6d8ae9..373403fff9 100644
--- a/apps/ui/public/locales/en/errors.json
+++ b/apps/ui/public/locales/en/errors.json
@@ -5,6 +5,13 @@
"500": {
"body": "Something unexpected happened"
},
+ "deactivatedAccount": {
+ "heading": "Your User ID is not valid.",
+ "subHeadingA": "Please contact support via email at",
+ "subHeadingB": "or via the Contact us form.",
+ "email": "varaamo@hel.fi",
+ "button": "Contact us"
+ },
"applicationMutation": {
"Validation error": "Error in the API call",
"Form validation error": "Form validation error",
diff --git a/apps/ui/public/locales/fi/errors.json b/apps/ui/public/locales/fi/errors.json
index c46122d463..4ae8b56e5a 100644
--- a/apps/ui/public/locales/fi/errors.json
+++ b/apps/ui/public/locales/fi/errors.json
@@ -5,6 +5,13 @@
"500": {
"body": "Jotain meni pieleen"
},
+ "deactivatedAccount": {
+ "heading": "Käyttäjätunnuksesi ei ole voimassa.",
+ "subHeadingA": "Ota yhteyttä asiakaspalveluun sähköpostitse",
+ "subHeadingB": "tai Ota yhteyttä-lomakkeella.",
+ "email": "varaamo@hel.fi",
+ "button": "Ota yhteyttä"
+ },
"applicationMutation": {
"Validation error": "Virheellinen rajapintakutsu",
"Form validation error": "Virheellinen lomake",
diff --git a/apps/ui/public/locales/sv/errors.json b/apps/ui/public/locales/sv/errors.json
index a427c711fa..3cfb510be7 100644
--- a/apps/ui/public/locales/sv/errors.json
+++ b/apps/ui/public/locales/sv/errors.json
@@ -5,6 +5,13 @@
"500": {
"body": "Något oväntat hände"
},
+ "deactivatedAccount": {
+ "heading": "Ditt användar-ID är inte giltigt.",
+ "subHeadingA": "Var god och kontakta vår kundtjänst via email på",
+ "subHeadingB": "eller via Ta kontakt-formuläret.",
+ "email": "varaamo@hel.fi",
+ "button": "Ta kontakt"
+ },
"applicationMutation": {
"Validation error": "Ett fel uppstod i API-anropet",
"Form validation error": "Formuläret innehåller fel",
diff --git a/packages/common/src/components/DeactivatedAccount.tsx b/packages/common/src/components/DeactivatedAccount.tsx
new file mode 100644
index 0000000000..c492e7c3a9
--- /dev/null
+++ b/packages/common/src/components/DeactivatedAccount.tsx
@@ -0,0 +1,111 @@
+import React from "react";
+import { useTranslation } from "next-i18next";
+import styled from "styled-components";
+import IconButton from "./IconButton";
+import { IconArrowRight } from "hds-react";
+import { fontBold, H1 } from "../common/typography";
+import { breakpoints } from "../common/style";
+import Image from "next/image";
+
+const ErrorContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ max-width: var(--container-width-xl);
+ margin: 0 auto;
+ gap: var(--spacing-xl);
+ padding-block: var(--spacing-xl);
+ padding-inline: var(--spacing-s);
+
+ --spacing-hz: var(--spacing-s);
+ @media (min-width: ${breakpoints.m}) {
+ flex-direction: row;
+ padding-inline: var(--spacing-m);
+
+ --spacing-hz: var(--spacing-m);
+ }
+`;
+
+const Img = styled(Image)`
+ object-fit: contain;
+ width: auto;
+ height: auto;
+ margin: 0 auto;
+ @media (min-width: ${breakpoints.l}) {
+ flex-grow: 1;
+ display: flex;
+ max-width: 350px;
+ width: 419px;
+ }
+`;
+
+const TextContent = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-xl);
+ justify-content: center;
+ @media (min-width: ${breakpoints.l}) {
+ order: -1;
+ }
+`;
+
+const Body = styled.p`
+ margin: 0;
+`;
+
+const Email = styled.a`
+ margin: var(--spacing-xs) 0 0;
+ ${fontBold}
+`;
+
+// NOTE: This is a copy of the Footer component from the common package
+const constructFeedbackUrl = (
+ feedbackUrl: string,
+ i18n: { language: string }
+) => {
+ try {
+ const url = new URL(feedbackUrl);
+ url.searchParams.set("lang", i18n.language);
+ return url.toString();
+ } catch (e) {
+ return null;
+ }
+};
+
+const DeactivatedAccount = ({
+ feedbackUrl,
+ imgSrc,
+}: {
+ feedbackUrl: string;
+ imgSrc: string;
+}) => {
+ const { t, i18n } = useTranslation();
+ return (
+
+
+
+ {t("errors:deactivatedAccount.heading")}
+
+ {`${t("errors:deactivatedAccount.subHeadingA")} `}
+
+ {t("errors:deactivatedAccount.email")}
+
+ {` ${t("errors:deactivatedAccount.subHeadingB")}`}
+
+ }
+ href={constructFeedbackUrl(feedbackUrl, i18n) ?? feedbackUrl}
+ rel="noopener noreferrer"
+ />
+
+
+ );
+};
+
+export default DeactivatedAccount;