2022-07-18 12:52:50 +00:00
|
|
|
import Head from "next/head";
|
2022-07-14 12:40:53 +00:00
|
|
|
import { useRouter } from "next/router";
|
2023-02-16 22:39:57 +00:00
|
|
|
import type { FormEvent } from "react";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
2022-07-14 12:40:53 +00:00
|
|
|
import { Toaster } from "react-hot-toast";
|
|
|
|
import { v4 as uuidv4 } from "uuid";
|
|
|
|
|
2022-10-19 21:25:03 +00:00
|
|
|
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
2022-07-28 10:50:25 +00:00
|
|
|
import classNames from "@calcom/lib/classNames";
|
2023-04-05 18:14:46 +00:00
|
|
|
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
2022-09-08 17:35:32 +00:00
|
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
2022-07-28 19:58:26 +00:00
|
|
|
import useTheme from "@calcom/lib/hooks/useTheme";
|
2022-07-22 17:27:06 +00:00
|
|
|
import { trpc } from "@calcom/trpc/react";
|
2023-02-16 22:39:57 +00:00
|
|
|
import type { AppGetServerSidePropsContext, AppPrisma } from "@calcom/types/AppGetServerSideProps";
|
|
|
|
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
|
2023-04-05 18:14:46 +00:00
|
|
|
import { Button, showToast, useCalcomTheme } from "@calcom/ui";
|
2022-07-14 12:40:53 +00:00
|
|
|
|
2022-11-10 12:58:07 +00:00
|
|
|
import FormInputFields from "../../components/FormInputFields";
|
2022-08-13 11:04:57 +00:00
|
|
|
import { getSerializableForm } from "../../lib/getSerializableForm";
|
|
|
|
import { processRoute } from "../../lib/processRoute";
|
2023-02-16 22:39:57 +00:00
|
|
|
import type { Response, Route } from "../../types/types";
|
2022-07-14 12:40:53 +00:00
|
|
|
|
2023-04-05 18:14:46 +00:00
|
|
|
const useBrandColors = ({
|
|
|
|
brandColor,
|
|
|
|
darkBrandColor,
|
|
|
|
}: {
|
|
|
|
brandColor?: string | null;
|
|
|
|
darkBrandColor?: string | null;
|
|
|
|
}) => {
|
|
|
|
const brandTheme = useGetBrandingColours({
|
|
|
|
lightVal: brandColor,
|
|
|
|
darkVal: darkBrandColor,
|
|
|
|
});
|
|
|
|
useCalcomTheme(brandTheme);
|
|
|
|
};
|
|
|
|
|
2022-10-19 21:25:03 +00:00
|
|
|
function RoutingForm({ form, profile, ...restProps }: inferSSRProps<typeof getServerSideProps>) {
|
2022-07-14 12:40:53 +00:00
|
|
|
const [customPageMessage, setCustomPageMessage] = useState<Route["action"]["value"]>("");
|
|
|
|
const formFillerIdRef = useRef(uuidv4());
|
2022-10-19 21:25:03 +00:00
|
|
|
const isEmbed = useIsEmbed(restProps.isEmbed);
|
2022-07-28 10:50:25 +00:00
|
|
|
useTheme(profile.theme);
|
2023-04-05 18:14:46 +00:00
|
|
|
useBrandColors({
|
|
|
|
brandColor: profile.brandColor,
|
|
|
|
darkBrandColor: profile.darkBrandColor,
|
|
|
|
});
|
2022-07-14 12:40:53 +00:00
|
|
|
// TODO: We might want to prevent spam from a single user by having same formFillerId across pageviews
|
|
|
|
// But technically, a user can fill form multiple times due to any number of reasons and we currently can't differentiate b/w that.
|
|
|
|
// - like a network error
|
|
|
|
// - or he abandoned booking flow in between
|
|
|
|
const formFillerId = formFillerIdRef.current;
|
|
|
|
const decidedActionRef = useRef<Route["action"]>();
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
const onSubmit = (response: Response) => {
|
|
|
|
const decidedAction = processRoute({ form, response });
|
|
|
|
|
|
|
|
if (!decidedAction) {
|
|
|
|
// FIXME: Make sure that when a form is created, there is always a fallback route and then remove this.
|
|
|
|
alert("Define atleast 1 route");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
responseMutation.mutate({
|
|
|
|
formId: form.id,
|
|
|
|
formFillerId,
|
|
|
|
response: response,
|
|
|
|
});
|
|
|
|
decidedActionRef.current = decidedAction;
|
|
|
|
};
|
|
|
|
|
2022-10-19 21:25:03 +00:00
|
|
|
useEffect(() => {
|
|
|
|
// Custom Page doesn't actually change Route, so fake it so that embed can adjust the scroll to make the content visible
|
|
|
|
sdkActionManager?.fire("__routeChanged", {});
|
|
|
|
}, [customPageMessage]);
|
|
|
|
|
2022-11-10 23:40:01 +00:00
|
|
|
const responseMutation = trpc.viewer.appRoutingForms.public.response.useMutation({
|
2022-07-14 12:40:53 +00:00
|
|
|
onSuccess: () => {
|
|
|
|
const decidedAction = decidedActionRef.current;
|
|
|
|
if (!decidedAction) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//TODO: Maybe take action after successful mutation
|
|
|
|
if (decidedAction.type === "customPageMessage") {
|
|
|
|
setCustomPageMessage(decidedAction.value);
|
|
|
|
} else if (decidedAction.type === "eventTypeRedirectUrl") {
|
|
|
|
router.push(`/${decidedAction.value}`);
|
|
|
|
} else if (decidedAction.type === "externalRedirectUrl") {
|
2023-04-24 14:12:50 +00:00
|
|
|
window.parent.location.href = decidedAction.value;
|
2022-07-14 12:40:53 +00:00
|
|
|
}
|
2022-07-28 10:50:25 +00:00
|
|
|
// showToast("Form submitted successfully! Redirecting now ...", "success");
|
2022-07-14 12:40:53 +00:00
|
|
|
},
|
|
|
|
onError: (e) => {
|
|
|
|
if (e?.message) {
|
|
|
|
return void showToast(e?.message, "error");
|
|
|
|
}
|
|
|
|
if (e?.data?.code === "CONFLICT") {
|
|
|
|
return void showToast("Form already submitted", "error");
|
|
|
|
}
|
2022-07-28 10:50:25 +00:00
|
|
|
// showToast("Something went wrong", "error");
|
2022-07-14 12:40:53 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const [response, setResponse] = useState<Response>({});
|
|
|
|
|
|
|
|
const handleOnSubmit = (e: FormEvent<HTMLFormElement>) => {
|
|
|
|
e.preventDefault();
|
|
|
|
onSubmit(response);
|
|
|
|
};
|
|
|
|
|
2022-09-08 17:35:32 +00:00
|
|
|
const { t } = useLocale();
|
|
|
|
|
2022-07-28 10:50:25 +00:00
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
{!customPageMessage ? (
|
|
|
|
<>
|
|
|
|
<Head>
|
2022-09-22 17:23:43 +00:00
|
|
|
<title>{`${form.name} | Cal.com Forms`}</title>
|
2022-07-28 10:50:25 +00:00
|
|
|
</Head>
|
|
|
|
<div className={classNames("mx-auto my-0 max-w-3xl", isEmbed ? "" : "md:my-24")}>
|
|
|
|
<div className="w-full max-w-4xl ltr:mr-2 rtl:ml-2">
|
2023-04-13 18:26:31 +00:00
|
|
|
<div className="main border-booker md:border-booker-width dark:bg-muted bg-default mx-0 rounded-md p-4 py-6 sm:-mx-4 sm:px-8 ">
|
2022-07-28 10:50:25 +00:00
|
|
|
<Toaster position="bottom-right" />
|
|
|
|
|
|
|
|
<form onSubmit={handleOnSubmit}>
|
|
|
|
<div className="mb-8">
|
2023-04-05 18:14:46 +00:00
|
|
|
<h1 className="font-cal text-emphasis mb-1 text-xl font-bold tracking-wide">
|
2022-07-28 10:50:25 +00:00
|
|
|
{form.name}
|
|
|
|
</h1>
|
|
|
|
{form.description ? (
|
2023-04-05 18:14:46 +00:00
|
|
|
<p className="min-h-10 text-subtle text-sm ltr:mr-4 rtl:ml-4">{form.description}</p>
|
2022-07-28 10:50:25 +00:00
|
|
|
) : null}
|
2022-07-18 12:52:50 +00:00
|
|
|
</div>
|
2022-11-10 12:58:07 +00:00
|
|
|
<FormInputFields form={form} response={response} setResponse={setResponse} />
|
2022-07-28 10:50:25 +00:00
|
|
|
<div className="mt-4 flex justify-end space-x-2 rtl:space-x-reverse">
|
2022-10-10 18:50:43 +00:00
|
|
|
<Button
|
|
|
|
className="dark:bg-darkmodebrand dark:text-darkmodebrandcontrast dark:hover:border-darkmodebrandcontrast dark:border-transparent"
|
|
|
|
loading={responseMutation.isLoading}
|
|
|
|
type="submit"
|
|
|
|
color="primary">
|
2022-09-08 17:35:32 +00:00
|
|
|
{t("submit")}
|
2022-07-28 10:50:25 +00:00
|
|
|
</Button>
|
2022-07-18 12:52:50 +00:00
|
|
|
</div>
|
2022-07-28 10:50:25 +00:00
|
|
|
</form>
|
|
|
|
</div>
|
2022-07-18 12:52:50 +00:00
|
|
|
</div>
|
2022-07-28 10:50:25 +00:00
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
<div className="mx-auto my-0 max-w-3xl md:my-24">
|
|
|
|
<div className="w-full max-w-4xl ltr:mr-2 rtl:ml-2">
|
2023-04-12 08:42:41 +00:00
|
|
|
<div className="main dark:bg-darkgray-100 sm:border-subtle bg-default -mx-4 rounded-md border border-neutral-200 p-4 py-6 sm:mx-0 sm:px-8">
|
2023-04-05 18:14:46 +00:00
|
|
|
<div className="text-emphasis">{customPageMessage}</div>
|
2022-07-28 10:50:25 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-07-18 12:52:50 +00:00
|
|
|
</div>
|
2022-07-28 10:50:25 +00:00
|
|
|
)}
|
2022-07-14 12:40:53 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-10-19 21:25:03 +00:00
|
|
|
export default function RoutingLink(props: inferSSRProps<typeof getServerSideProps>) {
|
|
|
|
return <RoutingForm {...props} />;
|
2022-07-14 12:40:53 +00:00
|
|
|
}
|
|
|
|
|
2023-04-17 12:16:54 +00:00
|
|
|
RoutingLink.isBookingPage = true;
|
2022-07-26 08:27:57 +00:00
|
|
|
|
2022-07-14 12:40:53 +00:00
|
|
|
export const getServerSideProps = async function getServerSideProps(
|
|
|
|
context: AppGetServerSidePropsContext,
|
|
|
|
prisma: AppPrisma
|
|
|
|
) {
|
|
|
|
const { params } = context;
|
|
|
|
if (!params) {
|
|
|
|
return {
|
|
|
|
notFound: true,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
const formId = params.appPages[0];
|
2022-10-19 21:25:03 +00:00
|
|
|
if (!formId || params.appPages.length > 2) {
|
2022-07-14 12:40:53 +00:00
|
|
|
return {
|
|
|
|
notFound: true,
|
|
|
|
};
|
|
|
|
}
|
2022-10-19 21:25:03 +00:00
|
|
|
const isEmbed = params.appPages[1] === "embed";
|
|
|
|
|
2022-07-14 12:40:53 +00:00
|
|
|
const form = await prisma.app_RoutingForms_Form.findUnique({
|
|
|
|
where: {
|
|
|
|
id: formId,
|
|
|
|
},
|
2022-07-28 10:50:25 +00:00
|
|
|
include: {
|
|
|
|
user: {
|
|
|
|
select: {
|
|
|
|
theme: true,
|
|
|
|
brandColor: true,
|
|
|
|
darkBrandColor: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-07-14 12:40:53 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
if (!form || form.disabled) {
|
|
|
|
return {
|
|
|
|
notFound: true,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
props: {
|
2022-10-19 21:25:03 +00:00
|
|
|
isEmbed,
|
2022-07-28 10:50:25 +00:00
|
|
|
profile: {
|
|
|
|
theme: form.user.theme,
|
|
|
|
brandColor: form.user.brandColor,
|
|
|
|
darkBrandColor: form.user.darkBrandColor,
|
|
|
|
},
|
2023-01-04 13:30:46 +00:00
|
|
|
form: await getSerializableForm(form),
|
2022-07-14 12:40:53 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
};
|