Improved authentication screens (Login/Logout/Forgot Password) (#1627)
This commit is contained in:
parent
5aaf702e2b
commit
e06edadda5
6 changed files with 218 additions and 229 deletions
|
@ -100,18 +100,15 @@ export const PasswordField = forwardRef<HTMLInputElement, InputFieldProps>(funct
|
||||||
return <InputField type="password" placeholder="•••••••••••••" ref={ref} {...props} />;
|
return <InputField type="password" placeholder="•••••••••••••" ref={ref} {...props} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const EmailInput = forwardRef<HTMLInputElement, JSX.IntrinsicElements["input"]>(function EmailInput(
|
export const EmailInput = forwardRef<HTMLInputElement, InputFieldProps>(function EmailInput(props, ref) {
|
||||||
props,
|
|
||||||
ref
|
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<input
|
<Input
|
||||||
|
ref={ref}
|
||||||
type="email"
|
type="email"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
inputMode="email"
|
inputMode="email"
|
||||||
ref={ref}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
40
components/ui/AuthContainer.tsx
Normal file
40
components/ui/AuthContainer.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import Loader from "@components/Loader";
|
||||||
|
import { HeadSeo } from "@components/seo/head-seo";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
footerText?: React.ReactNode | string;
|
||||||
|
showLogo?: boolean;
|
||||||
|
heading?: string;
|
||||||
|
loading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AuthContainer(props: React.PropsWithChildren<Props>) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col justify-center min-h-screen py-12 bg-neutral-50 sm:px-6 lg:px-8">
|
||||||
|
<HeadSeo title={props.title} description={props.description} />
|
||||||
|
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
|
{props.showLogo && (
|
||||||
|
<img className="h-6 mx-auto" src="/calendso-logo-white-word.svg" alt="Cal.com Logo" />
|
||||||
|
)}
|
||||||
|
{props.heading && (
|
||||||
|
<h2 className="mt-6 text-3xl font-bold text-center font-cal text-neutral-900">{props.heading}</h2>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{props.loading && (
|
||||||
|
<div className="absolute z-50 flex items-center w-full h-screen bg-gray-50">
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
|
<div className="px-4 py-8 mx-2 bg-white border rounded-sm sm:px-10 border-neutral-200">
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 text-sm text-center text-neutral-600">{props.footerText}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,13 +1,14 @@
|
||||||
import debounce from "lodash/debounce";
|
import debounce from "lodash/debounce";
|
||||||
import { GetServerSidePropsContext } from "next";
|
import { GetServerSidePropsContext } from "next";
|
||||||
import { getCsrfToken } from "next-auth/react";
|
import { getCsrfToken } from "next-auth/react";
|
||||||
|
import Link from "next/link";
|
||||||
import React, { SyntheticEvent } from "react";
|
import React, { SyntheticEvent } from "react";
|
||||||
|
|
||||||
import { getSession } from "@lib/auth";
|
import { getSession } from "@lib/auth";
|
||||||
import { useLocale } from "@lib/hooks/useLocale";
|
import { useLocale } from "@lib/hooks/useLocale";
|
||||||
|
|
||||||
import { EmailField } from "@components/form/fields";
|
import { EmailField } from "@components/form/fields";
|
||||||
import { HeadSeo } from "@components/seo/head-seo";
|
import AuthContainer from "@components/ui/AuthContainer";
|
||||||
import Button from "@components/ui/Button";
|
import Button from "@components/ui/Button";
|
||||||
|
|
||||||
export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
||||||
|
@ -72,64 +73,56 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
|
||||||
const Success = () => {
|
const Success = () => {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="mt-6 text-3xl font-extrabold text-center text-gray-900">{t("done")}</h2>
|
<p className="text-center">{t("check_email_reset_password")}</p>
|
||||||
<p>{t("check_email_reset_password")}</p>
|
{error && <p className="text-center text-red-600">{error.message}</p>}
|
||||||
{error && <p className="text-red-600">{error.message}</p>}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center min-h-screen py-12 bg-gray-50 sm:px-6 lg:px-8">
|
<AuthContainer
|
||||||
<HeadSeo title={t("forgot_password")} description={t("request_password_reset")} />
|
title={t("forgot_password")}
|
||||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
description={t("request_password_reset")}
|
||||||
<div className="px-4 pt-3 pb-8 mx-2 space-y-6 bg-white rounded-lg shadow sm:px-10">
|
heading={t("forgot_password")}
|
||||||
{success && <Success />}
|
footerText={
|
||||||
{!success && (
|
<>
|
||||||
<>
|
{t("already_have_an_account")}{" "}
|
||||||
<div className="space-y-6">
|
<Link href="/auth/login">
|
||||||
<h2 className="mt-6 text-3xl font-extrabold text-center text-gray-900 font-cal">
|
<a className="font-medium text-neutral-900">{t("login_instead")}</a>
|
||||||
{t("forgot_password")}
|
</Link>
|
||||||
</h2>
|
</>
|
||||||
<p className="text-sm text-gray-500">{t("reset_instructions")}</p>
|
}>
|
||||||
{error && <p className="text-red-600">{error.message}</p>}
|
{success && <Success />}
|
||||||
</div>
|
{!success && (
|
||||||
<form className="space-y-6" onSubmit={handleSubmit} action="#">
|
<>
|
||||||
<input name="csrfToken" type="hidden" defaultValue={csrfToken} hidden />
|
<div className="space-y-6">
|
||||||
|
<p className="mb-4 text-sm text-gray-500">{t("reset_instructions")}</p>
|
||||||
<EmailField
|
{error && <p className="text-red-600">{error.message}</p>}
|
||||||
onChange={handleChange}
|
</div>
|
||||||
id="email"
|
<form className="space-y-6" onSubmit={handleSubmit} action="#">
|
||||||
name="email"
|
<input name="csrfToken" type="hidden" defaultValue={csrfToken} hidden />
|
||||||
label={t("email_address")}
|
<EmailField
|
||||||
placeholder="john.doe@example.com"
|
onChange={handleChange}
|
||||||
required
|
id="email"
|
||||||
/>
|
name="email"
|
||||||
<div className="space-y-2">
|
label={t("email_address")}
|
||||||
<Button
|
placeholder="john.doe@example.com"
|
||||||
className="justify-center w-full"
|
required
|
||||||
type="submit"
|
/>
|
||||||
disabled={loading}
|
<div className="space-y-2">
|
||||||
aria-label={t("request_password_reset")}
|
<Button
|
||||||
loading={loading}>
|
className="justify-center w-full"
|
||||||
{t("request_password_reset")}
|
type="submit"
|
||||||
</Button>
|
disabled={loading}
|
||||||
|
aria-label={t("request_password_reset")}
|
||||||
<Button
|
loading={loading}>
|
||||||
href="/auth/login"
|
{t("request_password_reset")}
|
||||||
color="minimal"
|
</Button>
|
||||||
role="button"
|
</div>
|
||||||
aria-label={t("login_instead")}
|
</form>
|
||||||
className="justify-center w-full">
|
</>
|
||||||
{t("login_instead")}
|
)}
|
||||||
</Button>
|
</AuthContainer>
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,9 @@ import { trpc } from "@lib/trpc";
|
||||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
import AddToHomescreen from "@components/AddToHomescreen";
|
import AddToHomescreen from "@components/AddToHomescreen";
|
||||||
import Loader from "@components/Loader";
|
import { EmailField, PasswordField, TextField } from "@components/form/fields";
|
||||||
import { EmailInput } from "@components/form/fields";
|
import AuthContainer from "@components/ui/AuthContainer";
|
||||||
import { HeadSeo } from "@components/seo/head-seo";
|
import Button from "@components/ui/Button";
|
||||||
|
|
||||||
import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
|
import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
@ -97,150 +97,123 @@ export default function Login({
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center min-h-screen py-12 bg-neutral-50 sm:px-6 lg:px-8">
|
<>
|
||||||
<HeadSeo title={t("login")} description={t("login")} />
|
<AuthContainer
|
||||||
|
title={t("login")}
|
||||||
{isSubmitting && (
|
description={t("login")}
|
||||||
<div className="absolute z-50 flex items-center w-full h-screen bg-gray-50">
|
loading={isSubmitting}
|
||||||
<Loader />
|
showLogo
|
||||||
</div>
|
heading={t("sign_in_account")}
|
||||||
)}
|
footerText={
|
||||||
|
<>
|
||||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
{t("dont_have_an_account")} {/* replace this with your account creation flow */}
|
||||||
<img className="h-6 mx-auto" src="/calendso-logo-white-word.svg" alt="Cal.com Logo" />
|
<a href={`${WEBSITE_URL}/signup`} className="font-medium text-neutral-900">
|
||||||
<h2 className="mt-6 text-3xl font-bold text-center font-cal text-neutral-900">
|
{t("create_an_account")}
|
||||||
{t("sign_in_account")}
|
</a>
|
||||||
</h2>
|
</>
|
||||||
</div>
|
}>
|
||||||
|
<form className="space-y-6" onSubmit={handleSubmit}>
|
||||||
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
<input name="csrfToken" type="hidden" defaultValue={csrfToken || undefined} hidden />
|
||||||
<div className="px-4 py-8 mx-2 bg-white border rounded-sm sm:px-10 border-neutral-200">
|
<div>
|
||||||
<form className="space-y-6" onSubmit={handleSubmit}>
|
<label htmlFor="email" className="block text-sm font-medium text-neutral-700">
|
||||||
<input name="csrfToken" type="hidden" defaultValue={csrfToken || undefined} hidden />
|
{t("email_address")}
|
||||||
<div>
|
</label>
|
||||||
<label htmlFor="email" className="block text-sm font-medium text-neutral-700">
|
<div className="mt-1">
|
||||||
{t("email_address")}
|
<EmailField
|
||||||
</label>
|
id="email"
|
||||||
<div className="mt-1">
|
name="email"
|
||||||
<EmailInput
|
placeholder="john.doe@example.com"
|
||||||
id="email"
|
required
|
||||||
name="email"
|
value={email}
|
||||||
required
|
onInput={(e) => setEmail(e.currentTarget.value)}
|
||||||
value={email}
|
/>
|
||||||
onInput={(e) => setEmail(e.currentTarget.value)}
|
|
||||||
className="block w-full px-3 py-2 placeholder-gray-400 border rounded-sm shadow-sm appearance-none border-neutral-300 focus:outline-none focus:ring-neutral-900 focus:border-neutral-900 sm:text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="relative">
|
||||||
<div className="flex">
|
<div className="absolute right-0 -top-[2px]">
|
||||||
<div className="w-1/2">
|
<Link href="/auth/forgot-password">
|
||||||
<label htmlFor="password" className="block text-sm font-medium text-neutral-700">
|
<a tabIndex={-1} className="text-sm font-medium text-primary-600">
|
||||||
{t("password")}
|
{t("forgot")}
|
||||||
</label>
|
</a>
|
||||||
</div>
|
</Link>
|
||||||
<div className="w-1/2 text-right">
|
|
||||||
<Link href="/auth/forgot-password">
|
|
||||||
<a tabIndex={-1} className="text-sm font-medium text-primary-600">
|
|
||||||
{t("forgot")}
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-1">
|
|
||||||
<input
|
|
||||||
id="password"
|
|
||||||
name="password"
|
|
||||||
type="password"
|
|
||||||
autoComplete="current-password"
|
|
||||||
required
|
|
||||||
value={password}
|
|
||||||
onInput={(e) => setPassword(e.currentTarget.value)}
|
|
||||||
className="block w-full px-3 py-2 placeholder-gray-400 border rounded-sm shadow-sm appearance-none border-neutral-300 focus:outline-none focus:ring-neutral-900 focus:border-neutral-900 sm:text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<PasswordField
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
autoComplete="current-password"
|
||||||
|
required
|
||||||
|
value={password}
|
||||||
|
onInput={(e) => setPassword(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{secondFactorRequired && (
|
{secondFactorRequired && (
|
||||||
<div>
|
<TextField
|
||||||
<label htmlFor="email" className="block text-sm font-medium text-neutral-700">
|
className="mt-1"
|
||||||
{t("2fa_code")}
|
id="totpCode"
|
||||||
</label>
|
name={t("2fa_code")}
|
||||||
<div className="mt-1">
|
type="text"
|
||||||
<input
|
maxLength={6}
|
||||||
id="totpCode"
|
minLength={6}
|
||||||
name="totpCode"
|
inputMode="numeric"
|
||||||
type="text"
|
value={code}
|
||||||
maxLength={6}
|
onInput={(e) => setCode(e.currentTarget.value)}
|
||||||
minLength={6}
|
/>
|
||||||
inputMode="numeric"
|
|
||||||
value={code}
|
|
||||||
onInput={(e) => setCode(e.currentTarget.value)}
|
|
||||||
className="block w-full px-3 py-2 placeholder-gray-400 border rounded-sm shadow-sm appearance-none border-neutral-300 focus:outline-none focus:ring-neutral-900 focus:border-neutral-900 sm:text-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={isSubmitting}
|
|
||||||
className="flex justify-center w-full px-4 py-2 text-sm font-medium text-white border border-transparent rounded-sm shadow-sm bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
|
|
||||||
{t("sign_in")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
|
|
||||||
</form>
|
|
||||||
{isGoogleLoginEnabled && (
|
|
||||||
<div style={{ marginTop: "12px" }}>
|
|
||||||
<button
|
|
||||||
data-testid={"google"}
|
|
||||||
onClick={async () => await signIn("google")}
|
|
||||||
className="flex justify-center w-full px-4 py-2 text-sm font-medium text-black border border-transparent rounded-sm shadow-sm bg-secondary-50 hover:bg-secondary-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
|
|
||||||
{t("signin_with_google")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{isSAMLLoginEnabled && (
|
|
||||||
<div style={{ marginTop: "12px" }}>
|
|
||||||
<button
|
|
||||||
data-testid={"saml"}
|
|
||||||
onClick={async (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (!hostedCal) {
|
<div className="flex space-y-2">
|
||||||
await signIn("saml", {}, { tenant: samlTenantID, product: samlProductID });
|
<Button className="flex justify-center w-full" type="submit" disabled={isSubmitting}>
|
||||||
} else {
|
{t("sign_in")}
|
||||||
if (email.length === 0) {
|
</Button>
|
||||||
setErrorMessage(t("saml_email_required"));
|
</div>
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// hosted solution, fetch tenant and product from the backend
|
{errorMessage && <p className="mt-1 text-sm text-red-700">{errorMessage}</p>}
|
||||||
mutation.mutate({
|
</form>
|
||||||
email,
|
{isGoogleLoginEnabled && (
|
||||||
});
|
<div style={{ marginTop: "12px" }}>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
className="flex justify-center w-full"
|
||||||
|
data-testid={"google"}
|
||||||
|
onClick={async () => await signIn("google")}>
|
||||||
|
{" "}
|
||||||
|
{t("signin_with_google")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isSAMLLoginEnabled && (
|
||||||
|
<div style={{ marginTop: "12px" }}>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
data-testid={"saml"}
|
||||||
|
className="flex justify-center w-full"
|
||||||
|
onClick={async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!hostedCal) {
|
||||||
|
await signIn("saml", {}, { tenant: samlTenantID, product: samlProductID });
|
||||||
|
} else {
|
||||||
|
if (email.length === 0) {
|
||||||
|
setErrorMessage(t("saml_email_required"));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}}
|
|
||||||
className="flex justify-center w-full px-4 py-2 text-sm font-medium text-black border border-transparent rounded-sm shadow-sm bg-secondary-50 hover:bg-secondary-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
|
// hosted solution, fetch tenant and product from the backend
|
||||||
{t("signin_with_saml")}
|
mutation.mutate({
|
||||||
</button>
|
email,
|
||||||
</div>
|
});
|
||||||
)}
|
}
|
||||||
</div>
|
}}>
|
||||||
<div className="mt-4 text-sm text-center text-neutral-600">
|
{t("signin_with_saml")}
|
||||||
{t("dont_have_an_account")} {/* replace this with your account creation flow */}
|
</Button>
|
||||||
<a href={`${WEBSITE_URL}/signup`} className="font-medium text-neutral-900">
|
</div>
|
||||||
{t("create_an_account")}
|
)}
|
||||||
</a>
|
</AuthContainer>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<AddToHomescreen />
|
<AddToHomescreen />
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@ import { useEffect } from "react";
|
||||||
import { useLocale } from "@lib/hooks/useLocale";
|
import { useLocale } from "@lib/hooks/useLocale";
|
||||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
import { HeadSeo } from "@components/seo/head-seo";
|
import AuthContainer from "@components/ui/AuthContainer";
|
||||||
|
import Button from "@components/ui/Button";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
@ -23,40 +24,24 @@ export default function Logout(props: Props) {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<AuthContainer title={t("logged_out")} description={t("youve_been_logged_out")}>
|
||||||
className="fixed inset-0 z-50 overflow-y-auto"
|
<div className="mb-4">
|
||||||
aria-labelledby="modal-title"
|
<div className="flex items-center justify-center w-12 h-12 mx-auto bg-green-100 rounded-full">
|
||||||
role="dialog"
|
<CheckIcon className="w-6 h-6 text-green-600" />
|
||||||
aria-modal="true">
|
</div>
|
||||||
<HeadSeo title={t("logged_out")} description={t("logged_out")} />
|
<div className="mt-3 text-center sm:mt-5">
|
||||||
<div className="flex items-end justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
|
||||||
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
|
{t("youve_been_logged_out")}
|
||||||
​
|
</h3>
|
||||||
</span>
|
<div className="mt-2">
|
||||||
<div className="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white rounded-lg shadow-xl sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6">
|
<p className="text-sm text-gray-500">{t("hope_to_see_you_soon")}</p>
|
||||||
<div>
|
|
||||||
<div className="flex items-center justify-center w-12 h-12 mx-auto bg-green-100 rounded-full">
|
|
||||||
<CheckIcon className="w-6 h-6 text-green-600" />
|
|
||||||
</div>
|
|
||||||
<div className="mt-3 text-center sm:mt-5">
|
|
||||||
<h3 className="text-lg font-medium leading-6 text-gray-900" id="modal-title">
|
|
||||||
{t("youve_been_logged_out")}
|
|
||||||
</h3>
|
|
||||||
<div className="mt-2">
|
|
||||||
<p className="text-sm text-gray-500">{t("hope_to_see_you_soon")}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-5 sm:mt-6">
|
|
||||||
<Link href="/auth/login">
|
|
||||||
<a className="inline-flex justify-center w-full px-4 py-2 text-base font-medium border border-transparent rounded-md shadow-sm bg-brand text-brandcontrast focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black sm:text-sm">
|
|
||||||
{t("go_back_login")}
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Link href="/auth/login">
|
||||||
|
<Button className="flex justify-center w-full"> {t("go_back_login")}</Button>
|
||||||
|
</Link>
|
||||||
|
</AuthContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -158,6 +158,7 @@
|
||||||
"30min_meeting": "30 Min Meeting",
|
"30min_meeting": "30 Min Meeting",
|
||||||
"secret_meeting": "Secret Meeting",
|
"secret_meeting": "Secret Meeting",
|
||||||
"login_instead": "Login instead",
|
"login_instead": "Login instead",
|
||||||
|
"already_have_an_account": "Already have an account?",
|
||||||
"create_account": "Create Account",
|
"create_account": "Create Account",
|
||||||
"confirm_password": "Confirm password",
|
"confirm_password": "Confirm password",
|
||||||
"create_your_account": "Create your account",
|
"create_your_account": "Create your account",
|
||||||
|
|
Loading…
Reference in a new issue