Files
calendar/packages/app-store/routing-forms/pages/routing-link/[...appPages].tsx
T
+21 7bff15cc47 chore: replace toaster with sonner (#19342)
* fix: replace react-hot-toast with sonner for toast notifications

* feat: add country code (#19264)

* perf: Move PlainChat up the hierarchy to remove rerenders (#19255)

* perf: Move PlainChat up the hierarchy to remove rerenders

* rmeove yarn lock

---------

Co-authored-by: Nizzy <nizabizaher@gmail.com>
Co-authored-by: nizzy <140507264+nizzyabi@users.noreply.github.com>

* hotfix (#19266)

* chore: release v4.9.11

* Handle cancelled status (#19272)

* feat: Use new /routing URL for routing forms (#19235)

* feat: Use new /routing URL for routing forms

* Fixes

* Fix bug

* precent crash

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmgmail.com>

* fix: remove section on /bookings if no data exists (#19261)

* feat: v2 slots new version (#18758)

* refactor: version old slots

* feat: init new slots endpoints

* chore: range format

* fix: duratin

* fix: duratin

* test: slot releaseAt

* refactor: reserve slot response

* refactor variable name

* docs: have new slots controller in docs

* feat: crud for slots reservations

* refactor: use exclude all for response

* docs

* chore: slots input service

* refactor mini

* refactor: remove unused imports

* docs

* handle orgSlug for dynamic events

* refactor: correct name

* docs

* add optional organizationSlug to BySlug search

* refactor: slot output format

* refactor: return seated slot info

* docs

* rename functions

* refactor: slots seated response

* fix: handle same username in org and non org

* refactor: test format

* fix: allow reservationDuration only for authed requests

* chore: check if auth user has permissions to define custom reservation duration

* refactor: split e2e test files

* refactor: invalid request exception codes

* chore: old slots controller uses all old versions

* refactor: remove depreceated fields (#19279)

* refactor: remove depreceated fields

* chore: use v2 endpoint

* chore: update V2 Api type

* chore

* chore: Refactor to remove `getServerSideProps` for /teams route (#19286)

* remove getServerSideProps

* refactor teams route

* chore: release v4.9.12

* fix: Salesforce - if no contact throw Error (#19271)

* Throw error if no contact is passed to createEvent

* chore: update error message

* chore

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>

* fix: missing await router controller (#19276)

* fix: Default people filter in bookings list for admin (#18035)

* default people filter for admin

* chore

* type check

* added test case

* made test concise to reduce flakiness

* add timeout to wait for second get response before counting

* update timeout

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* feat: add CSAT to V2 bookings return (#19278)

* init

* test: v2 bookings returns rating

---------

Co-authored-by: supalarry <laurisskraucis@gmail.com>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>

* chore: disable v2 booking seats safe parse error logs (#19292)

* revert: Reapply "fix: Multiple rr-hosts combine to create erroneous availabil… (#19263)

* Reapply "fix: Multiple rr-hosts combine to create erroneous availability (#18772)"

This reverts commit 5dbc6d9ca6.

* Only thing left is a diff to show which slots are removed

* Add logging around time slot results

* Setup logging through axiom

* .... forgot

* fix: type errors

* Only log when there are differences

* Updated from old which had a failing test to new logic

* chore: release v4.9.13

* fix: flaky test (#19294)

* fix: flaky test

* fix another test

* update

---------

Co-authored-by: Eunjae Lee <hey@eunjae.dev>
Co-authored-by: unknown <adhabal2002@gmail.com>

* chore: calendars controller v2 docs (#19291)

Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>

* Merge branch 'main' into replace-toaster-with-sonner

* add ireland country code (#19117)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>

* perf: separate client vs server code from insights layout (#19285)

* add UpgradeTipWrapper

* refactor insights/layout.tsx

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>

* feat: Use Folders to organize SMS links on Dub (#19248)

* feat: Use Folders to organize SMS links on Dub

* Update yarn.lock

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>

* fix: removing-all-members-toggle-for-fh (#19277)

* fix : removing all members toggle for fh

* ux enhancements

* adding placeholder

* chore: release v5.0.0

* fix flake (#19306)

* chore: migrate `/apps/routing-forms/[...pages]` to App Router (#18956)

* wip

* fix

* fix

* refactor

* rename to routingServerSidePropsConfig

* refactor

* refactor

* add routing-link to useIsBookingPage hook

* remove Head component

* fix

* redirect user to apps/routing-forms/forms if user goes to apps/routing-forms

* refactor

* remove log

* remove client component not needed

* clean up code

* remove unneeded metadata

* clean up further

* clean up further 2

* fix type check

* routing-link does not need shell

* Fix title for Routing Form public link and also remove any

* Remove ; in HTML

* Remove unnecessary page reload

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmgmail.com>

* chore: migrate api for team upgrade to App Router (#19100)

* remove old pages router api

* add new app router api

* fix

* fix redirect

* refactor

* fix: cut off and alignment bug (#19317)

* fix: assignment label cut-off

* fix: add all team members label is vertically aligned

* fix: nested scrolls on /bookings (#19284)

* fix: Can't change availability for managed event type (#19006)

* fix: flaky test organization.spec

* fix

* fix: bulk-update-in-scheduleId-in-detailed-view (#19295)

* fix: bulk-update-in-scheduleId-in-detailed-view

* minor-change

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>

* feat: prevent automatic query parameters when opening booking page (#18132)

- Resolve the user hindrance where the event's booking page should not have search parameters such as date and month in the initial page load
- Contains changes for the fix of issue #18094 as well as it is needed

Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>

* feat: atoms adjustments with ui v3 styles (#19320)

* refactor: Clean out old code, no longer needed (#19323)

* refactor: Clean out old code, no longer needed

* Removed a sneaky one keen to stay behind

* chore: release v5.0.1

* feat: Create `UserCreationService` and use in API V1 create user endpoint (#19150)

* Create `createUser` method in `userCreationService`

* Refactor `UserRepository` create method to accept prisma input and remove business logic

* API use `UserCreationService`

* Move slugify to service

* Use hashPassword instead

* Type fixes in `UserCreationService`

* Add `userCreationService` tests

* API accept data object

* Type fixes

* Add user _post test

* Add test for locked user

* Add locked param to log

* Add user repository tests

* Do not return locked status

* Explicitly pass `locked` prop

* Fix tests when locked isn't returned

* Fix tests

* Pass locked prop

* Edit test name

* Use logger

* Fix passing hashed password

* fix (#19298)

* fix: timezone input text is now visible in dark mode (#19325)

* feat: make first tab active by default (#19315)

* feat: make first tab active by default

* perf: optimize event types tabs rendering with useMemo

---------

Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>

* perf: Use one PageWrapper instance for all settings pages (#18752)

* add a layoutfile to use-page-wrapper route group

* refactor and move bookings status page to route group

* improve logic in pagesAndRewritePaths

* fix

* add use client directive

* move /403 and /500

* move /auth/error

* move /connect-and-join

* move /enterprise

* move /maintenance

* move /more

* move /upgrade

* chore: remove WithLayout from 8 low-risk single pages (#18745)

* move /403 and /500

* move /auth/error

* move /connect-and-join

* move /enterprise

* move /maintenance

* move /more

* move /upgrade

* move /availability pages

* move /event-types pages

* move /insights pages

* move /apps to use-page-wrapper group

* move /getting-started to use-page-wrapper group

* move /signup to use-page-wrapper group

* move /workflows to use-page-wrapper group

* move /video pages to use-page-wrapper group

* pass dehydratedState in layout

* pass dehydratedState in layout

* move settings/(settings-layout) to use-page-wrapper

* move settings/(admin-layout) to use-page-wrapper

* move remaining settings pages to (use-page-wrapper)

* fix test

* remove unused code

* remove unused files

* fix type check

* remove test tag

* remove page

* RSC should not default export client components

* fix: Can't use unsaved booking question as variable in event name (#18604)

Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>

* chore: remove WithLayout from not-found.tsx (#19331)

* fix: redirect logic in /settings/admin/apps (#19332)

* chore: v3 fixed filters, new profile view (#19312)

* fixed filters with badge

* aligned recurrence in bookings

* removed new badge

* new profile view

* fixed try for free button

* fixed form input

* fix: Issues with date changes in column view (#19258)

* fix: coulmn view date change

* Update store.ts

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>

* chore: fixed tooltip position for routing form (#19267)

* fixed tooltip position for routing form

* fixed offset

* fix: Fairness when routing (#18550)

* Partial push of new filters

* Let's always return the passed host array when a filter doesn't need to be applied

* fix: isFixed typing in same round robin host check

* Add main bulk of rewritten logic for routed fairness support

* Remove console.log

* Add getAttributeWithEnabledWeights to AttributeRepository

* Add OOO entry repository

* Use getOrderedListOfLuckyUsers in lead threshold filtering

* Shortfall already includes weight

* test against minShortfall

* first attempt to fix fallbacks

* Added testcase updates

* Partially fix filterHosts test

* Some test updates

* Removed email field from normalization

* fix no available users found error

* fix unit test

* remove fallback from same host reschedule return

* fixes and improvements for findQualifiedHosts

* include fallback logic in handleNewBooking

* use fallback in handleNewBooking

* fix for contact owner in handleNewBooking

* WIP - comment

* pass routingFormResponseId

* fix type error

* pass routingFormResponse

* fixes in treshold logic

* remove reason

* merge qualified and fallback availabilities

* fix type error

* add and fix tests

* clean up test file

* fix filterHostsBySameRoundRobinHost tests

* fix findQualifiedHosts.test.ts

* fix getRoutedUsers.test.ts

* fix tests

* fix isRerouting check

* fix start time for two weeks check

* fix fallback hosts

* fix test

* fallback hosts should also include qualified hosts

* fix type error

* fix typos

* add contact owner to fallbacks

* fix typo

* fix qualifiedRRUsers

* set fixed users when no hosts

* add tets + code clean up

* add test for reschedule with same host

* fix typo

* add more tests for findQualifiedHosts

* remove skip from test

* add comment

* remove unused code

* fix fallback users in loadAndValidateUsers

* improve naming for fallback

* rename missed fallback variable

* fix reschedule with same rr host in handleNewBooking

* fixes for fallback slots

* add more tests + small fixes for contact owner

* fix tests and type errors

* add logs for fallback

* add type:json to logger

* Fix small merge issue

* fix end time if two weeks are cut off

* add more tests for contact owner availability check

* add new logic for two weeks fallback

* fix reschedule same RR host test

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>

* fix: EditLocationDialog footer positioning and structure (#19335)

* fix: EditLocationDialog footer positioning and structure

* fix: make RescheduleDialog footer layout and button structure right

* fix: input dialogue glitch now works great

* made tests happy

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* Merge branch 'main' into replace-toaster-with-sonner

* chore: remove DefaultToast from toast.stories.mdx

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Benny Joo <sldisek783@gmail.com>
Co-authored-by: Nizzy <nizabizaher@gmail.com>
Co-authored-by: nizzy <140507264+nizzyabi@users.noreply.github.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
Co-authored-by: GitHub Actions <github-actions@github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmgmail.com>
Co-authored-by: Eunjae Lee <hey@eunjae.dev>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Vijay <vijayraghav22@gmail.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: unknown <adhabal2002@gmail.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Steven Tey <stevensteel97@gmail.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>
Co-authored-by: amrit <iamamrit27@gmail.com>
Co-authored-by: Sanchit Tewari <46245135+sanchitttt@users.noreply.github.com>
Co-authored-by: Rajiv Sahal <sahalrajiv-extc@atharvacoe.ac.in>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
2025-02-24 11:48:38 -03:00

232 lines
9.0 KiB
TypeScript

"use client";
import { useRouter } from "next/navigation";
import type { FormEvent } from "react";
import { useEffect, useRef, useState } from "react";
import { Toaster } from "sonner";
import { v4 as uuidv4 } from "uuid";
import { sdkActionManager, useIsEmbed } from "@calcom/embed-core/embed-iframe";
import classNames from "@calcom/lib/classNames";
import useGetBrandingColours from "@calcom/lib/getBrandColours";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import useTheme from "@calcom/lib/hooks/useTheme";
import { navigateInTopWindow } from "@calcom/lib/navigateInTopWindow";
import { trpc } from "@calcom/trpc/react";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Button, showToast, useCalcomTheme } from "@calcom/ui";
import FormInputFields from "../../components/FormInputFields";
import { getAbsoluteEventTypeRedirectUrlWithEmbedSupport } from "../../getEventTypeRedirectUrl";
import getFieldIdentifier from "../../lib/getFieldIdentifier";
import { findMatchingRoute } from "../../lib/processRoute";
import { substituteVariables } from "../../lib/substituteVariables";
import { getFieldResponseForJsonLogic } from "../../lib/transformResponse";
import type { NonRouterRoute, FormResponse } from "../../types/types";
import { getServerSideProps } from "./getServerSideProps";
import { getUrlSearchParamsToForward } from "./getUrlSearchParamsToForward";
type Props = inferSSRProps<typeof getServerSideProps>;
const useBrandColors = ({
brandColor,
darkBrandColor,
}: {
brandColor?: string | null;
darkBrandColor?: string | null;
}) => {
const brandTheme = useGetBrandingColours({
lightVal: brandColor,
darkVal: darkBrandColor,
});
useCalcomTheme(brandTheme);
};
function RoutingForm({ form, profile, ...restProps }: Props) {
const [customPageMessage, setCustomPageMessage] = useState<NonRouterRoute["action"]["value"]>("");
const formFillerIdRef = useRef(uuidv4());
const isEmbed = useIsEmbed(restProps.isEmbed);
useTheme(profile.theme);
useBrandColors({
brandColor: profile.brandColor,
darkBrandColor: profile.darkBrandColor,
});
const [response, setResponse] = usePrefilledResponse(form);
const pageSearchParams = useCompatSearchParams();
const isBookingDryRun = pageSearchParams?.get("cal.isBookingDryRun") === "true";
// 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 chosenRouteWithFormResponseRef = useRef<{
route: NonRouterRoute;
response: FormResponse;
}>();
const router = useRouter();
const onSubmit = (response: FormResponse) => {
const chosenRoute = findMatchingRoute({ form, response });
if (!chosenRoute) {
// This error should never happen as we ensure that fallback route is always there that matches always
throw new Error("No matching route found");
}
responseMutation.mutate({
formId: form.id,
formFillerId,
response: response,
chosenRouteId: chosenRoute.id,
isPreview: isBookingDryRun,
});
chosenRouteWithFormResponseRef.current = {
route: chosenRoute,
response,
};
};
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]);
const responseMutation = trpc.viewer.routingForms.public.response.useMutation({
onSuccess: async (data) => {
const { teamMembersMatchingAttributeLogic, formResponse, attributeRoutingConfig } = data;
const chosenRouteWithFormResponse = chosenRouteWithFormResponseRef.current;
if (!chosenRouteWithFormResponse) {
return;
}
const fields = form.fields;
if (!fields) {
throw new Error("Routing Form fields must exist here");
}
const allURLSearchParams = getUrlSearchParamsToForward({
formResponse: chosenRouteWithFormResponse.response,
formResponseId: formResponse.id,
fields,
searchParams: new URLSearchParams(window.location.search),
teamMembersMatchingAttributeLogic,
attributeRoutingConfig: attributeRoutingConfig ?? null,
});
const chosenRoute = chosenRouteWithFormResponse.route;
const decidedAction = chosenRoute.action;
sdkActionManager?.fire("routed", {
actionType: decidedAction.type,
actionValue: decidedAction.value,
});
//TODO: Maybe take action after successful mutation
if (decidedAction.type === "customPageMessage") {
setCustomPageMessage(decidedAction.value);
} else if (decidedAction.type === "eventTypeRedirectUrl") {
const eventTypeUrlWithResolvedVariables = substituteVariables(decidedAction.value, response, fields);
router.push(
getAbsoluteEventTypeRedirectUrlWithEmbedSupport({
form,
eventTypeRedirectUrl: eventTypeUrlWithResolvedVariables,
allURLSearchParams,
isEmbed: !!isEmbed,
})
);
} else if (decidedAction.type === "externalRedirectUrl") {
navigateInTopWindow(`${decidedAction.value}?${allURLSearchParams}`);
}
// We don't want to show this message as it doesn't look good in Embed.
// showToast("Form submitted successfully! Redirecting now ...", "success");
},
onError: (e) => {
if (e?.message) {
return void showToast(e?.message, "error");
}
if (e?.data?.code === "CONFLICT") {
return void showToast("Form already submitted", "error");
}
// We don't want to show this error as it doesn't look good in Embed.
// showToast("Something went wrong", "error");
},
});
const handleOnSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
onSubmit(response);
};
const { t } = useLocale();
return (
<div>
<div>
{!customPageMessage ? (
<>
<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">
<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 ">
<Toaster position="bottom-right" />
<form onSubmit={handleOnSubmit}>
<div className="mb-8">
<h1 className="font-cal text-emphasis mb-1 text-xl font-semibold tracking-wide">
{form.name}
</h1>
{form.description ? (
<p className="min-h-10 text-subtle text-sm ltr:mr-4 rtl:ml-4">{form.description}</p>
) : null}
</div>
<FormInputFields form={form} response={response} setResponse={setResponse} />
<div className="mt-4 flex justify-end space-x-2 rtl:space-x-reverse">
<Button
className="dark:bg-darkmodebrand dark:text-darkmodebrandcontrast dark:hover:border-darkmodebrandcontrast dark:border-transparent"
loading={responseMutation.isPending}
type="submit"
color="primary">
{t("submit")}
</Button>
</div>
</form>
</div>
</div>
</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">
<div className="main sm:border-subtle bg-default -mx-4 rounded-md border border-neutral-200 p-4 py-6 sm:mx-0 sm:px-8">
<div className="text-emphasis">{customPageMessage}</div>
</div>
</div>
</div>
)}
</div>
</div>
);
}
export default function RoutingLink(props: inferSSRProps<typeof getServerSideProps>) {
return <RoutingForm {...props} />;
}
export { getServerSideProps };
const usePrefilledResponse = (form: Props["form"]) => {
const searchParams = useCompatSearchParams();
const prefillResponse: FormResponse = {};
// Prefill the form from query params
form.fields?.forEach((field) => {
const valuesFromQuery = searchParams?.getAll(getFieldIdentifier(field)).filter(Boolean) ?? [];
// We only want to keep arrays if the field is a multi-select
const value = valuesFromQuery.length > 1 ? valuesFromQuery : valuesFromQuery[0];
prefillResponse[field.id] = {
value: getFieldResponseForJsonLogic({ field, value }),
label: field.label,
};
});
const [response, setResponse] = useState<FormResponse>(prefillResponse);
return [response, setResponse] as const;
};