Signup inital work on UI
parent
de5df416df
commit
f6a9e3e645
|
@ -0,0 +1,307 @@
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import type { GetServerSidePropsContext } from "next";
|
||||||
|
import { signIn } from "next-auth/react";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import type { CSSProperties } from "react";
|
||||||
|
import type { SubmitHandler } from "react-hook-form";
|
||||||
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import getStripe from "@calcom/app-store/stripepayment/lib/client";
|
||||||
|
import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername";
|
||||||
|
import { getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||||
|
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
||||||
|
import { useFlagMap } from "@calcom/features/flags/context/provider";
|
||||||
|
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||||
|
import { IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import slugify from "@calcom/lib/slugify";
|
||||||
|
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||||
|
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||||
|
import { signupSchema as apiSignupSchema } from "@calcom/prisma/zod-utils";
|
||||||
|
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||||
|
import { Alert, Button, EmailField, HeadSeo, PasswordField, TextField } from "@calcom/ui";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
|
import { IS_GOOGLE_LOGIN_ENABLED } from "../server/lib/constants";
|
||||||
|
import { ssrInit } from "../server/lib/ssr";
|
||||||
|
|
||||||
|
const signupSchema = apiSignupSchema.extend({
|
||||||
|
apiError: z.string().optional(), // Needed to display API errors doesnt get passed to the API
|
||||||
|
});
|
||||||
|
|
||||||
|
type FormValues = z.infer<typeof signupSchema>;
|
||||||
|
|
||||||
|
type SignupProps = inferSSRProps<typeof getServerSideProps>;
|
||||||
|
|
||||||
|
export default function Signup({ prepopulateFormValues, token, orgSlug }: SignupProps) {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const telemetry = useTelemetry();
|
||||||
|
const { t, i18n } = useLocale();
|
||||||
|
const flags = useFlagMap();
|
||||||
|
const methods = useForm<FormValues>({
|
||||||
|
mode: "onChange",
|
||||||
|
resolver: zodResolver(signupSchema),
|
||||||
|
defaultValues: prepopulateFormValues,
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
formState: { errors, isSubmitting },
|
||||||
|
} = methods;
|
||||||
|
|
||||||
|
const handleErrors = async (resp: Response) => {
|
||||||
|
if (!resp.ok) {
|
||||||
|
const err = await resp.json();
|
||||||
|
if (err.checkoutSessionId) {
|
||||||
|
const stripe = await getStripe();
|
||||||
|
if (stripe) {
|
||||||
|
console.log("Redirecting to stripe checkout");
|
||||||
|
const { error } = await stripe.redirectToCheckout({
|
||||||
|
sessionId: err.checkoutSessionId,
|
||||||
|
});
|
||||||
|
console.warn(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const signUp: SubmitHandler<FormValues> = async (data) => {
|
||||||
|
await fetch("/api/auth/signup", {
|
||||||
|
body: JSON.stringify({
|
||||||
|
...data,
|
||||||
|
language: i18n.language,
|
||||||
|
token,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
})
|
||||||
|
.then(handleErrors)
|
||||||
|
.then(async () => {
|
||||||
|
telemetry.event(telemetryEventTypes.signup, collectPageParameters());
|
||||||
|
const verifyOrGettingStarted = flags["email-verification"] ? "auth/verify-email" : "getting-started";
|
||||||
|
await signIn<"credentials">("credentials", {
|
||||||
|
...data,
|
||||||
|
callbackUrl: `${
|
||||||
|
searchParams?.get("callbackUrl")
|
||||||
|
? `${WEBAPP_URL}/${searchParams.get("callbackUrl")}`
|
||||||
|
: `${WEBAPP_URL}/${verifyOrGettingStarted}`
|
||||||
|
}?from=signup`,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
methods.setError("apiError", { message: err.message });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="bg-muted flex min-h-screen flex-col justify-center "
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--cal-brand": "#111827",
|
||||||
|
"--cal-brand-emphasis": "#101010",
|
||||||
|
"--cal-brand-text": "white",
|
||||||
|
"--cal-brand-subtle": "#9CA3AF",
|
||||||
|
} as CSSProperties
|
||||||
|
}
|
||||||
|
aria-labelledby="modal-title"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true">
|
||||||
|
<HeadSeo title={t("sign_up")} description={t("sign_up")} />
|
||||||
|
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
|
<h2 className="font-cal text-emphasis text-center text-3xl font-extrabold">
|
||||||
|
{t("create_your_account")}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
|
<div className="bg-default mx-2 p-6 shadow sm:rounded-lg lg:p-8">
|
||||||
|
<FormProvider {...methods}>
|
||||||
|
<form
|
||||||
|
onSubmit={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (methods.formState?.errors?.apiError) {
|
||||||
|
methods.clearErrors("apiError");
|
||||||
|
}
|
||||||
|
methods.handleSubmit(signUp)(event);
|
||||||
|
}}
|
||||||
|
className="bg-default space-y-6">
|
||||||
|
{errors.apiError && <Alert severity="error" message={errors.apiError?.message} />}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<TextField
|
||||||
|
addOnLeading={
|
||||||
|
orgSlug
|
||||||
|
? getOrgFullDomain(orgSlug, { protocol: true })
|
||||||
|
: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/`
|
||||||
|
}
|
||||||
|
{...register("username")}
|
||||||
|
disabled={!!orgSlug}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<EmailField
|
||||||
|
{...register("email")}
|
||||||
|
disabled={prepopulateFormValues?.email}
|
||||||
|
className="disabled:bg-emphasis disabled:hover:cursor-not-allowed"
|
||||||
|
/>
|
||||||
|
<PasswordField
|
||||||
|
labelProps={{
|
||||||
|
className: "block text-sm font-medium text-default",
|
||||||
|
}}
|
||||||
|
{...register("password")}
|
||||||
|
hintErrors={["caplow", "min", "num"]}
|
||||||
|
className="border-default mt-1 block w-full rounded-md border px-3 py-2 shadow-sm focus:border-black focus:outline-none focus:ring-black sm:text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-2 rtl:space-x-reverse">
|
||||||
|
<Button type="submit" loading={isSubmitting} className="w-full justify-center">
|
||||||
|
{t("create_account")}
|
||||||
|
</Button>
|
||||||
|
{!token && (
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
className="w-full justify-center"
|
||||||
|
onClick={() =>
|
||||||
|
signIn("Cal.com", {
|
||||||
|
callbackUrl: searchParams?.get("callbackUrl")
|
||||||
|
? `${WEBAPP_URL}/${searchParams.get("callbackUrl")}`
|
||||||
|
: `${WEBAPP_URL}/getting-started`,
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
{t("login_instead")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||||
|
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
||||||
|
const flags = await getFeatureFlagMap(prisma);
|
||||||
|
const ssr = await ssrInit(ctx);
|
||||||
|
const token = z.string().optional().parse(ctx.query.token);
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED,
|
||||||
|
isSAMLLoginEnabled,
|
||||||
|
trpcState: ssr.dehydrate(),
|
||||||
|
prepopulateFormValues: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" || flags["disable-signup"]) {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// no token given, treat as a normal signup without verification token
|
||||||
|
if (!token) {
|
||||||
|
return {
|
||||||
|
props: JSON.parse(JSON.stringify(props)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const verificationToken = await prisma.verificationToken.findUnique({
|
||||||
|
where: {
|
||||||
|
token,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
metadata: true,
|
||||||
|
parentId: true,
|
||||||
|
parent: {
|
||||||
|
select: {
|
||||||
|
slug: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
slug: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!verificationToken || verificationToken.expires < new Date()) {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingUser = await prisma.user.findFirst({
|
||||||
|
where: {
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
email: verificationToken?.identifier,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emailVerified: {
|
||||||
|
not: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: false,
|
||||||
|
destination: "/auth/login?callbackUrl=" + `${WEBAPP_URL}/${ctx.query.callbackUrl}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const guessUsernameFromEmail = (email: string) => {
|
||||||
|
const [username] = email.split("@");
|
||||||
|
return username;
|
||||||
|
};
|
||||||
|
|
||||||
|
let username = guessUsernameFromEmail(verificationToken.identifier);
|
||||||
|
|
||||||
|
const tokenTeam = {
|
||||||
|
...verificationToken?.team,
|
||||||
|
metadata: teamMetadataSchema.parse(verificationToken?.team?.metadata),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Detect if the team is an org by either the metadata flag or if it has a parent team
|
||||||
|
const isOrganization = tokenTeam.metadata?.isOrganization || tokenTeam?.parentId !== null;
|
||||||
|
// If we are dealing with an org, the slug may come from the team itself or its parent
|
||||||
|
const orgSlug = isOrganization
|
||||||
|
? tokenTeam.slug || tokenTeam.metadata?.requestedSlug || tokenTeam.parent?.slug
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// Org context shouldn't check if a username is premium
|
||||||
|
if (!IS_SELF_HOSTED && !isOrganization) {
|
||||||
|
// Im not sure we actually hit this because of next redirects signup to website repo - but just in case this is pretty cool :)
|
||||||
|
const { available, suggestion } = await checkPremiumUsername(username);
|
||||||
|
|
||||||
|
username = available ? username : suggestion || username;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...props,
|
||||||
|
token,
|
||||||
|
prepopulateFormValues: {
|
||||||
|
email: verificationToken.identifier,
|
||||||
|
username: slugify(username),
|
||||||
|
},
|
||||||
|
orgSlug,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Signup.isThemeSupported = false;
|
||||||
|
Signup.PageWrapper = PageWrapper;
|
|
@ -1,18 +1,21 @@
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { ShieldCheckIcon } from "lucide-react";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import { useSearchParams } from "next/navigation";
|
import Link from "next/link";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import type { CSSProperties } from "react";
|
import type { CSSProperties } from "react";
|
||||||
import type { SubmitHandler } from "react-hook-form";
|
import type { SubmitHandler } from "react-hook-form";
|
||||||
import { FormProvider, useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import getStripe from "@calcom/app-store/stripepayment/lib/client";
|
import getStripe from "@calcom/app-store/stripepayment/lib/client";
|
||||||
|
import { getOrgFullDomain } from "@calcom/ee/organizations/lib/orgDomains";
|
||||||
import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername";
|
import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername";
|
||||||
import { getOrgFullDomain } from "@calcom/features/ee/organizations/lib/orgDomains";
|
|
||||||
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
||||||
import { useFlagMap } from "@calcom/features/flags/context/provider";
|
import { useFlagMap } from "@calcom/features/flags/context/provider";
|
||||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
||||||
|
import { classNames } from "@calcom/lib";
|
||||||
import { IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
|
import { IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import slugify from "@calcom/lib/slugify";
|
import slugify from "@calcom/lib/slugify";
|
||||||
|
@ -20,7 +23,7 @@ import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calco
|
||||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||||
import { signupSchema as apiSignupSchema } from "@calcom/prisma/zod-utils";
|
import { signupSchema as apiSignupSchema } from "@calcom/prisma/zod-utils";
|
||||||
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||||
import { Alert, Button, EmailField, HeadSeo, PasswordField, TextField } from "@calcom/ui";
|
import { Button, HeadSeo, PasswordField, TextField, Form } from "@calcom/ui";
|
||||||
|
|
||||||
import PageWrapper from "@components/PageWrapper";
|
import PageWrapper from "@components/PageWrapper";
|
||||||
|
|
||||||
|
@ -39,16 +42,17 @@ export default function Signup({ prepopulateFormValues, token, orgSlug }: Signup
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
const { t, i18n } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
|
const router = useRouter();
|
||||||
const flags = useFlagMap();
|
const flags = useFlagMap();
|
||||||
const methods = useForm<FormValues>({
|
const formMethods = useForm<FormValues>({
|
||||||
mode: "onChange",
|
mode: "onChange",
|
||||||
resolver: zodResolver(signupSchema),
|
resolver: zodResolver(signupSchema),
|
||||||
defaultValues: prepopulateFormValues,
|
defaultValues: prepopulateFormValues satisfies FormValues,
|
||||||
});
|
});
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
} = methods;
|
} = formMethods;
|
||||||
|
|
||||||
const handleErrors = async (resp: Response) => {
|
const handleErrors = async (resp: Response) => {
|
||||||
if (!resp.ok) {
|
if (!resp.ok) {
|
||||||
|
@ -94,14 +98,14 @@ export default function Signup({ prepopulateFormValues, token, orgSlug }: Signup
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
methods.setError("apiError", { message: err.message });
|
formMethods.setError("apiError", { message: err.message });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="bg-muted flex min-h-screen flex-col justify-center "
|
className="bg-muted grid min-h-screen grid-cols-1 grid-rows-1 lg:grid-cols-2 "
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
"--cal-brand": "#111827",
|
"--cal-brand": "#111827",
|
||||||
|
@ -109,12 +113,133 @@ export default function Signup({ prepopulateFormValues, token, orgSlug }: Signup
|
||||||
"--cal-brand-text": "white",
|
"--cal-brand-text": "white",
|
||||||
"--cal-brand-subtle": "#9CA3AF",
|
"--cal-brand-subtle": "#9CA3AF",
|
||||||
} as CSSProperties
|
} as CSSProperties
|
||||||
}
|
}>
|
||||||
aria-labelledby="modal-title"
|
|
||||||
role="dialog"
|
|
||||||
aria-modal="true">
|
|
||||||
<HeadSeo title={t("sign_up")} description={t("sign_up")} />
|
<HeadSeo title={t("sign_up")} description={t("sign_up")} />
|
||||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
<div className="flex w-full flex-col px-28 pt-16">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex flex-col gap-3 ">
|
||||||
|
<h1 className="font-cal text-[28px] ">Create your Cal.com account</h1>
|
||||||
|
<p className="text-subtle text-base font-medium leading-none">
|
||||||
|
Free for individuals. Team plans for collaborative features.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/* Form Container */}
|
||||||
|
<div className="mt-10">
|
||||||
|
<Form
|
||||||
|
className="flex flex-col gap-5"
|
||||||
|
form={formMethods}
|
||||||
|
onSubmit={(values) => console.log(values)}>
|
||||||
|
{/* Username */}
|
||||||
|
<TextField
|
||||||
|
{...register("username")}
|
||||||
|
label={t("username")}
|
||||||
|
addOnLeading={
|
||||||
|
orgSlug
|
||||||
|
? getOrgFullDomain(orgSlug, { protocol: true })
|
||||||
|
: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/`
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{/* Email */}
|
||||||
|
<TextField {...register("email")} label={t("email")} type="email" />
|
||||||
|
|
||||||
|
{/* Password */}
|
||||||
|
<PasswordField
|
||||||
|
label={t("password")}
|
||||||
|
{...register("password")}
|
||||||
|
hintErrors={["caplow", "min", "num"]}
|
||||||
|
/>
|
||||||
|
<Button type="submit" className="w-full justify-center">
|
||||||
|
{t("create_account")}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
{/* Continue with Social Logins */}
|
||||||
|
<div className="mt-6">
|
||||||
|
<div className="relative flex items-center">
|
||||||
|
<div className="border-subtle flex-grow border-t" />
|
||||||
|
<span className="text-subtle leadning-none mx-2 flex-shrink text-sm font-normal ">
|
||||||
|
Or continue with
|
||||||
|
</span>
|
||||||
|
<div className="border-subtle flex-grow border-t" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Social Logins */}
|
||||||
|
<div className="mt-6 grid gap-2 lg:grid-cols-2">
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
disabled={!!formMethods.formState.errors.username}
|
||||||
|
className={classNames(
|
||||||
|
"w-full justify-center rounded-md text-center",
|
||||||
|
formMethods.formState.errors.username ? "opacity-50" : ""
|
||||||
|
)}
|
||||||
|
onClick={async () => {
|
||||||
|
if (!formMethods.getValues("username")) {
|
||||||
|
formMethods.trigger("username");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const username = formMethods.getValues("username");
|
||||||
|
const searchQueryParams = new URLSearchParams();
|
||||||
|
searchQueryParams.set("username", formMethods.getValues("username"));
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL;
|
||||||
|
localStorage.setItem("username", username);
|
||||||
|
// @NOTE: don't remove username query param as it's required right now for stripe payment page
|
||||||
|
const googleAuthUrl = `${baseUrl}/auth/sso/google?${searchQueryParams.toString()}`;
|
||||||
|
|
||||||
|
router.push(googleAuthUrl);
|
||||||
|
}}>
|
||||||
|
<img className="mr-2 h-5 w-5" src="/google-icon.svg" alt="" />
|
||||||
|
Google
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
disabled={!!formMethods.formState.errors.username || !!formMethods.formState.errors.email}
|
||||||
|
className={classNames(
|
||||||
|
"w-full justify-center rounded-md text-center",
|
||||||
|
formMethods.formState.errors.username && formMethods.formState.errors.email
|
||||||
|
? "opacity-50"
|
||||||
|
: ""
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
if (!formMethods.getValues("username")) {
|
||||||
|
formMethods.trigger("username");
|
||||||
|
}
|
||||||
|
if (!formMethods.getValues("email")) {
|
||||||
|
formMethods.trigger("email");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const username = formMethods.getValues("username");
|
||||||
|
localStorage.setItem("username", username);
|
||||||
|
const sp = new URLSearchParams();
|
||||||
|
// @NOTE: don't remove username query param as it's required right now for stripe payment page
|
||||||
|
sp.set("username", formMethods.getValues("username"));
|
||||||
|
sp.set("email", formMethods.getValues("email"));
|
||||||
|
router.push(process.env.NEXT_PUBLIC_WEBAPP_URL + "/auth/sso/saml" + "?" + sp.toString());
|
||||||
|
}}>
|
||||||
|
<ShieldCheckIcon className="mr-2 h-5 w-5" />
|
||||||
|
{t("saml_sso")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Already have an account & T&C */}
|
||||||
|
<div className="mb-6 mt-auto">
|
||||||
|
<div className="flex flex-col text-sm">
|
||||||
|
<Link href="/auth/login" className="text-emphasis hover:underline">
|
||||||
|
I already have an account.
|
||||||
|
</Link>
|
||||||
|
<p className="text-subtle">
|
||||||
|
By signing up, you agree to our <span className="text-emphasis">Terms of Service</span> and{" "}
|
||||||
|
<span className="text-emphasis">Privacy Policy</span>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="my-6 w-full rounded-l-lg"
|
||||||
|
style={{
|
||||||
|
background: "radial-gradient(234.86% 110.55% at 109.58% 35%, #667593 0%, #D4D4D5 100%)",
|
||||||
|
}}>
|
||||||
|
<></>
|
||||||
|
</div>
|
||||||
|
{/* <div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
<h2 className="font-cal text-emphasis text-center text-3xl font-extrabold">
|
<h2 className="font-cal text-emphasis text-center text-3xl font-extrabold">
|
||||||
{t("create_your_account")}
|
{t("create_your_account")}
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -181,7 +306,7 @@ export default function Signup({ prepopulateFormValues, token, orgSlug }: Signup
|
||||||
</form>
|
</form>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M8.31877 15.36C4.26002 15.36 0.95752 12.0588 0.95752 8.00001C0.95752 3.94126 4.26002 0.640015 8.31877 0.640015C10.1575 0.640015 11.9175 1.32126 13.2763 2.55876L13.5238 2.78501L11.0963 5.21251L10.8713 5.02001C10.1588 4.41001 9.25252 4.07376 8.31877 4.07376C6.15377 4.07376 4.39127 5.83501 4.39127 8.00001C4.39127 10.165 6.15377 11.9263 8.31877 11.9263C9.88002 11.9263 11.1138 11.1288 11.695 9.77001H7.99877V6.45626L15.215 6.46626L15.2688 6.72001C15.645 8.50626 15.3438 11.1338 13.8188 13.0138C12.5563 14.57 10.7063 15.36 8.31877 15.36Z" fill="#4B5563"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 665 B |
|
@ -22,6 +22,8 @@ export function HintsOrErrors<T extends FieldValues = FieldValues>({
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const fieldErrors: FieldErrors<T> | undefined = formState.errors[fieldName];
|
const fieldErrors: FieldErrors<T> | undefined = formState.errors[fieldName];
|
||||||
|
|
||||||
|
console.log(hintErrors, fieldErrors);
|
||||||
|
|
||||||
if (!hintErrors && fieldErrors && !fieldErrors.message) {
|
if (!hintErrors && fieldErrors && !fieldErrors.message) {
|
||||||
// no hints passed, field errors exist and they are custom ones
|
// no hints passed, field errors exist and they are custom ones
|
||||||
return (
|
return (
|
||||||
|
|
Loading…
Reference in New Issue