* Add queued booking response table * Create `RoutingFormResponseRepository` * Pass `queueFormResponse` param * Queue up form response if param is passed * Forward queued form response parma to booker * Pass `queuedFormResponse` from booker to `handleNewBooking` * Write queued routing form response * Type fixes * Clean up * Allow dry run to work which wont have any QueuedFormResponse or FormResponse * Support passing the time when the modal was actually shown to the user and consider that time as the time of form submission * fix ts error * Queue -> Response through separate endpoint that would be triggered by embed * Make queueResponseId a non-guessable uid * Change queueFormResponse query param * fix ts error * Support useQueuedResponse to record new response data * revert handleNewBooking * Remove dead code formResponse * Refactor use repository * Unify migration files * refactor: moved api endpoint to app dir Signed-off-by: Omar López <zomars@me.com> * Update formResponse.ts * Refactor use-queued-response for test * Add tests * Fix ts error and unit test. recordFormResponse cant return nullish response * fix schema * feat: Support full reuse of preloaded iframe (#21803) * feat: support updating cal video settings in API v2 (#21784) * feat: support updating cal video settings in API v2 * chore: update descriptio * feat: support create event type * test: add test for updating event type * test: add test for create event type * chore: undo openapi * chore: bump libraries * Revert "chore: bump libraries" This reverts commit bdf36d09b021fc531497a7b7ea66ab9c52b7d136. * chore: bump libraries --------- Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com> Co-authored-by: supalarry <laurisskraucis@gmail.com> * fix tests and ts * Fix tests * wip-useQueuedResponseEndpoint * Add one more test * Change queueFormResponse query param * wip * Support useQueuedResponse to record new response data * Use the update useQueuedResponse endpoint * self-review addressed * Use queuedResponse if available in slots/utils * Add documentation * Remove use-queued-response from critical-path --------- Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com> Co-authored-by: supalarry <laurisskraucis@gmail.com> * Update schema.prisma * refactor: renamed to avoid react hooks confusion Signed-off-by: Omar López <zomars@me.com> --------- Signed-off-by: Omar López <zomars@me.com> Co-authored-by: Hariom Balhara <hariombalhara@gmail.com> Co-authored-by: Omar López <zomars@me.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com> Co-authored-by: supalarry <laurisskraucis@gmail.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com>
243 lines
12 KiB
HTML
243 lines
12 KiB
HTML
<html>
|
|
|
|
<head>
|
|
<title>Embed Playground - Routing Form</title>
|
|
<script type="text/javascript" src="./playground/lib/playground-init.ts"></script>
|
|
<script>
|
|
if (!location.search.includes("nonResponsive")) {
|
|
document.write('<meta name="viewport" content="width=device-width"/>');
|
|
}
|
|
|
|
(function addParamsAutomatically() {
|
|
const newSearchParams = new URLSearchParams(location.search);
|
|
let paramsChanged = false;
|
|
if (!newSearchParams.has("only")) {
|
|
newSearchParams.set("only", "ns:routingFormPrerender");
|
|
paramsChanged = true;
|
|
}
|
|
|
|
if (!newSearchParams.has("calOrigin")) {
|
|
newSearchParams.set("calOrigin", "http://acme.cal.local:3000");
|
|
paramsChanged = true;
|
|
}
|
|
|
|
if (!newSearchParams.has("cal.embed.logging")) {
|
|
newSearchParams.set("cal.embed.logging", "1");
|
|
paramsChanged = true;
|
|
}
|
|
|
|
if (paramsChanged) {
|
|
location.href = "?" + newSearchParams.toString();
|
|
}
|
|
})();
|
|
|
|
</script>
|
|
<script>
|
|
function embedSnippet() {
|
|
(function (C, A, L) {
|
|
let p = function (a, ar) {
|
|
a.q.push(ar);
|
|
};
|
|
let d = C.document;
|
|
C.Cal =
|
|
C.Cal ||
|
|
function () {
|
|
let cal = C.Cal;
|
|
let ar = arguments;
|
|
if (!cal.loaded) {
|
|
cal.ns = {};
|
|
cal.q = cal.q || [];
|
|
d.head.appendChild(d.createElement("script")).src = A;
|
|
cal.loaded = true;
|
|
}
|
|
if (ar[0] === L) {
|
|
const api = function () {
|
|
p(api, arguments);
|
|
};
|
|
const namespace = ar[1];
|
|
api.q = api.q || [];
|
|
if (typeof namespace === "string") {
|
|
// Make sure that even after re-execution of the snippet, the namespace is not overridden
|
|
cal.ns[namespace] = cal.ns[namespace] || api;
|
|
p(cal.ns[namespace], ar);
|
|
p(cal, ['initNamespace', namespace])
|
|
} else p(cal, ar);
|
|
return;
|
|
}
|
|
p(cal, ar);
|
|
};
|
|
})(window, window.calOrigin + "/embed/embed.js", "init");
|
|
}
|
|
embedSnippet();
|
|
</script>
|
|
|
|
<style>
|
|
body {
|
|
background: linear-gradient(90deg,
|
|
rgba(120, 116, 186, 1) 0%,
|
|
rgba(221, 221, 255, 1) 41%,
|
|
rgba(148, 232, 249, 1) 100%);
|
|
}
|
|
|
|
.inline-embed-container {
|
|
/* border: 1px solid black; */
|
|
margin-bottom: 5px;
|
|
border-bottom: 1px solid;
|
|
}
|
|
|
|
.loader {
|
|
color: green;
|
|
}
|
|
|
|
* {
|
|
--cal-brand-color: gray;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div style="display: flex; flex-direction: column; gap: 10px;">
|
|
<div id="cal-booking-place-routingFormWithoutPrerender">
|
|
<a href="?only=ns:routingFormWithoutPrerender">Routing Form Without Prerender Demo</a>
|
|
<form id="cal-booking-place-routingFormWithoutPrerender-form">
|
|
<input type="text" name="name" placeholder="John Doe" />
|
|
<input type="email" name="email" placeholder="john@example.com" />
|
|
<select name="skills" placeholder="JavaScript, Node.js">
|
|
<option value="JavaScript">JavaScript</option>
|
|
<option value="Sales">Sales</option>
|
|
</select>
|
|
</form>
|
|
<button id="cal-booking-place-routingFormWithoutPrerender-submit" data-cal-namespace="routingFormWithoutPrerender" data-cal-config='{"cal.embed.pageType":"team.event.booking.slots", "guests":["guest1@example.com", "guest2@example.com"]}'>Submit</button>
|
|
<script>
|
|
requestAnimationFrame(function updateSubmitButtonLink() {
|
|
const seededFormAcmeId = "948ae412-d995-4865-885a-48302588de03";
|
|
const form = document.getElementById("cal-booking-place-routingFormWithoutPrerender-form");
|
|
const name = form.querySelector("input[name='name']").value;
|
|
const email = form.querySelector("input[name='email']").value;
|
|
const skills = form.querySelector("select[name='skills']").value;
|
|
if (name && email) {
|
|
document.getElementById("cal-booking-place-routingFormWithoutPrerender-submit").setAttribute("data-cal-link", `router?form=${seededFormAcmeId}&email=${email}&name=${name}&Location=London&Department=Engineering&Manager=John Doe&Rating=5&skills=${skills}&Email=${email}`);
|
|
}
|
|
requestAnimationFrame(updateSubmitButtonLink);
|
|
});
|
|
</script>
|
|
</div>
|
|
<div id="cal-booking-place-routingFormPrerender">
|
|
<hr/>
|
|
<a href="?only=ns:routingFormPrerender">Routing Form Prerender Team Booking Page - Demo</a>
|
|
<p>1. As soon as you start typing in the form, the form will prerender the booking page(where redirect is supposed to happen)</p>
|
|
<p>2. Clicking on submit will open the prerendered/being prerendered iframe in the modal, sending fresh request for headless router, followed by fetching the slots</p>
|
|
<p>3. Changing the form response and clicking submit, will re-submit the response and reopen the modal with the updated data(update slots call, updated prefill fields)</p>
|
|
<p>4. Clicking submit again without changing the form response will just re-show the hidden modal(as long as slotsStaleTimeMs threshold is not breached). But, in case of an error it iframe always tries to rerender. Trying adding an invalid email to see the error</p>
|
|
<form id="cal-booking-place-routingFormPrerender-form">
|
|
<input type="text" name="name" placeholder="John Doe" />
|
|
<input type="email" name="email" placeholder="john@example.com" />
|
|
<select name="skills" placeholder="JavaScript, Node.js">
|
|
<option value="JavaScript">JavaScript</option>
|
|
<option value="Sales">Sales</option>
|
|
</select>
|
|
</form>
|
|
<button id="cal-booking-place-routingFormPrerender-submit" data-cal-namespace="routingFormPrerender" data-cal-config='{"cal.embed.pageType":"team.event.booking.slots", "guests":["guest1@example.com", "guest2@example.com"]}'>Submit</button>
|
|
<script>
|
|
document.getElementById("cal-booking-place-routingFormPrerender-form").addEventListener("input", function prerender(e) {
|
|
if (prerender.initiated) return;
|
|
prerender.initiated = true;
|
|
console.log("Playground: prerender initiated");
|
|
Cal.ns.routingFormPrerender("prerender", {
|
|
calLink: "team/insights-team/team-javascript",
|
|
type: "modal",
|
|
pageType: "user.event.booking.slots",
|
|
});
|
|
});
|
|
|
|
requestAnimationFrame(function updateSubmitButtonLink() {
|
|
const seededFormAcmeId = "948ae412-d995-4865-885a-48302588de03";
|
|
const form = document.getElementById("cal-booking-place-routingFormPrerender-form");
|
|
const name = form.querySelector("input[name='name']").value;
|
|
const email = form.querySelector("input[name='email']").value;
|
|
const skills = form.querySelector("select[name='skills']").value;
|
|
if (name && email) {
|
|
document.getElementById("cal-booking-place-routingFormPrerender-submit").setAttribute("data-cal-link", `router?form=${seededFormAcmeId}&email=${email}&name=${name}&Location=London&Department=Engineering&Manager=John Doe&Rating=5&skills=${skills}&Email=${email}`);
|
|
}
|
|
requestAnimationFrame(updateSubmitButtonLink);
|
|
});
|
|
</script>
|
|
</div>
|
|
<div id="cal-booking-routing-full-prerender">
|
|
<hr/>
|
|
<a href="?only=ns:routing-full-prerender&debug=1¶m.email=test@example.com">Routing Form - Prerender Headless Router itself queuing the form response</a>
|
|
<p>1. As page is loading, we prerender the headless router for the email passed as param.email</p>
|
|
<p>2. Whenever email changes and onblur happens, the prerender is triggered for the new email and previous prerendered modal is removed</p>
|
|
<p>3. The prerender of the headless router queues the form response and doesn't really record it. When the actual booking is made, the form response is recorded from the queue</p>
|
|
<p>4. If the CTA click happens before the slots are considered stale(configurable via options.slotsStaleTimeMs), then it will open the prerendered modal without fetching the slots, otherwise it will fetch the slots(for the routedTeamMembers/contactOwner only) and till then skeleton will be shown</p>
|
|
<p>5. If the CTA click happens after iframeForceReloadThresholdMs has passed, then fresh headless router request is sent which could be really slow. It is important to do force reload after a certain time because the Routing Form itself could have changed in the meantime or Salesforce ownership might be available or some other change might have occured in Cal.com's side</p>
|
|
<p>6. slotsStaleTimeMs is set to 10 seconds(default is 1 minute) and iframeForceReloadThresholdMs is set to 30 seconds(default is 15 minutes) and they are considered from the time when the prerender/modal call was made</p>
|
|
<p>7. To avoid reaching the iframeForceReloadThresholdMs, user could prerender the router again and again judiciously</p>
|
|
</p>
|
|
<script>
|
|
window.buildRouterUrl = ({email, formId}) => {
|
|
return `router?form=${formId}&email=${email}`;
|
|
}
|
|
</script>
|
|
<div class="inline-embed-container">
|
|
<div id="cal-booking-place-routing-full-prerender">
|
|
<div class="place"></div>
|
|
<label for="cal-booking-place-routing-full-prerender-input-email">Email</label>
|
|
<script>
|
|
document.write(`<input style="width: 400px" type="email" id="cal-booking-place-routing-full-prerender-input-email" value="${window.params.email ?? ''}" />`)
|
|
</script>
|
|
</div>
|
|
</div>
|
|
<h3><script>
|
|
document.write(`<button id="cta-routing-full-prerender" data-cal-namespace="routingFormFullPrerender" data-cal-origin="${window.calOrigin}" data-cal-link="${window.buildRouterUrl({email:document.getElementById('cal-booking-place-routing-full-prerender-input-email').value, formId:window.params.formId})}" data-cal-config='{"cal.embed.pageType":"team.event.booking.slots"}'>Demo</button>`)
|
|
</script></h3>
|
|
<script>
|
|
(()=>{
|
|
if (window.only !== "ns:routing-full-prerender") {
|
|
return;
|
|
}
|
|
Cal("init", "routingFormFullPrerender", {
|
|
debug: true,
|
|
calOrigin: window.calOrigin,
|
|
});
|
|
const prerender = ({email, formId}) => {
|
|
Cal.ns.routingFormFullPrerender("prerender", {
|
|
// Email will be automatically passed as query param
|
|
calLink: window.buildRouterUrl({email, formId}),
|
|
type: "modal",
|
|
pageType: "team.event.booking.slots",
|
|
options: {
|
|
slotsStaleTimeMs: 10 * 1000,
|
|
iframeForceReloadThresholdMs: 30 * 1000,
|
|
},
|
|
});
|
|
}
|
|
document.getElementById('cal-booking-place-routing-full-prerender-input-email').onchange = function onEmailChange(e) {
|
|
const element = e.target;
|
|
const email = element.validity.valid ? element.value : null;
|
|
if (!email) {
|
|
return;
|
|
}
|
|
function updateCtaLink(email) {
|
|
const element = document.getElementById('cta-routing-full-prerender');
|
|
const currentLink = element.getAttribute('data-cal-link');
|
|
const currentLinkObject = new URL(currentLink, 'https://example.com');
|
|
currentLinkObject.searchParams.set('email', email);
|
|
element.setAttribute('data-cal-link', currentLinkObject.pathname + currentLinkObject.search);
|
|
}
|
|
updateCtaLink(email);
|
|
prerender({email, formId:window.params.formId});
|
|
}
|
|
if (!window.params.email || !window.params.formId) {
|
|
alert('Pass param.email and param.formId query params')
|
|
}
|
|
prerender({email:window.params.email, formId:window.params.formId});
|
|
})();
|
|
</script>
|
|
</div>
|
|
</div>
|
|
<script type="module" src="./playground/lib/playground.ts"></script>
|
|
</script>
|
|
</body>
|
|
|
|
</html> |