verification

master
alex 2024-02-10 13:52:20 +01:00
parent e21745125d
commit c2147f45f9
9 changed files with 213 additions and 8 deletions

View File

@ -378,5 +378,22 @@
"improveMinLengthRequired": "Bitte geben Sie mindestens {{minLength}} Zeichen ein" "improveMinLengthRequired": "Bitte geben Sie mindestens {{minLength}} Zeichen ein"
} }
} }
},
"verification": {
"error": {
"title": "Verifizierung fehlgeschlagen",
"description": "Keine ausstehende Überprüfung gefunden"
},
"content": [
{
"requesting": {
"title": "E-Mail-Verifizierung ausstehend"
},
"Erfolg": {
"title": "E-Mail-Überprüfung erfolgreich",
"button": "Jetzt anmelden"
}
}
]
} }
} }

View File

@ -381,5 +381,22 @@
"improveMinLengthRequired": "Please enter at least {{minLength}} characters" "improveMinLengthRequired": "Please enter at least {{minLength}} characters"
} }
} }
},
"verification": {
"error": {
"title": "Verification failed",
"description": "No pending verification found."
},
"content": [
{
"requesting": {
"title": "Email verification pending"
},
"success": {
"title": "Email verification successful",
"button": "Login now"
}
}
]
} }
} }

View File

@ -19,7 +19,10 @@ import StoresProvider from "./Contexts/StoresContext";
import { clarity } from "react-microsoft-clarity"; import { clarity } from "react-microsoft-clarity";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import MyAppLogo from "./Components/MyAppLogo"; import MyAppLogo from "./Components/MyAppLogo";
import { AuthenticationRoutes } from "./Components/AppRoutes"; import {
AuthenticationRoutes,
VerificationRoutes,
} from "./Components/AppRoutes";
export function Loading() { export function Loading() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -65,7 +68,16 @@ export function Loading() {
); );
} }
export default function App() { export default function PreApp() {
// if the users comes from a verification email (e.g. when sign up or delete account)
if (window.location.pathname.startsWith("/verify/")) {
return <VerificationRoutes />;
}
return <App />;
}
export function App() {
const { _, i18n } = useTranslation(); const { _, i18n } = useTranslation();
const { userSession, setUserSession } = UseUserSession(); const { userSession, setUserSession } = UseUserSession();

View File

@ -3,6 +3,7 @@ import { Constants, isDevelopmentEnv } from "../../utils";
import { lazy } from "react"; import { lazy } from "react";
import { MySupsenseFallback } from "../MySupsenseFallback"; import { MySupsenseFallback } from "../MySupsenseFallback";
import { AuthenticationMethod } from "../../Pages/Authentication"; import { AuthenticationMethod } from "../../Pages/Authentication";
import Verification from "../../Pages/Verification";
// Lazy-loaded components // Lazy-loaded components
const Authentication = lazy(() => import("../../Pages/Authentication")); const Authentication = lazy(() => import("../../Pages/Authentication"));
@ -48,6 +49,30 @@ export function AuthenticationRoutes() {
); );
} }
export function VerificationRoutes() {
return (
<Routes>
<Route
path={`${Constants.ROUTE_PATHS.VERIFY}/:state/:emailVerificationId`}
element={
<MySupsenseFallback>
<Verification />
</MySupsenseFallback>
}
/>
<Route
path="*"
element={
<MySupsenseFallback>
<Verification />
</MySupsenseFallback>
}
/>
</Routes>
);
}
export function AppRoutes({ setUserSession }) { export function AppRoutes({ setUserSession }) {
return ( return (
<Routes> <Routes>

View File

@ -9,6 +9,7 @@ export default function MyModal({
onCancel, onCancel,
footer = <MyModalOnlyCloseButtonFooter onCancel={onCancel} />, footer = <MyModalOnlyCloseButtonFooter onCancel={onCancel} />,
title, title,
closable = true,
}) { }) {
const screenBreakpoint = useBreakpoint(); const screenBreakpoint = useBreakpoint();
@ -21,6 +22,7 @@ export default function MyModal({
footer={footer} footer={footer}
centered={screenBreakpoint.xs} centered={screenBreakpoint.xs}
title={title} title={title}
closable={closable}
> >
{children} {children}
</Modal> </Modal>

View File

@ -126,6 +126,7 @@ function Login({ notificationApi }) {
const [step, setStep] = useState(LoginStep.ACCOUNT_NAME); const [step, setStep] = useState(LoginStep.ACCOUNT_NAME);
const [isRequesting, setIsRequesting] = useState(false); const [isRequesting, setIsRequesting] = useState(false);
const recaptchaRef = useRef(null);
const recaptchaValueRef = useRef(null); const recaptchaValueRef = useRef(null);
const [form] = Form.useForm(); const [form] = Form.useForm();
@ -253,7 +254,7 @@ function Login({ notificationApi }) {
]} ]}
> >
<ReCAPTCHA <ReCAPTCHA
ref={recaptchaValueRef} ref={recaptchaRef}
sitekey={process.env.REACT_APP_RECAPTCHA_SITE_KEY} sitekey={process.env.REACT_APP_RECAPTCHA_SITE_KEY}
onChange={(value) => (recaptchaValueRef.current = value)} onChange={(value) => (recaptchaValueRef.current = value)}
/> />
@ -346,7 +347,7 @@ function Login({ notificationApi }) {
.catch((errStatus) => { .catch((errStatus) => {
showErrorNotification(errStatus); showErrorNotification(errStatus);
setIsRequesting(false); setIsRequesting(false);
recaptchaValueRef.current.reset(); recaptchaRef.current.reset();
}); });
}) })
.catch(() => showInputsInvalidNotification(notificationApi, t)); .catch(() => showInputsInvalidNotification(notificationApi, t));

View File

@ -3,7 +3,24 @@ import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Constants } from "../../utils"; import { Constants } from "../../utils";
export default function PageNotFound() { export function ButtonBackHome({ useHref }) {
const { t } = useTranslation();
return (
<Button
type="primary"
onClick={() => {
if (useHref) {
window.location.href = Constants.DASHBOARD_ADDRESS;
}
}}
>
{t("pageNotFound.buttonBackHome")}
</Button>
);
}
export default function PageNotFound({ useHref = false }) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@ -12,9 +29,13 @@ export default function PageNotFound() {
title={t("pageNotFound.title")} title={t("pageNotFound.title")}
subTitle={t("pageNotFound.subTitle")} subTitle={t("pageNotFound.subTitle")}
extra={ extra={
<Link to={Constants.ROUTE_PATHS.OVERVIEW}> useHref ? (
<Button type="primary">{t("pageNotFound.buttonBackHome")}</Button> <ButtonBackHome useHref={useHref} />
</Link> ) : (
<Link to={Constants.DASHBOARD_ADDRESS}>
{<ButtonBackHome useHref={useHref} />}
</Link>
)
} }
/> />
); );

View File

@ -0,0 +1,104 @@
import { useParams } from "react-router-dom";
import MyModal from "../../Components/MyModal";
import { Button, Flex, Result, Spin, notification } from "antd";
import { useEffect, useState } from "react";
import MyAppLogo from "../../Components/MyAppLogo";
import PageNotFound, { ButtonBackHome } from "../PageNotFound";
import { Constants, myFetch } from "../../utils";
import { useTranslation } from "react-i18next";
import { RequestState } from "../../Components/MyRequestStateItem";
export default function Verification() {
const { state, emailVerificationId } = useParams();
const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const [isRequesting, setIsRequesting] = useState(RequestState.REQUESTING);
const content = [
{
requesting: {
title: t("verification.content.0.requesting.title"),
},
sucess: {
title: t("verification.content.0.success.title"),
extra: (
<Button
type="primary"
onClick={() => {
window.location.href = `${Constants.DASHBOARD_ADDRESS}${Constants.ROUTE_PATHS.AUTHENTICATION.LOGIN}`;
}}
>
{t("verification.content.0.success.button")}
</Button>
),
},
},
];
const parsedState = isNaN(state) ? 0 : parseInt(state);
let elementContent;
if (
state === undefined ||
emailVerificationId === undefined ||
state > content.length - 1
) {
elementContent = <PageNotFound useHref />;
} else if (isRequesting === RequestState.REQUESTING) {
elementContent = (
<Result
title={content[parsedState].requesting.title}
extra={<Spin size="large" />}
/>
);
} else if (isRequesting === RequestState.SUCCESS) {
elementContent = (
<Result
status="success"
title={content[parsedState].sucess.title}
extra={content[parsedState].sucess.extra}
/>
);
} else if (isRequesting === RequestState.FAILED) {
elementContent = (
<Result
status="error"
title={t("verification.error.title")}
subTitle={t("verification.error.description")}
extra={<ButtonBackHome useHref />}
/>
);
} else {
elementContent = <PageNotFound useHref />;
}
useEffect(() => {
if (emailVerificationId === undefined || state === undefined) {
setIsRequesting(false);
return;
}
myFetch({
url: `/user/verify/${state}/${emailVerificationId}`,
notificationApi: notificationApi,
t: t,
})
.then(() => setIsRequesting(RequestState.SUCCESS))
.catch(() => setIsRequesting(RequestState.FAILED));
}, []);
return (
<MyModal isOpen={true} footer={null} closable={false}>
{notificationContextHolder}
<Flex justify="center">
<MyAppLogo height={100} />
</Flex>
{elementContent}
</MyModal>
);
}

View File

@ -7,22 +7,26 @@ import { v4 as uuidv4 } from "uuid";
*/ */
//const wssProtocol = window.location.protocol === "https:" ? "wss://" : "ws://"; //const wssProtocol = window.location.protocol === "https:" ? "wss://" : "ws://";
let dashboardAddress = "";
let apiAddress = ""; let apiAddress = "";
// let staticContentAddress = ""; // let staticContentAddress = "";
// let wsAddress = ""; // let wsAddress = "";
if (window.location.hostname === "localhost" && window.location.port === "") { if (window.location.hostname === "localhost" && window.location.port === "") {
// for docker container testing on localhost // for docker container testing on localhost
dashboardAddress = "http://localhost/";
apiAddress = "http://localhost/api/v1"; apiAddress = "http://localhost/api/v1";
// staticContentAddress = "http://localhost/api/"; // staticContentAddress = "http://localhost/api/";
// wsAddress = "ws://localhost/ws"; // wsAddress = "ws://localhost/ws";
} else if (window.location.hostname === "localhost") { } else if (window.location.hostname === "localhost") {
// programming on localhost // programming on localhost
dashboardAddress = `http://localhost:${window.location.port}/`;
apiAddress = "http://localhost:50128/api/v1"; apiAddress = "http://localhost:50128/api/v1";
// staticContentAddress = "http://localhost:50050/"; // staticContentAddress = "http://localhost:50050/";
// wsAddress = "ws://localhost:50050/ws"; // wsAddress = "ws://localhost:50050/ws";
} else { } else {
// production // production
dashboardAddress = `${window.location.protocol}//${window.location.hostname}/`;
apiAddress = `${window.location.protocol}//${window.location.hostname}/api/v1`; apiAddress = `${window.location.protocol}//${window.location.hostname}/api/v1`;
//staticContentAddress = `${window.location.protocol}//${window.location.hostname}/api/`; //staticContentAddress = `${window.location.protocol}//${window.location.hostname}/api/`;
// wsAddress = `${wssProtocol}${window.location.hostname}/ws`; // wsAddress = `${wssProtocol}${window.location.hostname}/ws`;
@ -30,6 +34,7 @@ if (window.location.hostname === "localhost" && window.location.port === "") {
export const Constants = { export const Constants = {
TEXT_EMPTY_PLACEHOLDER: "-/-", TEXT_EMPTY_PLACEHOLDER: "-/-",
DASHBOARD_ADDRESS: dashboardAddress,
API_ADDRESS: apiAddress, API_ADDRESS: apiAddress,
//STATIC_CONTENT_ADDRESS: staticContentAddress, //STATIC_CONTENT_ADDRESS: staticContentAddress,
// WS_ADDRESS: wsAddress, // WS_ADDRESS: wsAddress,
@ -42,6 +47,7 @@ export const Constants = {
LOGIN: "/login", LOGIN: "/login",
SIGN_UP: "/signup", SIGN_UP: "/signup",
}, },
VERIFY: "/verify",
OVERVIEW: "/", OVERVIEW: "/",
STORE: { STORE: {
// schema: /store/:storeId/:subPage // schema: /store/:storeId/:subPage