Skip to content

Commit

Permalink
Feat/3796 new UI for signin (#4369)
Browse files Browse the repository at this point in the history
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
3 people committed Sep 13, 2022
1 parent 6c4a372 commit 404168f
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 81 deletions.
9 changes: 8 additions & 1 deletion apps/storybook/stories/Input.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { ComponentMeta, ComponentStory } from "@storybook/react";
import { Copy } from "react-feather";

import { TextAreaField, TextField } from "@calcom/ui/v2/core/form/fields";
import { TextAreaField, TextField, PasswordField } from "@calcom/ui/v2/core/form/fields";
import DatePicker from "@calcom/ui/v2/modules/booker/DatePicker";

export default {
Expand Down Expand Up @@ -66,3 +67,9 @@ export const TextAreaInput: ComponentStory<typeof TextAreaField> = () => (
);

export const DatePickerInput: ComponentStory<typeof DatePicker> = () => <DatePicker date={new Date()} />;

export const PasswordInput: ComponentStory<typeof PasswordField> = () => (
<TooltipProvider>
<PasswordField />
</TooltipProvider>
);
4 changes: 2 additions & 2 deletions apps/web/components/v2/ui/AuthContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
</div>
)}
<div className="mb-auto mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="border-1 mx-2 rounded-md border-gray-200 bg-white px-4 py-8 sm:px-10">
<div className="border-1 mx-2 rounded-md border-gray-200 bg-white px-4 py-10 sm:px-10">
{props.children}
</div>
<div className="mt-4 text-center text-sm text-neutral-600">{props.footerText}</div>
<div className="mt-8 text-center text-sm text-neutral-600">{props.footerText}</div>
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions apps/web/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const V2_WHITELIST = [
"/workflows",
"/apps",
"/success",
"/auth/login",
];
const V2_BLACKLIST = [
//
Expand Down
100 changes: 49 additions & 51 deletions apps/web/pages/v2/auth/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { FaGoogle } from "react-icons/fa";

import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import prisma from "@calcom/prisma";
import { Icon } from "@calcom/ui";
import { Alert } from "@calcom/ui/Alert";
import { Icon } from "@calcom/ui/Icon";
import { Button, EmailField, Form, PasswordField } from "@calcom/ui/v2";
import SAMLLogin from "@calcom/ui/v2/modules/auth/SAMLLogin";

Expand All @@ -22,7 +23,7 @@ import { inferSSRProps } from "@lib/types/inferSSRProps";

import AddToHomescreen from "@components/AddToHomescreen";
import TwoFactor from "@components/auth/TwoFactor";
import AuthContainer from "@components/ui/AuthContainer";
import AuthContainer from "@components/v2/ui/AuthContainer";

import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
import { ssrInit } from "@server/lib/ssr";
Expand Down Expand Up @@ -76,12 +77,9 @@ export default function Login({
callbackUrl = safeCallbackUrl || "";

const LoginFooter = (
<span className="text-gray-600">
{t("dont_have_an_account")}{" "}
<a href={`${WEBSITE_URL}/signup`} className="text-brand-500 font-medium">
{t("create_an_account")}
</a>
</span>
<a href={`${WEBSITE_URL}/signup`} className="text-brand-500 font-medium">
{t("i_dont_have_an_account")}
</a>
);

const TwoFactorFooter = (
Expand All @@ -106,7 +104,6 @@ export default function Login({
footerText={twoFactorRequired ? TwoFactorFooter : LoginFooter}>
<Form
form={form}
className="space-y-6"
handleSubmit={async (values) => {
setErrorMessage(null);
telemetry.event(telemetryEventTypes.login, collectPageParameters());
Expand All @@ -132,51 +129,52 @@ export default function Login({
{...form.register("csrfToken")}
/>
</div>
<div className={classNames("space-y-6", { hidden: twoFactorRequired })}>
<EmailField
id="email"
label={t("email_address")}
defaultValue={router.query.email as string}
placeholder="john.doe@example.com"
required
{...form.register("email")}
/>
<div className="relative">
<div className="absolute right-0 -top-[2px]">
<Link href="/auth/forgot-password">
<a tabIndex={-1} className="text-sm font-medium text-gray-600">
{t("forgot_password")}
</a>
</Link>
</div>
<PasswordField
id="password"
type="password"
autoComplete="current-password"
<div className="space-y-6">
<div className={classNames("space-y-6", { hidden: twoFactorRequired })}>
<EmailField
id="email"
label={t("email_address")}
defaultValue={router.query.email as string}
placeholder="john.doe@example.com"
required
{...form.register("password")}
{...form.register("email")}
/>
<div className="relative">
<div className="absolute right-0 -top-[6px]">
<Link href="/auth/forgot-password">
<a tabIndex={-1} className="text-sm font-medium text-gray-600">
{t("forgot")}
</a>
</Link>
</div>
<PasswordField
id="password"
autoComplete="current-password"
required
className="mb-0"
{...form.register("password")}
/>
</div>
</div>
</div>

{twoFactorRequired && <TwoFactor center />}
{twoFactorRequired && <TwoFactor center />}

{errorMessage && <Alert severity="error" title={errorMessage} />}
<div className="pb-8">
<Button type="submit" color="primary" disabled={isSubmitting} className="w-full">
{errorMessage && <Alert severity="error" title={errorMessage} />}
<Button type="submit" color="primary" disabled={isSubmitting} className="w-full justify-center">
{twoFactorRequired ? t("submit") : t("sign_in")}
</Button>
</div>
</Form>
<hr />
{!twoFactorRequired && (
<>
{isGoogleLoginEnabled && (
<div className="mt-8">
{(isGoogleLoginEnabled || isSAMLLoginEnabled) && <hr className="my-8" />}
<div className="space-y-3">
{isGoogleLoginEnabled && (
<Button
color="secondary"
className="w-full"
className="w-full justify-center"
data-testid="google"
StartIcon={FaGoogle}
onClick={async (e) => {
e.preventDefault();
// track Google logins. Without personal data/payload
Expand All @@ -185,17 +183,17 @@ export default function Login({
}}>
{t("signin_with_google")}
</Button>
</div>
)}
{isSAMLLoginEnabled && (
<SAMLLogin
email={form.getValues("email")}
samlTenantID={samlTenantID}
samlProductID={samlProductID}
hostedCal={hostedCal}
setErrorMessage={setErrorMessage}
/>
)}
)}
{isSAMLLoginEnabled && (
<SAMLLogin
email={form.getValues("email")}
samlTenantID={samlTenantID}
samlProductID={samlProductID}
hostedCal={hostedCal}
setErrorMessage={setErrorMessage}
/>
)}
</div>
</>
)}
</AuthContainer>
Expand Down
3 changes: 3 additions & 0 deletions apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@
"2fa_enter_six_digit_code": "Enter the six-digit code from your authenticator app below.",
"create_an_account": "Create an account",
"dont_have_an_account": "Don't have an account?",
"i_dont_have_an_account": "I don't have an account",
"2fa_code": "Two-Factor Code",
"sign_in_account": "Sign in to your account",
"sign_in": "Sign in",
Expand Down Expand Up @@ -266,6 +267,8 @@
"enter_new_password": "Enter the new password you'd like for your account.",
"reset_password": "Reset Password",
"change_your_password": "Change your password",
"show_password": "Show password",
"hide_password": "Hide password",
"try_again": "Try Again",
"request_is_expired": "That Request is Expired.",
"reset_instructions": "Enter the email address associated with your account and we will send you a link to reset your password.",
Expand Down
39 changes: 36 additions & 3 deletions packages/ui/v2/core/form/fields.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useId } from "@radix-ui/react-id";
import React, { forwardRef, ReactElement, ReactNode, Ref } from "react";
import { Check, Circle, Info, X } from "react-feather";
import React, { forwardRef, ReactElement, ReactNode, Ref, useCallback, useMemo, useState } from "react";
import { Check, Circle, Info, X, Eye, EyeOff } from "react-feather";
import {
FieldErrors,
FieldValues,
Expand All @@ -13,6 +13,7 @@ import {
import classNames from "@calcom/lib/classNames";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Tooltip } from "@calcom/ui/v2";
import showToast from "@calcom/ui/v2/core/notifications";

import { Alert } from "../../../Alert";
Expand Down Expand Up @@ -248,7 +249,39 @@ export const PasswordField = forwardRef<HTMLInputElement, InputFieldProps>(funct
props,
ref
) {
return <InputField type="password" placeholder="•••••••••••••" ref={ref} {...props} />;
const { t } = useLocale();
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const toggleIsPasswordVisible = useCallback(
() => setIsPasswordVisible(!isPasswordVisible),
[isPasswordVisible, setIsPasswordVisible]
);
const textLabel = isPasswordVisible ? t("hide_password") : t("show_password");

return (
<div className="relative">
<InputField
type={isPasswordVisible ? "text" : "password"}
placeholder="•••••••••••••"
ref={ref}
{...props}
className={classNames("pr-10", props.className)}
/>

<Tooltip content={textLabel}>
<button
className="absolute bottom-0 right-3 h-9 text-gray-900"
type="button"
onClick={() => toggleIsPasswordVisible()}>
{isPasswordVisible ? (
<EyeOff className="h-4 stroke-[2.5px]" />
) : (
<Eye className="h-4 stroke-[2.5px]" />
)}
<span className="sr-only">{textLabel}</span>
</button>
</Tooltip>
</div>
);
});

export const EmailInput = forwardRef<HTMLInputElement, InputFieldProps>(function EmailInput(props, ref) {
Expand Down
48 changes: 24 additions & 24 deletions packages/ui/v2/modules/auth/SAMLLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useFormContext } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { trpc } from "@calcom/trpc/react";
import { Icon } from "@calcom/ui";
import Button from "@calcom/ui/v2/core/Button";

interface Props {
Expand All @@ -30,32 +31,31 @@ export default function SAMLLogin(props: Props) {
});

return (
<div className="mt-5">
<Button
color="secondary"
data-testid="saml"
className="flex w-full justify-center"
onClick={async (event) => {
event.preventDefault();
<Button
StartIcon={Icon.FiLock}
color="secondary"
data-testid="saml"
className="flex w-full justify-center"
onClick={async (event) => {
event.preventDefault();

// track Google logins. Without personal data/payload
telemetry.event(telemetryEventTypes.googleLogin, collectPageParameters());
// track Google logins. Without personal data/payload
telemetry.event(telemetryEventTypes.googleLogin, collectPageParameters());

if (!props.hostedCal) {
await signIn("saml", {}, { tenant: props.samlTenantID, product: props.samlProductID });
} else {
if (props.email.length === 0) {
props.setErrorMessage(t("saml_email_required"));
return;
}
// hosted solution, fetch tenant and product from the backend
mutation.mutate({
email: methods.getValues("email"),
});
if (!props.hostedCal) {
await signIn("saml", {}, { tenant: props.samlTenantID, product: props.samlProductID });
} else {
if (props.email.length === 0) {
props.setErrorMessage(t("saml_email_required"));
return;
}
}}>
{t("signin_with_saml")}
</Button>
</div>
// hosted solution, fetch tenant and product from the backend
mutation.mutate({
email: methods.getValues("email"),
});
}
}}>
{t("signin_with_saml")}
</Button>
);
}

0 comments on commit 404168f

Please sign in to comment.