Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import countries from "../../../../../data/countries.json";
import classes from "./CollectInformation.module.scss";
import {trackEvent, AnalyticsEvents} from "../../../../utilites/analytics.ts";
import {clearWaitlistJoinedForEvent} from "../../../../hooks/useWaitlistJoined.ts";
import {useCheckoutPrefill, CheckoutPrefill} from "../../../../hooks/useCheckoutPrefill.ts";

const LoadingSkeleton = () =>
(
Expand All @@ -48,6 +49,8 @@ export const CollectInformation = () => {
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const isFromWaitlist = searchParams.get('waitlist') === 'true';
const {prefill, lock} = useCheckoutPrefill();
const isLocked = (field: keyof CheckoutPrefill) => lock && prefill[field] !== undefined;
const {
isFetched: isOrderFetched,
data: order,
Expand Down Expand Up @@ -296,18 +299,38 @@ export const CollectInformation = () => {

useEffect(() => {
if (isEventFetched && isOrderFetched && isQuestionsFetched && productQuestions && orderQuestions) {
const products = createProductsAndQuestions(createProductIdToQuestionMap());
const builtProducts = createProductsAndQuestions(createProductIdToQuestionMap());
const formOrderQuestions = createFormOrderQuestions();

const orderPrefill = {
...(prefill.first_name !== undefined && {first_name: prefill.first_name}),
...(prefill.last_name !== undefined && {last_name: prefill.last_name}),
...(prefill.email !== undefined && {email: prefill.email, email_confirmation: prefill.email}),
};

const ticketProductIds = new Set(
(products ?? [])
.filter(product => product && product.product_type === 'TICKET')
.map(product => product!.id)
);

const prefilledProducts = builtProducts.map((product: any) =>
(!isPerOrderCollection && ticketProductIds.has(product.product_id))
? {...product, ...orderPrefill}
: product
);

form.setValues({
...form.values,
products: products,
products: prefilledProducts,
order: {
...form.values.order,
...orderPrefill,
questions: formOrderQuestions,
},
});
}
// prefill/lock intentionally omitted: they're memoized off query params that don't change during the page's lifetime
}, [isEventFetched, isOrderFetched, isQuestionsFetched]);

useEffect(() => {
Expand Down Expand Up @@ -433,12 +456,14 @@ export const CollectInformation = () => {
withAsterisk
label={t`First Name`}
placeholder={t`First name`}
disabled={isLocked('first_name')}
{...form.getInputProps("order.first_name")}
/>
<TextInput
withAsterisk
label={t`Last Name`}
placeholder={t`Last Name`}
disabled={isLocked('last_name')}
{...form.getInputProps("order.last_name")}
/>
</InputGroup>
Expand All @@ -449,6 +474,7 @@ export const CollectInformation = () => {
type={"email"}
label={t`Email Address`}
placeholder={t`Email Address`}
disabled={isLocked('email')}
rightSection={isEmailValid(form.values.order.email) ? <EmailCheckIcon/> : null}
{...form.getInputProps("order.email")}
/>
Expand All @@ -457,12 +483,13 @@ export const CollectInformation = () => {
type={"email"}
label={t`Confirm Email Address`}
placeholder={t`Confirm Email Address`}
disabled={isLocked('email')}
rightSection={isEmailValid(form.values.order.email_confirmation) ? <EmailCheckIcon/> : null}
{...form.getInputProps("order.email_confirmation")}
/>
</InputGroup>

{orderRequiresAttendeeDetails && !isPerOrderCollection && totalTicketAttendees > 0 && (
{orderRequiresAttendeeDetails && !isPerOrderCollection && totalTicketAttendees > 0 && !lock && (
<div className={classes.copyDetailsSection}>
{totalTicketAttendees === 1 ? (
<Tooltip
Expand Down Expand Up @@ -645,12 +672,14 @@ export const CollectInformation = () => {
withAsterisk
label={t`First Name`}
placeholder={t`First name`}
disabled={isLocked('first_name')}
{...form.getInputProps(`products.${currentProductIndex}.first_name`)}
/>
<TextInput
withAsterisk
label={t`Last Name`}
placeholder={t`Last Name`}
disabled={isLocked('last_name')}
{...form.getInputProps(`products.${currentProductIndex}.last_name`)}
/>
</InputGroup>
Expand All @@ -661,6 +690,7 @@ export const CollectInformation = () => {
type={"email"}
label={t`Email Address`}
placeholder={t`Email Address`}
disabled={isLocked('email')}
rightSection={isEmailValid(form.values.products[currentProductIndex]?.email || '') ?
<EmailCheckIcon/> : null}
{...form.getInputProps(`products.${currentProductIndex}.email`)}
Expand All @@ -670,6 +700,7 @@ export const CollectInformation = () => {
type={"email"}
label={t`Confirm Email Address`}
placeholder={t`Confirm Email Address`}
disabled={isLocked('email')}
rightSection={isEmailValid(form.values.products[currentProductIndex]?.email_confirmation || '') ?
<EmailCheckIcon/> : null}
{...form.getInputProps(`products.${currentProductIndex}.email_confirmation`)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {IconChevronRight, IconX} from "@tabler/icons-react"
import {getSessionIdentifier} from "../../../../utilites/sessionIdentifier.ts";
import {Constants} from "../../../../constants.ts";
import {clearWaitlistJoinedForEvent} from "../../../../hooks/useWaitlistJoined.ts";
import {CHECKOUT_PREFILL_PARAM_KEYS} from "../../../../hooks/useCheckoutPrefill.ts";

const AFFILIATE_EXPIRY_DAYS = 30;

Expand Down Expand Up @@ -150,16 +151,29 @@ const SelectProducts = (props: SelectProductsProps) => {
onSuccess: (data) => queryClient.invalidateQueries()
.then(() => {
const url = '/checkout/' + eventId + '/' + data.data.short_id + '/details';

// Forward checkout-prefill params (name/email/lock) from the event page
// to the details step, since this navigation would otherwise drop them.
const sourceParams = new URLSearchParams(window.location.search);
const prefillParams = new URLSearchParams();
CHECKOUT_PREFILL_PARAM_KEYS.forEach((key) => {
const value = sourceParams.get(key);
if (value !== null) {
prefillParams.set(key, value);
}
});
const prefillQuery = prefillParams.toString();

if (props.widgetMode === 'embedded') {
window.open(
url + '?session_identifier=' + data.data.session_identifier + '&utm_source=embedded_widget',
'_blank'
);
const embeddedQuery = 'session_identifier=' + data.data.session_identifier
+ '&utm_source=embedded_widget'
+ (prefillQuery ? '&' + prefillQuery : '');
window.open(url + '?' + embeddedQuery, '_blank');
setOrderInProcessOverlayVisible(true);
return;
}

return navigate(url);
return navigate(url + (prefillQuery ? '?' + prefillQuery : ''));
}),

onError: (error: any) => {
Expand Down
53 changes: 53 additions & 0 deletions frontend/src/hooks/useCheckoutPrefill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {useMemo} from "react";
import {useSearchParams} from "react-router";

export interface CheckoutPrefill {
first_name?: string;
last_name?: string;
email?: string;
}

export interface UseCheckoutPrefillResult {
prefill: CheckoutPrefill;
lock: boolean;
}

// Query params the checkout details form reads to prefill itself. Also forwarded
// from the event page to the details step so prefill survives order creation
// (see SelectProducts).
export const CHECKOUT_PREFILL_PARAM_KEYS = ["first_name", "last_name", "email", "lock"] as const;

const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

export const useCheckoutPrefill = (): UseCheckoutPrefillResult => {
const [searchParams] = useSearchParams();

const firstNameParam = searchParams.get("first_name");
const lastNameParam = searchParams.get("last_name");
const emailParam = searchParams.get("email");
const lockParam = searchParams.get("lock");

return useMemo(() => {
const prefill: CheckoutPrefill = {};

const firstName = firstNameParam?.trim();
if (firstName) {
prefill.first_name = firstName;
}

const lastName = lastNameParam?.trim();
if (lastName) {
prefill.last_name = lastName;
}

const email = emailParam?.trim();
if (email && EMAIL_REGEX.test(email)) {
prefill.email = email;
}

const hasPrefill = Object.keys(prefill).length > 0;
const lock = hasPrefill && (lockParam === "true" || lockParam === "1");

return {prefill, lock};
}, [firstNameParam, lastNameParam, emailParam, lockParam]);
};
Loading