UI tweaks (#3576)
This commit is contained in:
@@ -10,8 +10,10 @@ import slugify from "@calcom/lib/slugify";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
const querySchema = z.object({
|
||||
username: z.string().min(1),
|
||||
full_name: z.string(),
|
||||
username: z
|
||||
.string()
|
||||
.refine((val) => val.trim().length >= 1, { message: "Please enter at least one character" }),
|
||||
full_name: z.string().min(3, "Please enter at least 3 characters"),
|
||||
email_address: z.string().email({ message: "Please enter a valid email" }),
|
||||
password: z.string().refine((val) => isPasswordValid(val.trim()), {
|
||||
message:
|
||||
@@ -30,7 +32,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
throw new HttpError({ statusCode: 422, message: parsedQuery.error.message });
|
||||
}
|
||||
|
||||
const username = slugify(parsedQuery.data.username);
|
||||
const username = slugify(parsedQuery.data.username.trim());
|
||||
const userEmail = parsedQuery.data.email_address.toLowerCase();
|
||||
|
||||
const hashedPassword = await hashPassword(parsedQuery.data.password);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CheckIcon } from "@heroicons/react/solid";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useRouter } from "next/router";
|
||||
import { useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
@@ -32,11 +32,22 @@ const SetupFormStep1 = (props: { setIsLoading: (val: boolean) => void }) => {
|
||||
const { t } = useLocale();
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.string().min(1, t("at_least_characters", { count: 1 })),
|
||||
username: z
|
||||
.string()
|
||||
.refine((val) => val.trim().length >= 1, { message: t("at_least_characters", { count: 1 }) }),
|
||||
email_address: z.string().email({ message: t("enter_valid_email") }),
|
||||
full_name: z.string().min(3, t("at_least_characters", { count: 3 })),
|
||||
password: z.string().refine((val) => isPasswordValid(val.trim()), {
|
||||
message: t("invalid_password_hint"),
|
||||
password: z.string().superRefine((data, ctx) => {
|
||||
const result = isPasswordValid(data, true);
|
||||
Object.keys(result).map((key: string) => {
|
||||
if (!result[key as keyof typeof result]) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: [key],
|
||||
message: t(`password_hint_${key}`),
|
||||
});
|
||||
}
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -49,8 +60,6 @@ const SetupFormStep1 = (props: { setIsLoading: (val: boolean) => void }) => {
|
||||
resolver: zodResolver(formSchema),
|
||||
});
|
||||
|
||||
const formRef = useRef(null);
|
||||
|
||||
const onError = () => {
|
||||
props.setIsLoading(false);
|
||||
};
|
||||
@@ -60,7 +69,7 @@ const SetupFormStep1 = (props: { setIsLoading: (val: boolean) => void }) => {
|
||||
const response = await fetch("/api/auth/setup", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
username: data.username,
|
||||
username: data.username.trim(),
|
||||
full_name: data.full_name,
|
||||
email_address: data.email_address.toLowerCase(),
|
||||
password: data.password,
|
||||
@@ -78,7 +87,7 @@ const SetupFormStep1 = (props: { setIsLoading: (val: boolean) => void }) => {
|
||||
|
||||
return (
|
||||
<FormProvider {...formMethods}>
|
||||
<form id="setup-step-1" name="setup-step-1" className="space-y-4" onSubmit={onSubmit} ref={formRef}>
|
||||
<form id="setup-step-1" name="setup-step-1" className="space-y-4" onSubmit={onSubmit}>
|
||||
<div>
|
||||
<Controller
|
||||
name="username"
|
||||
@@ -140,7 +149,6 @@ const SetupFormStep1 = (props: { setIsLoading: (val: boolean) => void }) => {
|
||||
formMethods.setValue("email_address", e.target.value);
|
||||
await formMethods.trigger("email_address");
|
||||
}}
|
||||
defaultValue={router.query.email_address}
|
||||
className="my-0"
|
||||
name="email_address"
|
||||
/>
|
||||
@@ -160,6 +168,7 @@ const SetupFormStep1 = (props: { setIsLoading: (val: boolean) => void }) => {
|
||||
formMethods.setValue("password", e.target.value);
|
||||
await formMethods.trigger("password");
|
||||
}}
|
||||
hintErrors={["caplow", "min", "num"]}
|
||||
name="password"
|
||||
className="my-0"
|
||||
autoComplete="off"
|
||||
|
||||
@@ -405,6 +405,9 @@
|
||||
"new_password_matches_old_password": "New password matches your old password. Please choose a different password.",
|
||||
"forgotten_secret_description": "If you have lost or forgotten this secret, you can change it, but be aware that all integrations using this secret will need to be updated",
|
||||
"current_incorrect_password": "Current password is incorrect",
|
||||
"password_hint_caplow": "Mix of uppercase & lowercase letters",
|
||||
"password_hint_min": "Minimum 7 characters long",
|
||||
"password_hint_num": "Contain at least 1 number",
|
||||
"invalid_password_hint": "The password must be a minimum of 7 characters long containing at least one number and have a mixture of uppercase and lowercase letters",
|
||||
"incorrect_password": "Password is incorrect.",
|
||||
"1_on_1": "1-on-1",
|
||||
@@ -436,7 +439,7 @@
|
||||
"additional_guests": "+ Additional Guests",
|
||||
"your_name": "Your name",
|
||||
"email_address": "Email address",
|
||||
"valid_email_address": "Please enter a valid email",
|
||||
"enter_valid_email": "Please enter a valid email",
|
||||
"location": "Location",
|
||||
"yes": "yes",
|
||||
"no": "no",
|
||||
|
||||
+10
-7
@@ -19,11 +19,11 @@ export async function getSession(options: GetSessionParams): Promise<Session | n
|
||||
return session as Session | null;
|
||||
}
|
||||
|
||||
export function isPasswordValid(password: string) {
|
||||
let cap = false,
|
||||
low = false,
|
||||
num = false,
|
||||
min = false;
|
||||
export const isPasswordValid = ((password: string, breakdown?: boolean) => {
|
||||
let cap = false, // Has uppercase characters
|
||||
low = false, // Has lowercase characters
|
||||
num = false, // At least one number
|
||||
min = false; // Seven characters
|
||||
if (password.length > 6) min = true;
|
||||
for (let i = 0; i < password.length; i++) {
|
||||
if (!isNaN(parseInt(password[i]))) num = true;
|
||||
@@ -32,5 +32,8 @@ export function isPasswordValid(password: string) {
|
||||
if (password[i] === password[i].toLowerCase()) low = true;
|
||||
}
|
||||
}
|
||||
return cap && low && num && min;
|
||||
}
|
||||
return !!breakdown ? { caplow: cap && low, num, min } : cap && low && num && min;
|
||||
}) as {
|
||||
(password: string): boolean;
|
||||
(password: string, breakdown: boolean): { caplow: boolean; num: boolean; min: boolean };
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ function WizardForm<T extends DefaultStep>(props: { href: string; steps: T[]; co
|
||||
</div>
|
||||
<div className="print:p-none px-4 py-5 sm:p-6">{currentStep.content}</div>
|
||||
{currentStep.enabled !== false && (
|
||||
<div className="px-4 py-4 print:hidden sm:px-6">
|
||||
<div className="flex justify-end px-4 py-4 print:hidden sm:px-6">
|
||||
{step > 1 && (
|
||||
<Button
|
||||
color="secondary"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useId } from "@radix-ui/react-id";
|
||||
import React, { forwardRef, ReactElement, ReactNode, Ref } from "react";
|
||||
import { AlertCircle, Icon } from "react-feather";
|
||||
import { Info, Circle, Check, X } from "react-feather";
|
||||
import { FieldValues, FormProvider, SubmitHandler, useFormContext, UseFormReturn } from "react-hook-form";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
@@ -44,6 +44,7 @@ export function InputLeading(props: JSX.IntrinsicElements["div"]) {
|
||||
type InputFieldProps = {
|
||||
label?: ReactNode;
|
||||
hint?: ReactNode;
|
||||
hintErrors?: string[];
|
||||
addOnLeading?: ReactNode;
|
||||
addOnSuffix?: ReactNode;
|
||||
addOnFilled?: boolean;
|
||||
@@ -70,6 +71,7 @@ const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputF
|
||||
addOnSuffix,
|
||||
addOnFilled = true,
|
||||
hint,
|
||||
hintErrors,
|
||||
labelSrOnly,
|
||||
containerClassName,
|
||||
...passThrough
|
||||
@@ -81,7 +83,7 @@ const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputF
|
||||
<Label
|
||||
htmlFor={id}
|
||||
{...labelProps}
|
||||
className={classNames(labelSrOnly && "sr-only", props.error && "text-red-900", "pb-1")}>
|
||||
className={classNames(labelSrOnly && "sr-only", props.error && "text-red-900", "mb-2")}>
|
||||
{label}
|
||||
</Label>
|
||||
)}
|
||||
@@ -122,15 +124,59 @@ const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputF
|
||||
) : (
|
||||
<Input id={id} placeholder={placeholder} className={className} {...passThrough} ref={ref} />
|
||||
)}
|
||||
{hint && (
|
||||
<div className="text-gray flex items-center text-sm text-gray-700">
|
||||
<AlertCircle className="mr-1 h-3 w-3" />
|
||||
{hint}
|
||||
{methods?.formState?.errors[props.name] && methods?.formState?.errors[props.name].message && (
|
||||
<div className="text-gray mt-2 flex items-center text-sm text-red-700">
|
||||
<Info className="mr-1 h-3 w-3" />
|
||||
{methods.formState.errors[props.name].message}
|
||||
</div>
|
||||
)}
|
||||
{methods?.formState?.errors[props.name] && (
|
||||
<Alert className="mt-1" severity="error" message={methods.formState.errors[props.name].message} />
|
||||
{hintErrors && (
|
||||
<div className="text-gray mt-2 flex items-center text-sm text-gray-700">
|
||||
{methods?.formState?.errors[props.name] !== undefined ? (
|
||||
<ul className="then ml-2">
|
||||
{hintErrors.map((errorKey) => {
|
||||
const error =
|
||||
methods?.formState?.errors[props.name][errorKey] ||
|
||||
methods?.formState?.errors[props.name].message;
|
||||
const submitted = methods?.formState.isSubmitted;
|
||||
return (
|
||||
<li
|
||||
key={errorKey}
|
||||
className={error !== undefined ? (submitted ? "text-red-700" : "") : "text-green-600"}>
|
||||
{error !== undefined ? (
|
||||
submitted ? (
|
||||
<X size="12" strokeWidth="3" className="-ml-1 mr-2 inline-block" />
|
||||
) : (
|
||||
<Circle fill="currentColor" size="5" className="mr-2 inline-block" />
|
||||
)
|
||||
) : (
|
||||
<Check size="12" strokeWidth="3" className="-ml-1 mr-2 inline-block" />
|
||||
)}
|
||||
{error?.message || t(`${props.name}_hint_${errorKey}`)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
) : (
|
||||
<ul className="otherwise ml-2">
|
||||
{hintErrors.map((errorKey) => {
|
||||
const dirty = methods?.formState.dirtyFields[props.name];
|
||||
return (
|
||||
<li key={errorKey} className={!!dirty ? "text-green-600" : ""}>
|
||||
{!!dirty ? (
|
||||
<Check size="12" strokeWidth="3" className="-ml-1 mr-2 inline-block" />
|
||||
) : (
|
||||
<Circle fill="currentColor" size="5" className="mr-2 inline-block" />
|
||||
)}
|
||||
{t(`${props.name}_hint_${errorKey}`)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{hint && <div className="text-gray mt-2 flex items-center text-sm text-gray-700">{hint}</div>}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user