summit-registration-lite 7.0.1 → 7.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/.github/workflows/ci.yml +54 -0
  2. package/dist/components/index.css +12 -27
  3. package/dist/components/index.css.map +1 -1
  4. package/dist/components/index.js +813 -264
  5. package/dist/components/index.js.map +1 -1
  6. package/dist/components/login-passwordless.css +1 -2
  7. package/dist/components/login-passwordless.js +21 -4
  8. package/dist/components/login-passwordless.js.map +1 -1
  9. package/dist/components/login.css +1 -2
  10. package/dist/components/login.js +69 -2
  11. package/dist/components/login.js.map +1 -1
  12. package/dist/components/registration-form.css +12 -26
  13. package/dist/components/registration-form.css.map +1 -1
  14. package/dist/components/registration-form.js +808 -260
  15. package/dist/components/registration-form.js.map +1 -1
  16. package/dist/components/registration-modal.css +12 -27
  17. package/dist/components/registration-modal.css.map +1 -1
  18. package/dist/components/registration-modal.js +812 -263
  19. package/dist/components/registration-modal.js.map +1 -1
  20. package/dist/index.css +12 -27
  21. package/dist/index.css.map +1 -1
  22. package/dist/index.js +812 -263
  23. package/dist/index.js.map +1 -1
  24. package/dist/utils/constants.js +11 -1
  25. package/dist/utils/constants.js.map +1 -1
  26. package/e2e/fixtures.js +84 -0
  27. package/e2e/promo-code-discovery.spec.js +364 -0
  28. package/package.json +12 -4
  29. package/playwright.config.js +26 -0
  30. package/.claude/rules/summit-registration-lite-component-props.md +0 -95
  31. package/.claude/rules/summit-registration-lite-i18n.md +0 -62
  32. package/.claude/rules/summit-registration-lite-payment-providers.md +0 -69
  33. package/.claude/rules/summit-registration-lite-project.md +0 -80
  34. package/.claude/rules/summit-registration-lite-redux-actions.md +0 -65
  35. package/.claude/rules/summit-registration-lite-step-flow.md +0 -77
  36. package/.claude/rules/summit-registration-lite-testing.md +0 -97
  37. package/.claude/rules/summit-registration-lite-utils.md +0 -71
  38. package/.claude/skills/summit-registration-lite-add-provider/SKILL.md +0 -155
  39. package/.claude/skills/summit-registration-lite-dev-setup/SKILL.md +0 -67
  40. package/.claude/skills/summit-registration-lite-publish/SKILL.md +0 -64
  41. package/.claude/skills/summit-registration-lite-scaffold-component/SKILL.md +0 -152
@@ -25,6 +25,7 @@ __webpack_require__.d(__webpack_exports__, {
25
25
  "t$": () => (/* binding */ CLEAR_WIDGET_STATE),
26
26
  "rr": () => (/* binding */ CREATE_RESERVATION_SUCCESS),
27
27
  "bz": () => (/* binding */ DELETE_RESERVATION_SUCCESS),
28
+ "dQ": () => (/* binding */ DISCOVER_PROMO_CODES_SUCCESS),
28
29
  "Cw": () => (/* binding */ GET_MY_INVITATION),
29
30
  "bx": () => (/* binding */ GET_TAX_TYPES),
30
31
  "HC": () => (/* binding */ GET_TICKET_TYPES),
@@ -40,9 +41,14 @@ __webpack_require__.d(__webpack_exports__, {
40
41
  "Vx": () => (/* binding */ START_WIDGET_LOADING),
41
42
  "sC": () => (/* binding */ STOP_WIDGET_LOADING),
42
43
  "VH": () => (/* binding */ UPDATE_CLOCK),
44
+ "j6": () => (/* binding */ VALIDATE_PROMO_CODE),
45
+ "o5": () => (/* binding */ VALIDATE_PROMO_CODE_ERROR),
46
+ "Tk": () => (/* binding */ VALIDATE_PROMO_CODE_RATE_LIMITED),
47
+ "w6": () => (/* binding */ VALIDATE_PROMO_CODE_SUCCESS),
43
48
  "gs": () => (/* binding */ applyPromoCode),
44
49
  "sz": () => (/* binding */ changeStep),
45
50
  "YS": () => (/* binding */ clearWidgetState),
51
+ "kM": () => (/* binding */ discoverPromoCodes),
46
52
  "aH": () => (/* binding */ getLoginCode),
47
53
  "q1": () => (/* binding */ getMyInvitation),
48
54
  "xu": () => (/* binding */ getTicketTypesAndTaxes),
@@ -61,7 +67,7 @@ __webpack_require__.d(__webpack_exports__, {
61
67
  "jn": () => (/* binding */ validatePromoCode)
62
68
  });
63
69
 
64
- // UNUSED EXPORTS: CREATE_RESERVATION, CREATE_RESERVATION_ERROR, DELETE_RESERVATION, DELETE_RESERVATION_ERROR, VALIDATE_PROMO_CODE
70
+ // UNUSED EXPORTS: CREATE_RESERVATION, CREATE_RESERVATION_ERROR, DELETE_RESERVATION, DELETE_RESERVATION_ERROR, DISCOVER_PROMO_CODES
65
71
 
66
72
  ;// CONCATENATED MODULE: external "openstack-uicore-foundation/lib/utils/actions"
67
73
  const actions_namespaceObject = require("openstack-uicore-foundation/lib/utils/actions");
@@ -397,6 +403,11 @@ const LOAD_PROFILE_DATA = 'LOAD_PROFILE_DATA';
397
403
  const SET_CURRENT_PROMO_CODE = 'SET_CURRENT_PROMO_CODE';
398
404
  const CLEAR_CURRENT_PROMO_CODE = 'CLEAR_CURRENT_PROMO_CODE';
399
405
  const VALIDATE_PROMO_CODE = 'VALIDATE_PROMO_CODE';
406
+ const VALIDATE_PROMO_CODE_SUCCESS = 'VALIDATE_PROMO_CODE_SUCCESS';
407
+ const VALIDATE_PROMO_CODE_ERROR = 'VALIDATE_PROMO_CODE_ERROR';
408
+ const VALIDATE_PROMO_CODE_RATE_LIMITED = 'VALIDATE_PROMO_CODE_RATE_LIMITED';
409
+ const DISCOVER_PROMO_CODES = 'DISCOVER_PROMO_CODES';
410
+ const DISCOVER_PROMO_CODES_SUCCESS = 'DISCOVER_PROMO_CODES_SUCCESS';
400
411
  const startWidgetLoading = (0,actions_namespaceObject.createAction)(START_WIDGET_LOADING);
401
412
  const stopWidgetLoading = (0,actions_namespaceObject.createAction)(STOP_WIDGET_LOADING);
402
413
  const loadSession = settings => dispatch => {
@@ -409,6 +420,23 @@ const clearWidgetState = () => dispatch => {
409
420
  dispatch((0,actions_namespaceObject.createAction)(CLEAR_WIDGET_STATE)({}));
410
421
  };
411
422
 
423
+ const promoCodeErrorHandler = (err, res) => (dispatch, state) => {
424
+ // 404: promo code or ticket type not found
425
+ // 412: promo code invalid for this ticket type/qty
426
+ if (res && [404, 412].includes(res.statusCode)) {
427
+ dispatch((0,actions_namespaceObject.createAction)(VALIDATE_PROMO_CODE_ERROR)({}));
428
+ return;
429
+ } // 429: rate limited - transient, preserve current promo state
430
+
431
+
432
+ if (res && res.statusCode === 429) {
433
+ dispatch((0,actions_namespaceObject.createAction)(VALIDATE_PROMO_CODE_RATE_LIMITED)({}));
434
+ return;
435
+ }
436
+
437
+ return (0,actions_namespaceObject.authErrorHandler)(err, res)(dispatch, state);
438
+ };
439
+
412
440
  const customErrorHandler = (err, res) => (dispatch, state) => {
413
441
  if (err.timeout) {
414
442
  return err;
@@ -426,13 +454,35 @@ const customErrorHandler = (err, res) => (dispatch, state) => {
426
454
  };
427
455
  /*********************************************************************************/
428
456
 
457
+ /* PROMO CODE DISCOVERY */
458
+
459
+ /*********************************************************************************/
460
+
461
+
462
+ const discoverPromoCodes = summitId => async (dispatch, getState, {
463
+ apiBaseUrl,
464
+ getAccessToken
465
+ }) => {
466
+ try {
467
+ const accessToken = await getAccessToken();
468
+ return (0,actions_namespaceObject.getRequest)((0,actions_namespaceObject.createAction)(DISCOVER_PROMO_CODES), (0,actions_namespaceObject.createAction)(DISCOVER_PROMO_CODES_SUCCESS), `${apiBaseUrl}/api/v1/summits/${summitId}/promo-codes/all/discover`, // Discovery is non-blocking - errors silently ignored.
469
+ // Auth errors will surface on the next user-initiated action.
470
+ null)({
471
+ access_token: accessToken
472
+ })(dispatch);
473
+ } catch (e) {
474
+ console.log(e);
475
+ return null;
476
+ }
477
+ };
478
+ /*********************************************************************************/
479
+
429
480
  /* TICKETS */
430
481
 
431
482
  /*********************************************************************************/
432
483
  // api/v1/summits/{id}/ticket-types/allowed
433
484
  // api/v1/summits/{id}/tax-types
434
485
 
435
-
436
486
  const getTicketTypesAndTaxes = summitId => async dispatch => {
437
487
  return Promise.all([dispatch(getTicketTypes(summitId)), dispatch(getTaxesTypes(summitId))]).then(values => {
438
488
  return values;
@@ -504,22 +554,23 @@ const getTaxesTypes = summitId => async (dispatch, getState, {
504
554
  }
505
555
  };
506
556
 
507
- const applyPromoCode = currentPromoCode => (dispatch, getState) => {
508
- try {
509
- const {
510
- registrationLiteState: {
511
- settings: {
512
- summitId
513
- }
557
+ const applyPromoCode = currentPromoCode => async (dispatch, getState) => {
558
+ const {
559
+ registrationLiteState: {
560
+ settings: {
561
+ summitId
514
562
  }
515
- } = getState(); // set the current promo code and get ticket types again
563
+ }
564
+ } = getState();
516
565
 
566
+ try {
517
567
  dispatch((0,actions_namespaceObject.createAction)(SET_CURRENT_PROMO_CODE)({
518
568
  currentPromoCode
519
569
  }));
520
- dispatch(getTicketTypes(summitId));
570
+ await dispatch(getTicketTypes(summitId));
521
571
  } catch (e) {
522
- return Promise.reject(e);
572
+ dispatch((0,actions_namespaceObject.createAction)(CLEAR_CURRENT_PROMO_CODE)({}));
573
+ throw e;
523
574
  }
524
575
  };
525
576
  const removePromoCode = () => (dispatch, getState) => {
@@ -539,9 +590,13 @@ const removePromoCode = () => (dispatch, getState) => {
539
590
  return Promise.reject(e);
540
591
  }
541
592
  };
542
- const validatePromoCode = (ticketData, {
543
- onError
544
- }) => async (dispatch, getState, {
593
+ /**
594
+ * Validates promo code for a specific ticket selection.
595
+ * Stores allows_to_reassign in Redux state.
596
+ * Returns response or throws error - caller handles UI concerns.
597
+ */
598
+
599
+ const validatePromoCode = ticketData => async (dispatch, getState, {
545
600
  apiBaseUrl,
546
601
  getAccessToken
547
602
  }) => {
@@ -552,61 +607,24 @@ const validatePromoCode = (ticketData, {
552
607
  },
553
608
  promoCode: currentPromoCode
554
609
  }
555
- } = getState();
556
- const {
557
- promoCode: formPromoCode
558
- } = ticketData;
559
-
560
- if (formPromoCode && !currentPromoCode) {
561
- const defaultMessage = `You entered a promo code but it hasn't been applied. Make sure to click the 'Apply' button or remove it before continuing.`;
562
- const notAppliedCodeError = {
563
- body: {
564
- errors: [defaultMessage]
565
- }
566
- };
567
- return onError(null, notAppliedCodeError);
568
- }
569
-
570
- if (summitId && currentPromoCode) {
571
- dispatch(startWidgetLoading());
572
- const {
573
- ticketQuantity,
574
- id,
575
- sub_type
576
- } = ticketData;
577
- const access_token = await getAccessToken();
578
- let apiUrl = external_urijs_default()(`${apiBaseUrl}/api/v1/summits/${summitId}/promo-codes/${currentPromoCode}/apply`);
579
- apiUrl.addQuery('access_token', access_token);
580
- apiUrl.addQuery('filter[]', `ticket_type_id==${id}`);
581
- apiUrl.addQuery('filter[]', `ticket_type_qty==${ticketQuantity}`);
582
- apiUrl.addQuery('filter[]', `ticket_type_subtype==${sub_type}`);
583
-
584
- const errorHandler = (err, res) => (dispatch, state) => {
585
- if (res && res.statusCode === 404 && onError) return onError(err, res);
586
- if (res && res.statusCode === 412 && onError) return onError(err, res);
587
- if (res && res.statusCode === 429 && onError) return onError(err, res);
588
-
589
- if (res && res.statusCode === 500) {
590
- const defaultMessage = 'Server Error';
591
- const msg = res?.body?.message || defaultMessage;
592
- external_sweetalert2_default().fire("Server Error", msg, "error");
593
- return;
594
- }
610
+ } = getState(); // Nothing to validate if no promo code applied
595
611
 
596
- return (0,actions_namespaceObject.authErrorHandler)(err, res)(dispatch, state);
597
- };
598
-
599
- return (0,actions_namespaceObject.getRequest)(null, (0,actions_namespaceObject.createAction)(VALIDATE_PROMO_CODE), `${apiUrl}`, errorHandler)({})(dispatch).then(res => {
600
- dispatch(changeStep(constants.STEP_PERSONAL_INFO));
601
- dispatch(stopWidgetLoading());
602
- return res;
603
- }).catch(error => {
604
- dispatch(stopWidgetLoading());
605
- return Promise.reject(error);
606
- });
612
+ if (!summitId || !currentPromoCode) {
613
+ return null;
607
614
  }
608
615
 
609
- return dispatch(changeStep(constants.STEP_PERSONAL_INFO));
616
+ const {
617
+ ticketQuantity = 1,
618
+ id,
619
+ sub_type
620
+ } = ticketData;
621
+ const access_token = await getAccessToken();
622
+ let apiUrl = external_urijs_default()(`${apiBaseUrl}/api/v1/summits/${summitId}/promo-codes/${currentPromoCode}/apply`);
623
+ apiUrl.addQuery('access_token', access_token);
624
+ apiUrl.addQuery('filter[]', `ticket_type_id==${id}`);
625
+ apiUrl.addQuery('filter[]', `ticket_type_qty==${ticketQuantity}`);
626
+ apiUrl.addQuery('filter[]', `ticket_type_subtype==${sub_type}`);
627
+ return (0,actions_namespaceObject.getRequest)((0,actions_namespaceObject.createAction)(VALIDATE_PROMO_CODE), (0,actions_namespaceObject.createAction)(VALIDATE_PROMO_CODE_SUCCESS), `${apiUrl}`, promoCodeErrorHandler)({})(dispatch);
610
628
  };
611
629
  const reserveTicket = ({
612
630
  provider,
@@ -1326,7 +1344,7 @@ LoginComponent.defaultProps = {
1326
1344
 
1327
1345
  /***/ }),
1328
1346
 
1329
- /***/ 558:
1347
+ /***/ 95:
1330
1348
  /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
1331
1349
 
1332
1350
  "use strict";
@@ -1356,6 +1374,256 @@ const external_react_use_namespaceObject = require("react-use");
1356
1374
  const constants_namespaceObject = require("openstack-uicore-foundation/lib/security/constants");
1357
1375
  // EXTERNAL MODULE: ./src/actions.js + 5 modules
1358
1376
  var actions = __webpack_require__(595);
1377
+ ;// CONCATENATED MODULE: external "i18n-react"
1378
+ const external_i18n_react_namespaceObject = require("i18n-react");
1379
+ var external_i18n_react_default = /*#__PURE__*/__webpack_require__.n(external_i18n_react_namespaceObject);
1380
+ // EXTERNAL MODULE: ./src/utils/constants.js
1381
+ var constants = __webpack_require__(243);
1382
+ ;// CONCATENATED MODULE: ./src/hooks/usePromoCode.js
1383
+
1384
+
1385
+
1386
+
1387
+ const usePromoCode = ({
1388
+ // Redux state
1389
+ discoveredPromoCodes,
1390
+ promoCode,
1391
+ promoCodeVerified,
1392
+ promoCodeValidating,
1393
+ // Redux dispatchers
1394
+ applyPromoCode,
1395
+ removePromoCode,
1396
+ validatePromoCode,
1397
+ // Form integration
1398
+ ticketDataLoaded = false,
1399
+ hasTickets = false,
1400
+ setFormPromoCode
1401
+ }) => {
1402
+ // Per-session lock: once the user removes (or auto-apply fails for) a
1403
+ // discovered code, don't re-apply it on this widget mount. Never reset.
1404
+ const [userRemovedAutoApply, setUserRemovedAutoApply] = (0,external_react_.useState)(false);
1405
+ const [isAutoApplied, setIsAutoApplied] = (0,external_react_.useState)(false);
1406
+ const [suggestionActive, setSuggestionActive] = (0,external_react_.useState)(false);
1407
+ const [suggestionDismissed, setSuggestionDismissed] = (0,external_react_.useState)(false); // Error written by handleValidationError (API) or the form (unapplied-code warning).
1408
+ // The user-facing `validationError` is computed below by merging this with the
1409
+ // status-derived INVALID message.
1410
+
1411
+ const [manualError, setManualError] = (0,external_react_.useState)(null);
1412
+ const [applyingCode, setApplyingCode] = (0,external_react_.useState)(false); // Pick first auto_apply code, or first code if none has auto_apply
1413
+
1414
+ const discoveredPromoCode = (0,external_react_.useMemo)(() => {
1415
+ if (!discoveredPromoCodes?.length) return null;
1416
+ return discoveredPromoCodes.find(c => c.auto_apply) || discoveredPromoCodes[0];
1417
+ }, [discoveredPromoCodes]);
1418
+ const isApplied = !!promoCode;
1419
+ const isDiscoveredCode = isApplied && discoveredPromoCode?.code === promoCode; // --- Status ---
1420
+
1421
+ const status = (0,external_react_.useMemo)(() => {
1422
+ if (isApplied) {
1423
+ if (promoCodeValidating) return constants.PROMO_STATUS.VALIDATING;
1424
+ if (promoCodeVerified === true) return constants.PROMO_STATUS.VALID;
1425
+ if (promoCodeVerified === false) return constants.PROMO_STATUS.INVALID; // Applied but no tickets returned and not currently applying: code is invalid
1426
+
1427
+ if (!applyingCode && ticketDataLoaded && !hasTickets) return constants.PROMO_STATUS.INVALID;
1428
+ return constants.PROMO_STATUS.APPLYING;
1429
+ }
1430
+
1431
+ if (suggestionActive && !suggestionDismissed) return constants.PROMO_STATUS.SUGGESTED;
1432
+ return constants.PROMO_STATUS.IDLE;
1433
+ }, [isApplied, promoCodeVerified, promoCodeValidating, suggestionActive, suggestionDismissed, applyingCode, ticketDataLoaded, hasTickets]); // Hook's own validation error. Composed from the in-flight API error (if any)
1434
+ // and the status-derived "invalid code" message when status is INVALID.
1435
+ // Consumers may layer their own warning on top before display.
1436
+
1437
+ const validationError = manualError ?? (status === constants.PROMO_STATUS.INVALID ? external_i18n_react_default().translate('promo_code.invalid_code') : null); // --- Derived values ---
1438
+
1439
+ const suggestedCode = discoveredPromoCode?.code || null;
1440
+ const activeDiscoveredCode = status === constants.PROMO_STATUS.VALID && isDiscoveredCode ? discoveredPromoCode : null;
1441
+ const perAccountLimit = activeDiscoveredCode?.quantity_per_account > 0 ? activeDiscoveredCode.remaining_quantity_per_account : null; // Tightest promo-code-level quantity cap for the stepper (discovered codes only).
1442
+ // Both cap sources use `!= null` so a value of 0 (sold-out / no remaining) caps the
1443
+ // stepper at 0 instead of being silently ignored.
1444
+
1445
+ const maxQuantityFromPromo = (0,external_react_.useMemo)(() => {
1446
+ if (!activeDiscoveredCode) return null;
1447
+ const caps = [];
1448
+ if (activeDiscoveredCode.remaining_quantity_per_account != null) caps.push(activeDiscoveredCode.remaining_quantity_per_account);
1449
+ if (activeDiscoveredCode.quantity_available != null) caps.push(activeDiscoveredCode.quantity_available);
1450
+ return caps.length > 0 ? Math.min(...caps) : null;
1451
+ }, [activeDiscoveredCode]); // True when the user can safely advance from the ticket step
1452
+ // (no in-flight promo apply/validate and no INVALID state to block on).
1453
+
1454
+ const isReady = status === constants.PROMO_STATUS.IDLE || status === constants.PROMO_STATUS.SUGGESTED || status === constants.PROMO_STATUS.VALID; // --- Discovery: ticket qualification ---
1455
+
1456
+ const isCodeValidForTicket = (0,external_react_.useCallback)(ticket => {
1457
+ if (!discoveredPromoCode || !ticket) return false;
1458
+ const allowed = discoveredPromoCode.allowed_ticket_types || [];
1459
+ if (allowed.length === 0) return true;
1460
+ return allowed.some(tt => (typeof tt === 'object' ? tt.id : tt) === ticket.id);
1461
+ }, [discoveredPromoCode]); // --- Helpers ---
1462
+
1463
+ const handleValidationError = (0,external_react_.useCallback)(e => {
1464
+ if (e?.res?.body) {
1465
+ const errors = e.res.body.errors || [e.res.body.message || external_i18n_react_default().translate('promo_code.validation_error')];
1466
+ const first = errors[0];
1467
+ const firstStr = typeof first === 'string' ? first : first?.message ?? String(first);
1468
+ const msg = /is not a valid code/i.test(firstStr) ? external_i18n_react_default().translate('promo_code.invalid_code') : firstStr;
1469
+ setManualError(msg);
1470
+ } else {
1471
+ setManualError(external_i18n_react_default().translate('promo_code.validation_error'));
1472
+ }
1473
+ }, []); // --- Actions ---
1474
+
1475
+ const onRevalidate = (0,external_react_.useCallback)(async (ticket, quantity) => {
1476
+ setManualError(null);
1477
+
1478
+ try {
1479
+ await validatePromoCode({
1480
+ id: ticket.id,
1481
+ ticketQuantity: quantity,
1482
+ sub_type: ticket.sub_type
1483
+ });
1484
+ return true;
1485
+ } catch (e) {
1486
+ handleValidationError(e);
1487
+ return false;
1488
+ }
1489
+ }, [validatePromoCode, handleValidationError]); // Shared auto-apply flow. Caller is responsible for the gating conditions
1490
+ // (auto_apply / single code / not already applied / not user-removed) so each
1491
+ // call site keeps its own trigger logic. Returns true if the code was applied
1492
+ // and (if a ticket was passed) successfully revalidated.
1493
+ //
1494
+ // Note on concurrency: the two call sites (early-auto-apply effect and
1495
+ // onTicketSelected's auto-apply branch) operate on disjoint states by design
1496
+ // (the effect requires `!hasTickets`, the branch requires a selected ticket),
1497
+ // so a true concurrent invocation is unreachable in practice. If a future
1498
+ // change makes that overlap possible, gate this body with a ref-tracked
1499
+ // in-flight flag rather than `applyingCode` (which is captured stale here).
1500
+
1501
+ const tryAutoApply = (0,external_react_.useCallback)(async ticket => {
1502
+ setIsAutoApplied(true);
1503
+ setApplyingCode(true);
1504
+
1505
+ try {
1506
+ await applyPromoCode(discoveredPromoCode.code);
1507
+
1508
+ if (ticket) {
1509
+ const valid = await onRevalidate(ticket, 1);
1510
+
1511
+ if (!valid) {
1512
+ setIsAutoApplied(false);
1513
+ return false;
1514
+ }
1515
+ }
1516
+
1517
+ return true;
1518
+ } catch (e) {
1519
+ setIsAutoApplied(false);
1520
+ handleValidationError(e);
1521
+ return false;
1522
+ } finally {
1523
+ setApplyingCode(false);
1524
+ }
1525
+ }, [discoveredPromoCode, applyPromoCode, onRevalidate, handleValidationError]);
1526
+ const onTicketSelected = (0,external_react_.useCallback)(async ticket => {
1527
+ const qualifies = discoveredPromoCode && isCodeValidForTicket(ticket);
1528
+ setSuggestionActive(qualifies);
1529
+ setSuggestionDismissed(false);
1530
+ setManualError(null); // Manual (non-discovered) code is applied: re-validate for new ticket
1531
+
1532
+ if (isApplied && !isDiscoveredCode) {
1533
+ await onRevalidate(ticket, 1);
1534
+ return;
1535
+ }
1536
+
1537
+ if (!discoveredPromoCode) return; // Discovered code is currently applied
1538
+
1539
+ if (isDiscoveredCode) {
1540
+ if (!qualifies) {
1541
+ setIsAutoApplied(false);
1542
+ removePromoCode();
1543
+ } else {
1544
+ const valid = await onRevalidate(ticket, 1);
1545
+ if (!valid) setIsAutoApplied(false);
1546
+ }
1547
+
1548
+ return;
1549
+ } // No code applied, ticket qualifies, auto-apply configured, single code only
1550
+
1551
+
1552
+ if (!isApplied && qualifies && discoveredPromoCode.auto_apply && !userRemovedAutoApply && discoveredPromoCodes.length === 1) {
1553
+ await tryAutoApply(ticket);
1554
+ }
1555
+ }, [discoveredPromoCode, isApplied, isDiscoveredCode, userRemovedAutoApply, discoveredPromoCodes, isCodeValidForTicket, removePromoCode, onRevalidate, tryAutoApply]); // Early auto-apply: when no tickets are available and a single auto_apply code
1556
+ // was discovered, apply it so the API returns WithPromoCode ticket types.
1557
+ // On failure, mark as removed to prevent re-fire loops.
1558
+
1559
+ (0,external_react_.useEffect)(() => {
1560
+ if (userRemovedAutoApply || isApplied) return;
1561
+ if (!ticketDataLoaded || hasTickets) return;
1562
+ if (!discoveredPromoCode?.auto_apply) return;
1563
+ if (discoveredPromoCodes.length !== 1) return;
1564
+ tryAutoApply(null).then(success => {
1565
+ if (!success) setUserRemovedAutoApply(true);
1566
+ });
1567
+ }, [userRemovedAutoApply, ticketDataLoaded, hasTickets, discoveredPromoCode, discoveredPromoCodes, isApplied, tryAutoApply]);
1568
+ const onApply = (0,external_react_.useCallback)(async (code, ticket, quantity) => {
1569
+ setManualError(null);
1570
+ setApplyingCode(true);
1571
+
1572
+ try {
1573
+ await applyPromoCode(code);
1574
+ } catch (e) {
1575
+ handleValidationError(e);
1576
+ setApplyingCode(false);
1577
+ return;
1578
+ }
1579
+
1580
+ if (ticket) {
1581
+ await onRevalidate(ticket, quantity);
1582
+ }
1583
+
1584
+ setApplyingCode(false);
1585
+ }, [applyPromoCode, onRevalidate, handleValidationError]);
1586
+ const onRemove = (0,external_react_.useCallback)(() => {
1587
+ if (isAutoApplied || isDiscoveredCode) setUserRemovedAutoApply(true);
1588
+ setIsAutoApplied(false);
1589
+ setManualError(null);
1590
+ setSuggestionDismissed(false);
1591
+ if (discoveredPromoCode) setSuggestionActive(true);
1592
+ setFormPromoCode('');
1593
+ removePromoCode();
1594
+ }, [isAutoApplied, isDiscoveredCode, discoveredPromoCode, removePromoCode, setFormPromoCode]);
1595
+ const onInputChange = (0,external_react_.useCallback)(value => {
1596
+ setManualError(null);
1597
+ setSuggestionDismissed(value !== discoveredPromoCode?.code);
1598
+ setFormPromoCode(value);
1599
+ }, [discoveredPromoCode, setFormPromoCode]);
1600
+ return {
1601
+ state: {
1602
+ // Status (what's happening with the applied/suggested code)
1603
+ status,
1604
+ isReady,
1605
+ validationError,
1606
+ // Applied code origin
1607
+ isDiscoveredCode,
1608
+ isAutoApplied,
1609
+ // Discovery / suggestion
1610
+ suggestedCode,
1611
+ // Quantity caps from the active discovered code
1612
+ maxQuantityFromPromo,
1613
+ perAccountLimit
1614
+ },
1615
+ actions: {
1616
+ // Ordered by lifecycle: input → apply → ticket change → revalidate → remove
1617
+ onInputChange,
1618
+ onApply,
1619
+ onTicketSelected,
1620
+ onRevalidate,
1621
+ onRemove
1622
+ }
1623
+ };
1624
+ };
1625
+
1626
+ /* harmony default export */ const hooks_usePromoCode = (usePromoCode);
1359
1627
  ;// CONCATENATED MODULE: external "openstack-uicore-foundation/lib/components/ajaxloader"
1360
1628
  const ajaxloader_namespaceObject = require("openstack-uicore-foundation/lib/components/ajaxloader");
1361
1629
  var ajaxloader_default = /*#__PURE__*/__webpack_require__.n(ajaxloader_namespaceObject);
@@ -1377,8 +1645,6 @@ const methods_namespaceObject = require("openstack-uicore-foundation/lib/utils/m
1377
1645
  ;// CONCATENATED MODULE: ./src/components/lawpay-form/index.module.scss
1378
1646
  // extracted by mini-css-extract-plugin
1379
1647
  /* harmony default export */ const lawpay_form_index_module = ({"form":"form___zXb7s","fieldWrapper":"fieldWrapper___G4Wqw","inputWrapper":"inputWrapper___Yz5zB","fieldRow":"fieldRow___NfZdJ","addressField":"addressField___vmAQh","lawpayWrapper":"lawpayWrapper___hpUBf","dateWrapper":"dateWrapper___XDfqs","dropdown":"dropdown___l3_bk","fieldError":"fieldError___Igq3U"});
1380
- // EXTERNAL MODULE: ./src/utils/constants.js
1381
- var constants = __webpack_require__(243);
1382
1648
  ;// CONCATENATED MODULE: ./src/components/lawpay-form/index.js
1383
1649
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
1384
1650
 
@@ -2123,7 +2389,7 @@ var company_input_v2_default = /*#__PURE__*/__webpack_require__.n(company_input_
2123
2389
  var helpers = __webpack_require__(499);
2124
2390
  ;// CONCATENATED MODULE: ./src/components/personal-information/index.module.scss
2125
2391
  // extracted by mini-css-extract-plugin
2126
- /* harmony default export */ const personal_information_index_module = ({"title":"title___ECoNz","form":"form___lDFka","fieldWrapper":"fieldWrapper___Mi_nL","fieldWrapperRadio":"fieldWrapperRadio___x18VG","inputWrapper":"inputWrapper___PEQFR","readOnly":"readOnly___WRazF","fieldError":"fieldError___ksJVe","moreInfo":"moreInfo___cQYdZ","moreInfoTooltip":"moreInfoTooltip___lslgT","ticketQuantityNotice":"ticketQuantityNotice___L6gis","formErrors":"formErrors___dQQMe"});
2392
+ /* harmony default export */ const personal_information_index_module = ({"title":"title___ECoNz","form":"form___lDFka","fieldWrapper":"fieldWrapper___Mi_nL","fieldWrapperRadio":"fieldWrapperRadio___x18VG","form-check-label":"form-check-label___MgGSC","inputWrapper":"inputWrapper___PEQFR","readOnly":"readOnly___WRazF","fieldError":"fieldError___ksJVe","moreInfo":"moreInfo___cQYdZ","moreInfoTooltip":"moreInfoTooltip___lslgT","ticketQuantityNotice":"ticketQuantityNotice___L6gis","formErrors":"formErrors___dQQMe"});
2127
2393
  ;// CONCATENATED MODULE: ./src/components/personal-information/index.js
2128
2394
  function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2129
2395
 
@@ -2164,20 +2430,27 @@ const PersonalInfoComponent = ({
2164
2430
  summitId,
2165
2431
  handleCompanyError,
2166
2432
  formValues,
2167
- formErrors = {},
2433
+ formErrors = [],
2168
2434
  invitation,
2169
2435
  showCompanyInput = true,
2170
2436
  companyDDLPlaceholder,
2171
2437
  showCompanyInputDefaultOptions,
2172
- companyDDLOptions2Show
2438
+ companyDDLOptions2Show,
2439
+ promoCodeAllowsReassign = true
2173
2440
  }) => {
2174
2441
  const initialFirstName = userProfile.given_name || (invitation ? invitation.first_name : '');
2175
2442
  const initialLastName = userProfile.family_name || (invitation ? invitation.last_name : '');
2176
2443
  const [ticketOwnerOption, setTicketOwnerOption] = (0,external_react_.useState)('');
2177
- const [ticketOwnerError, setTicketOwnerError] = (0,external_react_.useState)(false); // if there's only one ticket on the order and there is no invitation available, display the radio button to assign the ticket
2444
+ const [ticketOwnerError, setTicketOwnerError] = (0,external_react_.useState)(false); // if there's only one ticket on the order and there is no invitation available, handle ticket assignment
2445
+
2446
+ const isSingleTicketOrder = formValues.ticketQuantity === 1 && !invitation && !(0,utils/* isPrePaidTicketType */.B6)(formValues.ticketType); // check if reassignment is allowed by both promo code AND ticket type
2178
2447
 
2179
- const shouldDisplayTicketAssignment = () => formValues.ticketQuantity === 1 && !invitation && !(0,utils/* isPrePaidTicketType */.B6)(formValues.ticketType);
2448
+ const ticketTypeAllowsReassign = formValues.ticketType?.allows_to_reassign !== false;
2449
+ const canReassign = promoCodeAllowsReassign && ticketTypeAllowsReassign; // show radio options only if reassignment is allowed by both sources
2180
2450
 
2451
+ const shouldDisplayTicketAssignment = isSingleTicketOrder && canReassign; // show notice when ticket is non-transferable (either promo code or ticket type disallows)
2452
+
2453
+ const isNonTransferable = isSingleTicketOrder && !canReassign;
2181
2454
  const radioListOptions = [{
2182
2455
  label: "Myself",
2183
2456
  value: constants.TICKET_OWNER_MYSELF
@@ -2220,7 +2493,7 @@ const PersonalInfoComponent = ({
2220
2493
  email: reservation.owner_email ? reservation.owner_email : personalInfo.email,
2221
2494
  company: {
2222
2495
  id: null,
2223
- name: reservation.owner_company ? reservation.owner_company : personalInfo.company
2496
+ name: reservation.owner_company || personalInfo.company?.name || ''
2224
2497
  }
2225
2498
  });
2226
2499
  }
@@ -2236,12 +2509,21 @@ const PersonalInfoComponent = ({
2236
2509
  };
2237
2510
 
2238
2511
  const onSubmit = data => {
2239
- if (!personalInfo.company.name && showCompanyInput) {
2512
+ if (!personalInfo.company?.name && showCompanyInput) {
2240
2513
  setCompanyError(true);
2241
2514
  return;
2242
2515
  }
2243
2516
 
2244
- if (shouldDisplayTicketAssignment()) {
2517
+ if (isNonTransferable) {
2518
+ // auto-assign to purchaser when ticket is non-transferable
2519
+ data = personal_information_objectSpread(personal_information_objectSpread({}, data), {}, {
2520
+ attendee: {
2521
+ firstName: data.firstName,
2522
+ lastName: data.lastName,
2523
+ email: data.email
2524
+ }
2525
+ });
2526
+ } else if (shouldDisplayTicketAssignment) {
2245
2527
  if (!ticketOwnerOption) {
2246
2528
  setTicketOwnerError(true);
2247
2529
  return;
@@ -2338,7 +2620,7 @@ const PersonalInfoComponent = ({
2338
2620
  className: personal_information_index_module.title
2339
2621
  }, /*#__PURE__*/external_react_default().createElement("span", null, "Purchaser Information"), !isActive && /*#__PURE__*/external_react_default().createElement("div", {
2340
2622
  "data-testid": "personal-info"
2341
- }, /*#__PURE__*/external_react_default().createElement("span", null, `${personalInfo.firstName} ${personalInfo.lastName}${personalInfo.company.name ? ` - ${personalInfo.company.name}` : ''}`), /*#__PURE__*/external_react_default().createElement("br", null), /*#__PURE__*/external_react_default().createElement("span", null, personalInfo.email))), /*#__PURE__*/external_react_default().createElement(external_react_spring_namespaceObject.animated.div, {
2623
+ }, /*#__PURE__*/external_react_default().createElement("span", null, `${personalInfo.firstName} ${personalInfo.lastName}${personalInfo.company?.name ? ` - ${personalInfo.company.name}` : ''}`), /*#__PURE__*/external_react_default().createElement("br", null), /*#__PURE__*/external_react_default().createElement("span", null, personalInfo.email))), /*#__PURE__*/external_react_default().createElement(external_react_spring_namespaceObject.animated.div, {
2342
2624
  style: personal_information_objectSpread({
2343
2625
  overflow: `${isActive ? '' : 'hidden'}`
2344
2626
  }, toggleAnimation)
@@ -2433,7 +2715,7 @@ const PersonalInfoComponent = ({
2433
2715
  }), companyError && /*#__PURE__*/external_react_default().createElement("div", {
2434
2716
  className: personal_information_index_module.fieldError,
2435
2717
  "data-testid": "company-error"
2436
- }, "This field is required."))), shouldDisplayTicketAssignment() && /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, /*#__PURE__*/external_react_default().createElement("div", {
2718
+ }, "This field is required."))), shouldDisplayTicketAssignment && /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, /*#__PURE__*/external_react_default().createElement("div", {
2437
2719
  className: personal_information_index_module.fieldWrapperRadio
2438
2720
  }, /*#__PURE__*/external_react_default().createElement("label", null, "Ticket is for:"), /*#__PURE__*/external_react_default().createElement(components_namespaceObject.RadioList, {
2439
2721
  id: `ticket-self-radio`,
@@ -2501,12 +2783,9 @@ const PersonalInfoComponent = ({
2501
2783
  ;// CONCATENATED MODULE: external "openstack-uicore-foundation/lib/components/raw-html"
2502
2784
  const raw_html_namespaceObject = require("openstack-uicore-foundation/lib/components/raw-html");
2503
2785
  var raw_html_default = /*#__PURE__*/__webpack_require__.n(raw_html_namespaceObject);
2504
- ;// CONCATENATED MODULE: external "i18n-react"
2505
- const external_i18n_react_namespaceObject = require("i18n-react");
2506
- var external_i18n_react_default = /*#__PURE__*/__webpack_require__.n(external_i18n_react_namespaceObject);
2507
2786
  ;// CONCATENATED MODULE: ./src/components/ticket-type/index.module.scss
2508
2787
  // extracted by mini-css-extract-plugin
2509
- /* harmony default export */ const ticket_type_index_module = ({"title":"title___DNZyl","summary":"summary___quWdZ","promoCode":"promoCode___bqTCw","crossOut":"crossOut___QZ7dy","discount":"discount___sEK_Q","promocodeError":"promocodeError___oxk4p","taxes":"taxes___fe8oJ","promo":"promo___F8lPO","form":"form___aoo7w","dropdown":"dropdown____HWg0","quantity":"quantity___SIEQZ","soldOut":"soldOut___Hatfr","moreInfo":"moreInfo___LmwOe","moreInfoTooltip":"moreInfoTooltip___nOBf1","inPersonDisclaimer":"inPersonDisclaimer___PXGTz"});
2788
+ /* harmony default export */ const ticket_type_index_module = ({"title":"title___DNZyl","summary":"summary___quWdZ","promoCode":"promoCode___bqTCw","crossOut":"crossOut___QZ7dy","discount":"discount___sEK_Q","taxes":"taxes___fe8oJ","promo":"promo___F8lPO","form":"form___aoo7w","dropdown":"dropdown____HWg0","quantity":"quantity___SIEQZ","soldOut":"soldOut___Hatfr","moreInfo":"moreInfo___LmwOe","moreInfoTooltip":"moreInfoTooltip___nOBf1","inPersonDisclaimer":"inPersonDisclaimer___PXGTz"});
2510
2789
  ;// CONCATENATED MODULE: external "lodash/isEqual"
2511
2790
  const isEqual_namespaceObject = require("lodash/isEqual");
2512
2791
  var isEqual_default = /*#__PURE__*/__webpack_require__.n(isEqual_namespaceObject);
@@ -2551,7 +2830,7 @@ const TicketDropdownComponent = ({
2551
2830
  (0,external_react_.useEffect)(() => {
2552
2831
  const prevTicketTypes = prevTicketTypesRef.current;
2553
2832
 
2554
- if (!isEqual_default()(ticketTypes, []) && !isEqual_default()(prevTicketTypes, ticketTypes)) {
2833
+ if (!isEqual_default()(prevTicketTypes, ticketTypes)) {
2555
2834
  setCurrentTicketTypes(ticketTypes);
2556
2835
  }
2557
2836
 
@@ -2594,12 +2873,9 @@ const TicketDropdownComponent = ({
2594
2873
  ;// CONCATENATED MODULE: external "react-tooltip"
2595
2874
  const external_react_tooltip_namespaceObject = require("react-tooltip");
2596
2875
  var external_react_tooltip_default = /*#__PURE__*/__webpack_require__.n(external_react_tooltip_namespaceObject);
2597
- // EXTERNAL MODULE: ./src/assets/icon-check-circle.svg
2598
- var icon_check_circle = __webpack_require__(60);
2599
- var icon_check_circle_default = /*#__PURE__*/__webpack_require__.n(icon_check_circle);
2600
2876
  ;// CONCATENATED MODULE: ./src/components/promocode-input/index.module.scss
2601
2877
  // extracted by mini-css-extract-plugin
2602
- /* harmony default export */ const promocode_input_index_module = ({"promoCodeWrapper":"promoCodeWrapper___aw3Zx","promoCodeInput":"promoCodeInput___rDiET","promoCodeActive":"promoCodeActive___j7xnn","codeButtonWrapper":"codeButtonWrapper___jVZh5","noCode":"noCode___YUmVy","appliedCodeIcon":"appliedCodeIcon___pa3B4","moreInfo":"moreInfo___Ru3Rv","moreInfoTooltip":"moreInfoTooltip___eaYWm"});
2878
+ /* harmony default export */ const promocode_input_index_module = ({"promoCodeWrapper":"promoCodeWrapper___aw3Zx","promoCodeInput":"promoCodeInput___rDiET","promoCodeActive":"promoCodeActive___j7xnn","codeButtonWrapper":"codeButtonWrapper___jVZh5","noCode":"noCode___YUmVy","statusIcon":"statusIcon___l1uV0","valid":"valid___pDUq_","invalid":"invalid___UO9dX","spinner":"spinner___SKEJg","spin":"spin___wP5uK","moreInfo":"moreInfo___Ru3Rv","moreInfoTooltip":"moreInfoTooltip___eaYWm"});
2603
2879
  ;// CONCATENATED MODULE: ./src/components/promocode-input/index.js
2604
2880
  /**
2605
2881
  * Copyright 2020 OpenStack Foundation
@@ -2621,56 +2897,101 @@ var icon_check_circle_default = /*#__PURE__*/__webpack_require__.n(icon_check_ci
2621
2897
 
2622
2898
 
2623
2899
  const PromoCodeInput = ({
2624
- applyPromoCode,
2900
+ promoStatus,
2625
2901
  promoCode,
2626
- removePromoCode,
2627
- showMultipleTicketTexts,
2628
- onPromoCodeChange
2902
+ suggestedCode,
2903
+ isAutoApplied,
2904
+ onApply,
2905
+ onRemove,
2906
+ onInputChange,
2907
+ showMultipleTicketTexts
2629
2908
  }) => {
2630
- const [statePromoCode, setStatePromoCode] = (0,external_react_.useState)(promoCode);
2909
+ const [userTypedValue, setUserTypedValue] = (0,external_react_.useState)('');
2910
+ (0,external_react_.useEffect)(() => {
2911
+ if (!promoCode) setUserTypedValue('');
2912
+ }, [promoCode]); // Lock the input + show Remove (instead of Apply) whenever a code is in flight
2913
+ // or has settled (valid or invalid). The user must explicitly Remove to edit again.
2914
+
2915
+ const isLocked = promoStatus === constants.PROMO_STATUS.APPLYING || promoStatus === constants.PROMO_STATUS.VALIDATING || promoStatus === constants.PROMO_STATUS.VALID || promoStatus === constants.PROMO_STATUS.INVALID;
2916
+ const inputValue = (0,external_react_.useMemo)(() => {
2917
+ if (promoCode) return promoCode;
2918
+ if (promoStatus === constants.PROMO_STATUS.SUGGESTED) return suggestedCode || '';
2919
+ return userTypedValue;
2920
+ }, [promoCode, promoStatus, suggestedCode, userTypedValue]);
2921
+ const label = (0,external_react_.useMemo)(() => {
2922
+ switch (promoStatus) {
2923
+ case constants.PROMO_STATUS.VALID:
2924
+ if (isAutoApplied) return external_i18n_react_default().translate('promo_code.auto_applied_label');
2925
+ return external_i18n_react_default().translate('promo_code.applied_label');
2926
+
2927
+ case constants.PROMO_STATUS.APPLYING:
2928
+ case constants.PROMO_STATUS.VALIDATING:
2929
+ if (isAutoApplied) return external_i18n_react_default().translate('promo_code.auto_applied_label');
2930
+ return external_i18n_react_default().translate('promo_code.applying_label');
2931
+
2932
+ case constants.PROMO_STATUS.INVALID:
2933
+ return undefined;
2934
+
2935
+ case constants.PROMO_STATUS.SUGGESTED:
2936
+ return external_i18n_react_default().translate('promo_code.suggestion_label');
2937
+
2938
+ default:
2939
+ return undefined;
2940
+ }
2941
+ }, [promoStatus, isAutoApplied]);
2942
+ const canApply = !isLocked && !!inputValue;
2631
2943
 
2632
- const handlePromoCodeChange = value => {
2633
- onPromoCodeChange(value);
2634
- setStatePromoCode(value);
2944
+ const handleInputChange = value => {
2945
+ setUserTypedValue(value);
2946
+ onInputChange(value);
2635
2947
  };
2636
2948
 
2637
- (0,external_react_.useEffect)(() => {
2638
- if ((0,utils/* isEmptyString */.ke)(promoCode)) handlePromoCodeChange(promoCode);
2639
- }, [promoCode]);
2640
2949
  return /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, /*#__PURE__*/external_react_default().createElement("div", {
2641
2950
  className: promocode_input_index_module.promoCodeWrapper
2642
- }, /*#__PURE__*/external_react_default().createElement("span", null, "Do you have a promo code?"), /*#__PURE__*/external_react_default().createElement("div", {
2951
+ }, /*#__PURE__*/external_react_default().createElement("span", {
2952
+ style: {
2953
+ display: 'flex',
2954
+ justifyContent: 'space-between',
2955
+ alignItems: 'center'
2956
+ }
2957
+ }, /*#__PURE__*/external_react_default().createElement("span", null, label || external_i18n_react_default().translate('promo_code.default_label')), showMultipleTicketTexts && /*#__PURE__*/external_react_default().createElement("a", {
2958
+ "data-tip": true,
2959
+ "data-for": "promo-code-info",
2960
+ className: promocode_input_index_module.moreInfo,
2961
+ style: {
2962
+ margin: 0
2963
+ }
2964
+ }, /*#__PURE__*/external_react_default().createElement("i", {
2965
+ className: "glyphicon glyphicon-info-sign",
2966
+ "aria-hidden": "true"
2967
+ }), ` `, "Have multiple promo codes?")), /*#__PURE__*/external_react_default().createElement("div", {
2643
2968
  className: promocode_input_index_module.promoCodeInput
2644
2969
  }, /*#__PURE__*/external_react_default().createElement("input", {
2645
- className: `${promoCode ? promocode_input_index_module.promoCodeActive : ''}`,
2970
+ className: `${isLocked ? promocode_input_index_module.promoCodeActive : ''}`,
2646
2971
  type: "text",
2647
- value: statePromoCode,
2648
- onChange: ev => handlePromoCodeChange(ev.target.value),
2972
+ value: inputValue,
2973
+ onChange: ev => handleInputChange(ev.target.value),
2649
2974
  placeholder: "Enter your promo code",
2650
2975
  onKeyDown: e => {
2651
- if (e.key === "Enter") applyPromoCode(statePromoCode);
2976
+ if (e.key === "Enter" && canApply) onApply(inputValue);
2652
2977
  },
2653
- readOnly: !(0,utils/* isEmptyString */.ke)(promoCode)
2654
- }), promoCode && /*#__PURE__*/external_react_default().createElement("img", {
2655
- src: (icon_check_circle_default()),
2656
- className: promocode_input_index_module.appliedCodeIcon
2657
- }), /*#__PURE__*/external_react_default().createElement("div", {
2658
- className: `${promocode_input_index_module.codeButtonWrapper} ${statePromoCode ? '' : promocode_input_index_module.noCode}`
2659
- }, promoCode !== '' ? /*#__PURE__*/external_react_default().createElement("button", {
2660
- onClick: () => removePromoCode()
2978
+ readOnly: isLocked
2979
+ }), (promoStatus === constants.PROMO_STATUS.VALIDATING || promoStatus === constants.PROMO_STATUS.APPLYING) && /*#__PURE__*/external_react_default().createElement("span", {
2980
+ className: `${promocode_input_index_module.statusIcon} ${promocode_input_index_module.spinner}`
2981
+ }), promoStatus === constants.PROMO_STATUS.VALID && /*#__PURE__*/external_react_default().createElement("span", {
2982
+ className: `${promocode_input_index_module.statusIcon} ${promocode_input_index_module.valid}`
2983
+ }, "\u2713"), promoStatus === constants.PROMO_STATUS.INVALID && /*#__PURE__*/external_react_default().createElement("span", {
2984
+ className: `${promocode_input_index_module.statusIcon} ${promocode_input_index_module.invalid}`
2985
+ }, "\u2715"), /*#__PURE__*/external_react_default().createElement("div", {
2986
+ className: `${promocode_input_index_module.codeButtonWrapper} ${inputValue ? '' : promocode_input_index_module.noCode}`
2987
+ }, isLocked ? /*#__PURE__*/external_react_default().createElement("button", {
2988
+ onClick: onRemove
2661
2989
  }, "Remove") : /*#__PURE__*/external_react_default().createElement("button", {
2662
- disabled: !statePromoCode,
2663
- onClick: () => applyPromoCode(statePromoCode)
2664
- }, "Apply"))), showMultipleTicketTexts && /*#__PURE__*/external_react_default().createElement("div", {
2665
- className: promocode_input_index_module.moreInfo
2666
- }, /*#__PURE__*/external_react_default().createElement("a", {
2667
- "data-tip": true,
2668
- "data-for": "promo-code-info"
2669
- }, /*#__PURE__*/external_react_default().createElement("i", {
2670
- className: "glyphicon glyphicon-info-sign",
2671
- "aria-hidden": "true"
2672
- }), ` `, "Have multiple promo codes?"))), /*#__PURE__*/external_react_default().createElement((external_react_tooltip_default()), {
2990
+ disabled: !canApply,
2991
+ onClick: () => onApply(inputValue)
2992
+ }, "Apply")))), /*#__PURE__*/external_react_default().createElement((external_react_tooltip_default()), {
2673
2993
  id: "promo-code-info",
2994
+ place: "bottom",
2674
2995
  overridePosition: utils/* avoidTooltipOverflow */.kb
2675
2996
  }, /*#__PURE__*/external_react_default().createElement("div", {
2676
2997
  className: promocode_input_index_module.moreInfoTooltip
@@ -2678,6 +2999,32 @@ const PromoCodeInput = ({
2678
2999
  };
2679
3000
 
2680
3001
  /* harmony default export */ const promocode_input = (PromoCodeInput);
3002
+ ;// CONCATENATED MODULE: ./src/components/ticket-notice/index.module.scss
3003
+ // extracted by mini-css-extract-plugin
3004
+ /* harmony default export */ const ticket_notice_index_module = ({"notice":"notice____Pa2z","error":"error___WzZms","info":"info___WFfzs","icon":"icon___YWZms"});
3005
+ ;// CONCATENATED MODULE: ./src/components/ticket-notice/index.js
3006
+
3007
+ // `message` is either a single string or an array of strings. Arrays render
3008
+ // stacked consecutively inside the same notice box. Returns null when the
3009
+ // message is empty/unset so callers can pass conditional arrays without an
3010
+ // outer guard.
3011
+
3012
+ const TicketNotice = ({
3013
+ message,
3014
+ variant = 'info'
3015
+ }) => {
3016
+ const items = Array.isArray(message) ? message : message ? [message] : [];
3017
+ if (items.length === 0) return null;
3018
+ return /*#__PURE__*/external_react_default().createElement("div", {
3019
+ className: `${ticket_notice_index_module.notice} ${ticket_notice_index_module[variant]}`
3020
+ }, variant === 'error' && /*#__PURE__*/external_react_default().createElement("span", {
3021
+ className: ticket_notice_index_module.icon
3022
+ }, "\u26A0"), items.map((m, i) => /*#__PURE__*/external_react_default().createElement("div", {
3023
+ key: i
3024
+ }, m)));
3025
+ };
3026
+
3027
+ /* harmony default export */ const ticket_notice = (TicketNotice);
2681
3028
  ;// CONCATENATED MODULE: ./src/components/ticket-type/index.js
2682
3029
  function ticket_type_ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
2683
3030
 
@@ -2712,6 +3059,7 @@ function ticket_type_defineProperty(obj, key, value) { if (key in obj) { Object.
2712
3059
 
2713
3060
 
2714
3061
 
3062
+
2715
3063
  const TicketTypeComponent = ({
2716
3064
  allowedTicketTypes,
2717
3065
  originalTicketTypes,
@@ -2719,20 +3067,35 @@ const TicketTypeComponent = ({
2719
3067
  taxTypes,
2720
3068
  isActive,
2721
3069
  changeForm,
2722
- formErrors,
2723
3070
  reservation,
2724
3071
  inPersonDisclaimer,
2725
3072
  showMultipleTicketTexts,
2726
3073
  allowPromoCodes,
2727
- applyPromoCode,
2728
- removePromoCode,
3074
+ promo = {},
3075
+ validationError,
2729
3076
  promoCode,
2730
- trackViewItem
3077
+ promoCodeAllowsReassign = true,
3078
+ trackViewItem,
3079
+ noTicketsAvailableMessage
2731
3080
  }) => {
3081
+ const {
3082
+ state: promoState = {},
3083
+ actions: promoActions = {}
3084
+ } = promo;
2732
3085
  const [ticket, setTicket] = (0,external_react_.useState)(null);
2733
3086
  const [quantity, setQuantity] = (0,external_react_.useState)(1);
2734
3087
  const minQuantity = 1;
2735
- const maxQuantity = (0,helpers/* getTicketMaxQuantity */.UE)(ticket);
3088
+ const maxQuantity = (0,helpers/* getTicketMaxQuantity */.UE)(ticket, promoState.maxQuantityFromPromo); // Clamp quantity when max changes (e.g. per-account limit kicks in after auto-apply).
3089
+ // If the cap drops below minQuantity (e.g. cap of 0), use the cap directly rather
3090
+ // than flooring at minQuantity, otherwise quantity would end up above the cap.
3091
+
3092
+ (0,external_react_.useEffect)(() => {
3093
+ if (!ticket) return;
3094
+
3095
+ if (quantity > maxQuantity) {
3096
+ setQuantity(maxQuantity < minQuantity ? 0 : maxQuantity);
3097
+ }
3098
+ }, [maxQuantity, quantity, ticket]);
2736
3099
  const [ref, {
2737
3100
  height
2738
3101
  }] = (0,external_react_use_namespaceObject.useMeasure)();
@@ -2756,17 +3119,25 @@ const TicketTypeComponent = ({
2756
3119
  }
2757
3120
  }, []);
2758
3121
  (0,external_react_.useEffect)(() => {
3122
+ const ticketSelectionValid = !!ticket && quantity >= minQuantity && quantity <= maxQuantity;
2759
3123
  changeForm({
2760
3124
  ticketType: ticket,
2761
- ticketQuantity: quantity
3125
+ ticketQuantity: quantity,
3126
+ ticketSelectionValid
2762
3127
  });
2763
- }, [ticket, quantity]);
3128
+ }, [ticket, quantity, maxQuantity]);
2764
3129
  (0,external_react_.useEffect)(() => {
2765
- // if the promo code had changed ( set or not set)
2766
- // try to find the updated ticket from the original ticket types collection from api
2767
- // and update the current ticket that exist on component state
2768
- // bc a discount could be applied to the current selected ticket type
2769
- if (!ticket) return;
3130
+ // When promo code changes, the API returns updated ticket types with/without discount.
3131
+ // Sync the selected ticket with the refreshed data.
3132
+ if (!ticket) {
3133
+ // Auto-select if only one ticket type available after promo code applied
3134
+ if (promoCode && originalTicketTypes.length === 1) {
3135
+ handleTicketChange(originalTicketTypes[0]);
3136
+ }
3137
+
3138
+ return;
3139
+ }
3140
+
2770
3141
  const updatedCurrentTicket = originalTicketTypes.find(t => t?.id === ticket.id);
2771
3142
 
2772
3143
  if (updatedCurrentTicket) {
@@ -2774,35 +3145,62 @@ const TicketTypeComponent = ({
2774
3145
  ticketType: updatedCurrentTicket
2775
3146
  });
2776
3147
  setTicket(updatedCurrentTicket);
3148
+ } else {
3149
+ setTicket(null);
3150
+ setQuantity(minQuantity);
2777
3151
  }
2778
-
2779
- if (!promoCode) changeForm({
2780
- promoCode: ''
2781
- });
2782
3152
  }, [promoCode, originalTicketTypes]);
2783
- const isPrePaidReservation = (0,external_react_.useMemo)(() => reservation ? (0,utils/* isPrePaidOrder */.xm)(reservation) : false, [reservation]);
3153
+ const showTicketSelector = allowedTicketTypes.length > 0;
3154
+ const isPrePaidReservation = (0,external_react_.useMemo)(() => reservation ? (0,utils/* isPrePaidOrder */.xm)(reservation) : false, [reservation]); // check if reassignment is allowed by both promo code AND ticket type
3155
+
3156
+ const ticketTypeAllowsReassign = ticket?.allows_to_reassign !== false;
3157
+ const canReassign = promoCodeAllowsReassign && ticketTypeAllowsReassign; // Per-order cap is interesting only when it's tighter than what inventory
3158
+ // would otherwise allow (i.e. the binding constraint on the stepper).
3159
+
3160
+ const ticketPerOrderLimit = (0,external_react_.useMemo)(() => {
3161
+ if (!ticket) return null;
3162
+ const cap = ticket.max_quantity_per_order;
3163
+ const inventory = (ticket.quantity_2_sell ?? Number.MAX_SAFE_INTEGER) - (ticket.quantity_sold ?? 0);
3164
+ return cap != null && cap > 0 && cap < inventory ? cap : null;
3165
+ }, [ticket]); // Messages composed for the info notice (stacked in display order):
3166
+ // (1) promo per-account cap, (2) ticket-type per-order cap, (3) non-transferable.
3167
+
3168
+ const infoMessage = (0,external_react_.useMemo)(() => {
3169
+ if (!ticket) return [];
3170
+ const lines = [];
3171
+
3172
+ if (promoState.perAccountLimit != null) {
3173
+ lines.push(external_i18n_react_default().translate(promoState.perAccountLimit === 1 ? 'promo_code.per_account_limit_one' : 'promo_code.per_account_limit_other', {
3174
+ limit: promoState.perAccountLimit
3175
+ }));
3176
+ }
3177
+
3178
+ if (ticketPerOrderLimit != null) {
3179
+ lines.push(external_i18n_react_default().translate(ticketPerOrderLimit === 1 ? 'ticket_type.max_per_order_one' : 'ticket_type.max_per_order_other', {
3180
+ limit: ticketPerOrderLimit
3181
+ }));
3182
+ }
3183
+
3184
+ if (!canReassign) {
3185
+ lines.push(external_i18n_react_default().translate('promo_code.non_transferable'));
3186
+ }
2784
3187
 
2785
- const handleTicketChange = t => {
3188
+ return lines;
3189
+ }, [ticket, promoState.perAccountLimit, ticketPerOrderLimit, canReassign]);
3190
+
3191
+ const handleTicketChange = async t => {
2786
3192
  setTicket(t);
2787
3193
  setQuantity(minQuantity);
2788
3194
  trackViewItem(t);
2789
- };
2790
-
2791
- const handlePromoCodeChange = code => {
2792
- changeForm({
2793
- promoCode: code
2794
- });
3195
+ await promoActions.onTicketSelected(t);
2795
3196
  };
2796
3197
 
2797
3198
  const incrementQuantity = () => setQuantity(quantity + 1);
2798
3199
 
2799
3200
  const decrementQuantity = () => setQuantity(quantity - 1);
2800
3201
 
2801
- const promoCodeError = Object.keys(formErrors).length > 0 ? formErrors : null;
2802
-
2803
- const handleRemovePromoCode = () => {
2804
- setTicket(null);
2805
- removePromoCode();
3202
+ const handleApplyPromoCode = async code => {
3203
+ await promoActions.onApply(code, ticket, quantity);
2806
3204
  };
2807
3205
 
2808
3206
  return /*#__PURE__*/external_react_default().createElement("div", {
@@ -2811,9 +3209,26 @@ const TicketTypeComponent = ({
2811
3209
  className: ticket_type_index_module.innerWrapper
2812
3210
  }, /*#__PURE__*/external_react_default().createElement("div", {
2813
3211
  className: ticket_type_index_module.title
2814
- }, /*#__PURE__*/external_react_default().createElement("span", null, "Ticket"), /*#__PURE__*/external_react_default().createElement("div", {
3212
+ }, /*#__PURE__*/external_react_default().createElement("span", {
3213
+ style: isActive ? {
3214
+ display: 'flex',
3215
+ justifyContent: 'space-between',
3216
+ alignItems: 'center',
3217
+ width: '100%'
3218
+ } : {}
3219
+ }, /*#__PURE__*/external_react_default().createElement("span", null, "Ticket"), isActive && showMultipleTicketTexts && /*#__PURE__*/external_react_default().createElement("a", {
3220
+ className: ticket_type_index_module.moreInfo,
3221
+ "data-tip": true,
3222
+ "data-for": "ticket-quantity-info",
3223
+ style: {
3224
+ margin: 0
3225
+ }
3226
+ }, /*#__PURE__*/external_react_default().createElement("i", {
3227
+ className: "glyphicon glyphicon-info-sign",
3228
+ "aria-hidden": "true"
3229
+ }), ` `, "Need multiple ticket types?")), /*#__PURE__*/external_react_default().createElement("div", {
2815
3230
  className: ticket_type_index_module.summary
2816
- }, /*#__PURE__*/external_react_default().createElement("span", null, ticket && /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, `${ticket.name} (${quantity}): `, /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, (0,utils/* getTicketCost */.fX)(ticket, quantity)), `${(0,utils/* getTicketTaxes */.h5)(ticket, taxTypes)}`, !isActive && reservation?.discount_amount > 0 && /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, /*#__PURE__*/external_react_default().createElement("br", null), /*#__PURE__*/external_react_default().createElement("span", {
3231
+ }, /*#__PURE__*/external_react_default().createElement("span", null, !isActive && ticket && /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, `${ticket.name} (${quantity}): `, /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, (0,utils/* getTicketCost */.fX)(ticket, quantity)), `${(0,utils/* getTicketTaxes */.h5)(ticket, taxTypes)}`, !isActive && reservation?.discount_amount > 0 && /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, /*#__PURE__*/external_react_default().createElement("br", null), /*#__PURE__*/external_react_default().createElement("span", {
2817
3232
  className: ticket_type_index_module.promoCode
2818
3233
  }, "Promo code\xA0", /*#__PURE__*/external_react_default().createElement("abbr", {
2819
3234
  title: reservation.promo_code
@@ -2835,13 +3250,13 @@ const TicketTypeComponent = ({
2835
3250
  })} ${ticket.currency}`), /*#__PURE__*/external_react_default().createElement("br", null));
2836
3251
  })), !isActive && reservation && !isPrePaidReservation && /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, /*#__PURE__*/external_react_default().createElement("br", null), "Total: ", `${(0,helpers/* formatCurrency */.xG)(reservation.amount, {
2837
3252
  currency: ticket.currency
2838
- })} ${ticket.currency}`)), !ticket && /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, "No ticket selected")))), /*#__PURE__*/external_react_default().createElement(external_react_spring_namespaceObject.animated.div, {
3253
+ })} ${ticket.currency}`))))), /*#__PURE__*/external_react_default().createElement(external_react_spring_namespaceObject.animated.div, {
2839
3254
  style: ticket_type_objectSpread({
2840
3255
  overflow: 'hidden'
2841
3256
  }, toggleAnimation)
2842
3257
  }, /*#__PURE__*/external_react_default().createElement("div", {
2843
3258
  ref: ref
2844
- }, /*#__PURE__*/external_react_default().createElement("div", {
3259
+ }, showTicketSelector && /*#__PURE__*/external_react_default().createElement("div", {
2845
3260
  className: ticket_type_index_module.form
2846
3261
  }, /*#__PURE__*/external_react_default().createElement("div", {
2847
3262
  className: ticket_type_index_module.dropdown
@@ -2880,28 +3295,31 @@ const TicketTypeComponent = ({
2880
3295
  disabled: maxQuantity === 0 || quantity >= maxQuantity
2881
3296
  }, /*#__PURE__*/external_react_default().createElement("i", {
2882
3297
  className: "fa fa-plus"
2883
- }))))))), allowPromoCodes && /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, /*#__PURE__*/external_react_default().createElement(promocode_input, {
2884
- promoCode: promoCode,
2885
- applyPromoCode: applyPromoCode,
2886
- showMultipleTicketTexts: showMultipleTicketTexts,
2887
- removePromoCode: handleRemovePromoCode,
2888
- onPromoCodeChange: handlePromoCodeChange
2889
- }), promoCodeError && Object.values(promoCodeError).map((er, index) => /*#__PURE__*/external_react_default().createElement("div", {
2890
- key: `error-${index}`,
2891
- className: `${ticket_type_index_module.promocodeError} alert alert-danger`
2892
- }, er))), showMultipleTicketTexts && /*#__PURE__*/external_react_default().createElement("a", {
2893
- className: ticket_type_index_module.moreInfo,
2894
- "data-tip": true,
2895
- "data-for": "ticket-quantity-info"
2896
- }, /*#__PURE__*/external_react_default().createElement("i", {
2897
- className: "glyphicon glyphicon-info-sign",
2898
- "aria-hidden": "true"
2899
- }), ` `, "Need multiple ticket types?"), /*#__PURE__*/external_react_default().createElement((external_react_tooltip_default()), {
3298
+ }))))))), !showTicketSelector && /*#__PURE__*/external_react_default().createElement(ticket_notice, {
3299
+ message: noTicketsAvailableMessage || external_i18n_react_default().translate("ticket_type.no_tickets_available"),
3300
+ variant: "info"
3301
+ }), /*#__PURE__*/external_react_default().createElement((external_react_tooltip_default()), {
2900
3302
  id: "ticket-quantity-info",
3303
+ place: "bottom",
2901
3304
  overridePosition: utils/* avoidTooltipOverflow */.kb
2902
3305
  }, /*#__PURE__*/external_react_default().createElement("div", {
2903
3306
  className: ticket_type_index_module.moreInfoTooltip
2904
- }, external_i18n_react_default().translate("ticket_type.ticket_quantity_tooltip"))))), inPersonDisclaimer && ticket && (0,actions/* isInPersonTicketType */.Qc)(ticket) && /*#__PURE__*/external_react_default().createElement("div", {
3307
+ }, external_i18n_react_default().translate("ticket_type.ticket_quantity_tooltip"))), allowPromoCodes && /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, /*#__PURE__*/external_react_default().createElement(promocode_input, {
3308
+ promoStatus: promoState.status,
3309
+ promoCode: promoCode,
3310
+ suggestedCode: promoState.suggestedCode,
3311
+ isAutoApplied: promoState.isAutoApplied,
3312
+ onInputChange: promoActions.onInputChange,
3313
+ onApply: handleApplyPromoCode,
3314
+ onRemove: promoActions.onRemove,
3315
+ showMultipleTicketTexts: showMultipleTicketTexts
3316
+ })), /*#__PURE__*/external_react_default().createElement(ticket_notice, {
3317
+ message: validationError,
3318
+ variant: "error"
3319
+ }), /*#__PURE__*/external_react_default().createElement(ticket_notice, {
3320
+ message: infoMessage,
3321
+ variant: "info"
3322
+ }))), inPersonDisclaimer && ticket && (0,actions/* isInPersonTicketType */.Qc)(ticket) && /*#__PURE__*/external_react_default().createElement("div", {
2905
3323
  className: ticket_type_index_module.inPersonDisclaimer
2906
3324
  }, /*#__PURE__*/external_react_default().createElement((raw_html_default()), null, inPersonDisclaimer)))));
2907
3325
  };
@@ -2937,16 +3355,16 @@ function button_bar_defineProperty(obj, key, value) { if (key in obj) { Object.d
2937
3355
  const ButtonBarComponent = ({
2938
3356
  step,
2939
3357
  changeStep,
2940
- validatePromoCode,
2941
- onValidateError,
3358
+ onNextStep,
2942
3359
  formValues,
2943
3360
  removeReservedTicket,
2944
- inPersonDisclaimer
3361
+ inPersonDisclaimer,
3362
+ promoIsReady
2945
3363
  }) => {
2946
3364
  const {
2947
3365
  ticketType,
2948
3366
  ticketQuantity,
2949
- promoCode
3367
+ ticketSelectionValid
2950
3368
  } = formValues || {};
2951
3369
  const nextButtonText = inPersonDisclaimer && ticketType && (0,actions/* isInPersonTicketType */.Qc)(ticketType) ? 'Accept' : 'Next';
2952
3370
  return /*#__PURE__*/external_react_default().createElement("div", {
@@ -2964,12 +3382,11 @@ const ButtonBarComponent = ({
2964
3382
  className: `${button_bar_index_module.button} button`,
2965
3383
  onClick: () => removeReservedTicket()
2966
3384
  }, "< Back"), step === constants.STEP_SELECT_TICKET_TYPE && /*#__PURE__*/external_react_default().createElement("button", {
2967
- disabled: !ticketType,
3385
+ disabled: !ticketSelectionValid || !promoIsReady,
2968
3386
  className: `${button_bar_index_module.button} button`,
2969
- onClick: () => validatePromoCode(button_bar_objectSpread(button_bar_objectSpread({}, ticketType), {}, {
2970
- ticketQuantity,
2971
- promoCode
2972
- }), onValidateError)
3387
+ onClick: () => onNextStep(button_bar_objectSpread(button_bar_objectSpread({}, ticketType), {}, {
3388
+ ticketQuantity
3389
+ }))
2973
3390
  }, nextButtonText), step === constants.STEP_PERSONAL_INFO && ticketType?.cost === 0 && /*#__PURE__*/external_react_default().createElement("button", {
2974
3391
  className: `${button_bar_index_module.button} button`,
2975
3392
  type: "submit",
@@ -3235,10 +3652,10 @@ const TicketOwnedComponent = ({
3235
3652
  const NoAllowedTickets = ({
3236
3653
  noAllowedTicketsMessage
3237
3654
  }) => {
3238
- return /*#__PURE__*/external_react_default().createElement("div", {
3239
- className: no_allowed_tickets_index_module.noAllowedWrapper
3240
- }, /*#__PURE__*/external_react_default().createElement("div", {
3241
- className: `${no_allowed_tickets_index_module.alert} alert alert-warning`,
3655
+ return /*#__PURE__*/React.createElement("div", {
3656
+ className: styles.noAllowedWrapper
3657
+ }, /*#__PURE__*/React.createElement("div", {
3658
+ className: `${styles.alert} alert alert-warning`,
3242
3659
  role: "alert",
3243
3660
  dangerouslySetInnerHTML: {
3244
3661
  __html: noAllowedTicketsMessage
@@ -3246,7 +3663,7 @@ const NoAllowedTickets = ({
3246
3663
  }));
3247
3664
  };
3248
3665
 
3249
- /* harmony default export */ const no_allowed_tickets = (NoAllowedTickets);
3666
+ /* harmony default export */ const no_allowed_tickets = ((/* unused pure expression or super */ null && (NoAllowedTickets)));
3250
3667
  ;// CONCATENATED MODULE: ./src/components/ticket-taxes-error/index.module.scss
3251
3668
  // extracted by mini-css-extract-plugin
3252
3669
  /* harmony default export */ const ticket_taxes_error_index_module = ({"ticketTaxesErrorWrapper":"ticketTaxesErrorWrapper___ldztd","alert":"alert___AM17V"});
@@ -3286,7 +3703,7 @@ const TicketTaxesError = ({
3286
3703
 
3287
3704
  /* harmony default export */ const ticket_taxes_error = (TicketTaxesError);
3288
3705
  ;// CONCATENATED MODULE: ./src/components/registration-form/index.js
3289
- const registration_form_excluded = ["loadSession", "setMarketingSettings", "changeStep", "removeReservedTicket", "reserveTicket", "payTicketWithProvider", "trackEvent", "onPurchaseComplete", "getTicketTypesAndTaxes", "getLoginCode", "passwordlessLogin", "goToLogin", "loginOptions", "allowsNativeAuth", "allowsOtpAuth", "reservation", "checkout", "ticketTypes", "taxTypes", "step", "passwordlessCodeSent", "passwordlessEmail", "passwordlessCode", "passwordlessCodeLifeTime", "getPasswordlessCode", "passwordlessCodeError", "loginWithCode", "goToExtraQuestions", "goToMyOrders", "goToEvent", "profileData", "summitData", "supportEmail", "ticketOwned", "ownedTickets", "widgetLoading", "loading", "inPersonDisclaimer", "userProfile", "handleCompanyError", "providerOptions", "invitation", "loginInitialEmailInputValue", "getMyInvitation", "showMultipleTicketTexts", "noAllowedTicketsMessage", "ticketTaxesErrorMessage", "authErrorCallback", "clearWidgetState", "allowPromoCodes", "showCompanyInput", "companyDDLPlaceholder", "nowUtc", "updateClock", "completedExtraQuestions", "loadProfileData", "closeWidget", "hasVirtualAccessLevel", "hidePostalCode", "onError", "successfulPaymentReturnUrl", "idpLogoLight", "idpLogoDark", "idpLogoAlt", "showCompanyInputDefaultOptions", "companyDDLOptions2Show", "promoCode", "hasDiscount", "getTicketDiscount", "removePromoCode", "applyPromoCode", "validatePromoCode", "closeHandlerRef"];
3706
+ const registration_form_excluded = ["loadSession", "setMarketingSettings", "changeStep", "removeReservedTicket", "reserveTicket", "payTicketWithProvider", "trackEvent", "onPurchaseComplete", "getTicketTypesAndTaxes", "getLoginCode", "passwordlessLogin", "goToLogin", "loginOptions", "allowsNativeAuth", "allowsOtpAuth", "reservation", "checkout", "ticketTypes", "taxTypes", "step", "passwordlessCodeSent", "passwordlessEmail", "passwordlessCode", "passwordlessCodeLifeTime", "getPasswordlessCode", "passwordlessCodeError", "loginWithCode", "goToExtraQuestions", "goToMyOrders", "goToEvent", "profileData", "summitData", "supportEmail", "ticketOwned", "ownedTickets", "widgetLoading", "loading", "inPersonDisclaimer", "userProfile", "handleCompanyError", "providerOptions", "invitation", "loginInitialEmailInputValue", "getMyInvitation", "showMultipleTicketTexts", "noAllowedTicketsMessage", "noTicketsAvailableMessage", "ticketTaxesErrorMessage", "authErrorCallback", "clearWidgetState", "allowPromoCodes", "showCompanyInput", "companyDDLPlaceholder", "nowUtc", "updateClock", "completedExtraQuestions", "loadProfileData", "closeWidget", "hasVirtualAccessLevel", "hidePostalCode", "onError", "successfulPaymentReturnUrl", "idpLogoLight", "idpLogoDark", "idpLogoAlt", "showCompanyInputDefaultOptions", "companyDDLOptions2Show", "promoCode", "promoCodeVerified", "promoCodeValidating", "promoCodeAllowsReassign", "discoveredPromoCodes", "hasDiscount", "getTicketDiscount", "removePromoCode", "applyPromoCode", "validatePromoCode", "discoverPromoCodes", "startWidgetLoading", "stopWidgetLoading", "closeHandlerRef"];
3290
3707
 
3291
3708
  function registration_form_ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
3292
3709
 
@@ -3335,6 +3752,7 @@ function registration_form_objectWithoutPropertiesLoose(source, excluded) { if (
3335
3752
 
3336
3753
 
3337
3754
 
3755
+
3338
3756
 
3339
3757
 
3340
3758
  let language = (0,utils/* getCurrentUserLanguage */.AS)();
@@ -3398,6 +3816,7 @@ const RegistrationFormContent = _ref => {
3398
3816
  getMyInvitation,
3399
3817
  showMultipleTicketTexts,
3400
3818
  noAllowedTicketsMessage,
3819
+ noTicketsAvailableMessage,
3401
3820
  ticketTaxesErrorMessage,
3402
3821
  authErrorCallback,
3403
3822
  clearWidgetState,
@@ -3419,11 +3838,18 @@ const RegistrationFormContent = _ref => {
3419
3838
  showCompanyInputDefaultOptions,
3420
3839
  companyDDLOptions2Show,
3421
3840
  promoCode,
3841
+ promoCodeVerified,
3842
+ promoCodeValidating,
3843
+ promoCodeAllowsReassign,
3844
+ discoveredPromoCodes,
3422
3845
  hasDiscount,
3423
3846
  getTicketDiscount,
3424
3847
  removePromoCode,
3425
3848
  applyPromoCode,
3426
3849
  validatePromoCode,
3850
+ discoverPromoCodes,
3851
+ startWidgetLoading,
3852
+ stopWidgetLoading,
3427
3853
  closeHandlerRef
3428
3854
  } = _ref,
3429
3855
  rest = registration_form_objectWithoutProperties(_ref, registration_form_excluded);
@@ -3438,31 +3864,32 @@ const RegistrationFormContent = _ref => {
3438
3864
  },
3439
3865
  errors: []
3440
3866
  });
3867
+ const [hasTicketData, setHasTicketData] = (0,external_react_.useState)(false);
3441
3868
  const [ticketDataError, setTicketDataError] = (0,external_react_.useState)(false);
3442
- const [ticketDataLoaded, setTicketDataLoaded] = (0,external_react_.useState)(false);
3869
+ const [unappliedCodeWarning, setUnappliedCodeWarning] = (0,external_react_.useState)(null);
3870
+ const isAuthenticated = !!profileData;
3871
+ const summitId = summitData?.id;
3872
+ const userId = profileData?.id;
3443
3873
  const {
3444
3874
  values: formValues,
3445
3875
  errors: formErrors
3446
3876
  } = registrationForm;
3447
-
3448
- const setFormValues = values => setRegistrationForm(registration_form_objectSpread(registration_form_objectSpread({}, registrationForm), {}, {
3449
- values
3450
- }));
3451
-
3452
- const setFormErrors = errors => setRegistrationForm(registration_form_objectSpread(registration_form_objectSpread({}, registrationForm), {}, {
3877
+ const mergeFormValues = (0,external_react_.useCallback)(partial => setRegistrationForm(prev => registration_form_objectSpread(registration_form_objectSpread({}, prev), {}, {
3878
+ values: registration_form_objectSpread(registration_form_objectSpread({}, prev.values), partial)
3879
+ })), []);
3880
+ const setFormErrors = (0,external_react_.useCallback)(errors => setRegistrationForm(prev => registration_form_objectSpread(registration_form_objectSpread({}, prev), {}, {
3453
3881
  errors
3454
- }));
3455
-
3882
+ })), []);
3456
3883
  const {
3457
3884
  publicKey,
3458
3885
  provider
3459
3886
  } = (0,utils/* getCurrentProvider */.U5)(summitData);
3460
- const allowedTicketTypes = ticketDataLoaded ? ticketTypes.filter(tt => tt.sub_type === constants.TICKET_TYPE_SUBTYPE_PREPAID || tt.sales_start_date === null && tt.sales_end_date === null || nowUtc >= tt.sales_start_date && nowUtc <= tt.sales_end_date) : [];
3461
- const noAvailableTickets = (0,external_react_.useMemo)(() => profileData && ticketDataLoaded && !ticketDataError && allowedTicketTypes.length === 0 && step !== constants.STEP_COMPLETE, [profileData, ticketDataLoaded, ticketDataError, allowedTicketTypes, step]);
3462
- const alreadyOwnedTickets = (0,external_react_.useMemo)(() => profileData && ticketDataLoaded && !ticketDataError && allowedTicketTypes.length > 0 && ownedTickets.length > 0, [profileData, ticketDataLoaded, ticketDataError, allowedTicketTypes, ownedTickets]);
3887
+ const allowedTicketTypes = (0,external_react_.useMemo)(() => hasTicketData ? ticketTypes.filter(tt => tt.sub_type === constants.TICKET_TYPE_SUBTYPE_PREPAID || tt.sales_start_date === null && tt.sales_end_date === null || nowUtc >= tt.sales_start_date && nowUtc <= tt.sales_end_date) : [], [hasTicketData, ticketTypes, nowUtc]);
3888
+ const noAvailableTickets = (0,external_react_.useMemo)(() => isAuthenticated && hasTicketData && !ticketDataError && allowedTicketTypes.length === 0 && step !== constants.STEP_COMPLETE, [isAuthenticated, hasTicketData, ticketDataError, allowedTicketTypes, step]);
3889
+ const alreadyOwnedTickets = (0,external_react_.useMemo)(() => isAuthenticated && hasTicketData && !ticketDataError && allowedTicketTypes.length > 0 && ownedTickets.length > 0, [isAuthenticated, hasTicketData, ticketDataError, allowedTicketTypes, ownedTickets]);
3463
3890
  (0,external_react_.useEffect)(() => {
3464
3891
  if (profileData) loadProfileData(profileData);
3465
- }, [profileData]);
3892
+ }, [userId]);
3466
3893
  (0,external_react_.useEffect)(() => {
3467
3894
  loadSession(registration_form_objectSpread(registration_form_objectSpread({}, rest), {}, {
3468
3895
  summitData,
@@ -3474,12 +3901,12 @@ const RegistrationFormContent = _ref => {
3474
3901
  }
3475
3902
  }, []);
3476
3903
  (0,external_react_.useEffect)(() => {
3477
- if (summitData && profileData) {
3478
- const ensureInvitation = () => summitData.invite_only_registration ? getMyInvitation(summitData.id) : Promise.resolve();
3904
+ if (summitId && isAuthenticated) {
3905
+ const ensureInvitation = () => summitData.invite_only_registration ? getMyInvitation(summitId) : Promise.resolve();
3479
3906
 
3480
- ensureInvitation().catch(e => console.log(e)).finally(() => handleGetTicketTypesAndTaxes(summitData.id));
3907
+ ensureInvitation().catch(e => console.log(e)).finally(() => handleGetTicketTypesAndTaxes(summitId));
3481
3908
  }
3482
- }, [summitData?.id, profileData]);
3909
+ }, [summitId, isAuthenticated]);
3483
3910
  (0,external_react_.useEffect)(() => {
3484
3911
  if (step > constants.STEP_SELECT_TICKET_TYPE && !registrationForm.values?.ticketType && !reservation) {
3485
3912
  changeStep(constants.STEP_SELECT_TICKET_TYPE);
@@ -3487,7 +3914,43 @@ const RegistrationFormContent = _ref => {
3487
3914
  }, [registrationForm.values, step]);
3488
3915
  (0,external_react_.useEffect)(() => {
3489
3916
  setFormErrors([]);
3490
- }, [step]);
3917
+ }, [step]); // Discovery: fetch qualifying promo codes after auth
3918
+
3919
+ (0,external_react_.useEffect)(() => {
3920
+ if (isAuthenticated && summitId) {
3921
+ discoverPromoCodes(summitId);
3922
+ }
3923
+ }, [isAuthenticated, summitId]);
3924
+ const handleFormPromoCodeChange = (0,external_react_.useCallback)(code => mergeFormValues({
3925
+ promoCode: code
3926
+ }), [mergeFormValues]);
3927
+ const promo = hooks_usePromoCode({
3928
+ discoveredPromoCodes,
3929
+ promoCode,
3930
+ promoCodeVerified,
3931
+ promoCodeValidating,
3932
+ applyPromoCode,
3933
+ removePromoCode,
3934
+ validatePromoCode,
3935
+ setFormPromoCode: handleFormPromoCodeChange,
3936
+ ticketDataLoaded: hasTicketData && !ticketDataError,
3937
+ hasTickets: allowedTicketTypes.length > 0
3938
+ }); // Local destructure for readability at call sites.
3939
+
3940
+ const {
3941
+ state: promoState,
3942
+ actions: promoActions
3943
+ } = promo; // Error rendered in the promo notice slot — form-level warning layered on top
3944
+ // of the hook's own validation error (API rejection or status-derived).
3945
+
3946
+ const ticketStepError = unappliedCodeWarning ?? promoState.validationError; // Clear the unapplied-code warning once the condition that would have raised it
3947
+ // no longer holds (input cleared, code applied, or a suggestion is showing).
3948
+
3949
+ (0,external_react_.useEffect)(() => {
3950
+ if (!formValues?.promoCode || promoCode || promoState.status === constants.PROMO_STATUS.SUGGESTED) {
3951
+ setUnappliedCodeWarning(null);
3952
+ }
3953
+ }, [formValues?.promoCode, promoCode, promoState.status]);
3491
3954
  const [ref, {
3492
3955
  height
3493
3956
  }] = (0,external_react_use_namespaceObject.useMeasure)();
@@ -3529,8 +3992,9 @@ const RegistrationFormContent = _ref => {
3529
3992
 
3530
3993
  const handleGetTicketTypesAndTaxes = summitId => {
3531
3994
  setTicketDataError(false);
3532
- setTicketDataLoaded(false);
3533
- getTicketTypesAndTaxes(summitId).catch(error => {
3995
+ getTicketTypesAndTaxes(summitId).then(() => {
3996
+ setHasTicketData(true);
3997
+ }).catch(error => {
3534
3998
  let {
3535
3999
  message
3536
4000
  } = error;
@@ -3541,17 +4005,31 @@ const RegistrationFormContent = _ref => {
3541
4005
  }
3542
4006
 
3543
4007
  setTicketDataError(true);
3544
- }).finally(() => {
3545
- setTicketDataLoaded(true);
3546
4008
  });
3547
4009
  };
3548
4010
 
3549
- const handleValidatePromocode = (data, onError) => {
3550
- validatePromoCode(data, onError).then(() => {
3551
- trackAddToCart(data);
3552
- }).catch(e => {
3553
- (0,utils/* handleSentryException */.Gj)(e);
3554
- });
4011
+ const handleAdvanceFromTicketStep = async data => {
4012
+ if (formValues?.promoCode && !promoCode && promoState.status !== constants.PROMO_STATUS.SUGGESTED) {
4013
+ setUnappliedCodeWarning(external_i18n_react_default().translate('promo_code.unapplied_code_warning'));
4014
+ return;
4015
+ } // Re-validate manual codes with final quantity before advancing
4016
+
4017
+
4018
+ if (promoCode && !promoState.isDiscoveredCode) {
4019
+ startWidgetLoading();
4020
+ let valid = false;
4021
+
4022
+ try {
4023
+ valid = await promoActions.onRevalidate(formValues.ticketType, data.ticketQuantity);
4024
+ } finally {
4025
+ stopWidgetLoading();
4026
+ }
4027
+
4028
+ if (!valid) return;
4029
+ }
4030
+
4031
+ trackAddToCart(data);
4032
+ changeStep(constants.STEP_PERSONAL_INFO);
3555
4033
  };
3556
4034
 
3557
4035
  const trackViewItem = data => {
@@ -3574,12 +4052,11 @@ const RegistrationFormContent = _ref => {
3574
4052
  trackEvent(constants.PURCHASE_COMPLETE, {
3575
4053
  order
3576
4054
  });
3577
- }; // If user is logged in but ticket data hasn't loaded yet (and no error occurred),
3578
- // don't render to avoid flash. Uses local state instead of Redux to prevent
3579
- // race conditions with redux-persist rehydration.
4055
+ }; // Authenticated first-load: wait until ticket data is in before rendering
4056
+ // to avoid a flash of empty/wrong state.
3580
4057
 
3581
4058
 
3582
- if (profileData && !ticketDataLoaded && !ticketDataError) return null;
4059
+ if (isAuthenticated && !hasTicketData && !ticketDataError) return null;
3583
4060
  return /*#__PURE__*/external_react_default().createElement("div", {
3584
4061
  className: "summit-registration-lite"
3585
4062
  }, /*#__PURE__*/external_react_default().createElement((ajaxloader_default()), {
@@ -3593,8 +4070,6 @@ const RegistrationFormContent = _ref => {
3593
4070
  }), profileData && ticketDataError && /*#__PURE__*/external_react_default().createElement(ticket_taxes_error, {
3594
4071
  ticketTaxesErrorMessage: ticketTaxesErrorMessage,
3595
4072
  retryTicketTaxes: () => handleGetTicketTypesAndTaxes(summitData?.id)
3596
- }), noAvailableTickets && /*#__PURE__*/external_react_default().createElement(no_allowed_tickets, {
3597
- noAllowedTicketsMessage: noAllowedTicketsMessage
3598
4073
  }), !ticketDataError && /*#__PURE__*/external_react_default().createElement((external_react_default()).Fragment, null, !profileData && !passwordlessCodeSent && /*#__PURE__*/external_react_default().createElement(login["default"], {
3599
4074
  summitData: summitData,
3600
4075
  loginOptions: loginOptions,
@@ -3627,19 +4102,14 @@ const RegistrationFormContent = _ref => {
3627
4102
  reservation: reservation,
3628
4103
  isActive: step === constants.STEP_SELECT_TICKET_TYPE,
3629
4104
  allowPromoCodes: allowPromoCodes,
3630
- applyPromoCode: applyPromoCode,
3631
- removePromoCode: () => {
3632
- setFormErrors({});
3633
- setFormValues(registration_form_objectSpread(registration_form_objectSpread({}, formValues), {}, {
3634
- promoCode: ""
3635
- }));
3636
- removePromoCode();
3637
- },
4105
+ promo: promo,
4106
+ validationError: ticketStepError,
3638
4107
  promoCode: promoCode,
3639
- formErrors: formErrors,
3640
- changeForm: ticketForm => setFormValues(registration_form_objectSpread(registration_form_objectSpread({}, formValues), ticketForm)),
4108
+ promoCodeAllowsReassign: promoCodeAllowsReassign,
4109
+ changeForm: mergeFormValues,
3641
4110
  trackViewItem: trackViewItem,
3642
- showMultipleTicketTexts: showMultipleTicketTexts
4111
+ showMultipleTicketTexts: showMultipleTicketTexts,
4112
+ noTicketsAvailableMessage: noTicketsAvailableMessage
3643
4113
  }), /*#__PURE__*/external_react_default().createElement(personal_information, {
3644
4114
  isActive: step === constants.STEP_PERSONAL_INFO,
3645
4115
  reservation: reservation,
@@ -3647,9 +4117,9 @@ const RegistrationFormContent = _ref => {
3647
4117
  invitation: invitation,
3648
4118
  summitId: summitData.id,
3649
4119
  changeForm: personalInformation => {
3650
- setFormValues(registration_form_objectSpread(registration_form_objectSpread({}, registrationForm.values), {}, {
4120
+ mergeFormValues({
3651
4121
  personalInformation
3652
- }));
4122
+ });
3653
4123
  reserveTicket({
3654
4124
  provider,
3655
4125
  personalInformation: personalInformation,
@@ -3677,7 +4147,8 @@ const RegistrationFormContent = _ref => {
3677
4147
  showCompanyInput: showCompanyInput,
3678
4148
  companyDDLPlaceholder: companyDDLPlaceholder,
3679
4149
  showCompanyInputDefaultOptions: showCompanyInputDefaultOptions,
3680
- companyDDLOptions2Show: companyDDLOptions2Show
4150
+ companyDDLOptions2Show: companyDDLOptions2Show,
4151
+ promoCodeAllowsReassign: promoCodeAllowsReassign
3681
4152
  }), /*#__PURE__*/external_react_default().createElement(external_react_spring_namespaceObject.animated.div, {
3682
4153
  style: registration_form_objectSpread({}, toggleAnimation)
3683
4154
  }, /*#__PURE__*/external_react_default().createElement("div", {
@@ -3698,11 +4169,9 @@ const RegistrationFormContent = _ref => {
3698
4169
  step: step,
3699
4170
  inPersonDisclaimer: inPersonDisclaimer,
3700
4171
  formValues: formValues,
4172
+ promoIsReady: promoState.isReady,
3701
4173
  removeReservedTicket: removeReservedTicket,
3702
- validatePromoCode: handleValidatePromocode,
3703
- onValidateError: {
3704
- onError: (err, res) => setFormErrors(res.body.errors)
3705
- },
4174
+ onNextStep: handleAdvanceFromTicketStep,
3706
4175
  changeStep: changeStep
3707
4176
  })), profileData && step === constants.STEP_COMPLETE && /*#__PURE__*/external_react_default().createElement(purchase_complete, {
3708
4177
  checkout: checkout,
@@ -3745,7 +4214,11 @@ const mapStateToProps = ({
3745
4214
  passwordlessCodeSent: registrationLiteState.passwordless.code_sent,
3746
4215
  passwordlessCodeError: registrationLiteState.passwordless.error,
3747
4216
  nowUtc: registrationLiteState.nowUtc,
3748
- promoCode: registrationLiteState.promoCode
4217
+ promoCode: registrationLiteState.promoCode,
4218
+ promoCodeVerified: registrationLiteState.promoCodeVerified,
4219
+ promoCodeValidating: registrationLiteState.promoCodeValidating,
4220
+ promoCodeAllowsReassign: registrationLiteState.promoCodeAllowsReassign,
4221
+ discoveredPromoCodes: registrationLiteState.discoveredPromoCodes
3749
4222
  });
3750
4223
 
3751
4224
  const RegistrationForm = (0,external_react_redux_.connect)(mapStateToProps, {
@@ -3764,7 +4237,10 @@ const RegistrationForm = (0,external_react_redux_.connect)(mapStateToProps, {
3764
4237
  loadProfileData: actions/* loadProfileData */.Bi,
3765
4238
  applyPromoCode: actions/* applyPromoCode */.gs,
3766
4239
  removePromoCode: actions/* removePromoCode */.$r,
3767
- validatePromoCode: actions/* validatePromoCode */.jn
4240
+ validatePromoCode: actions/* validatePromoCode */.jn,
4241
+ discoverPromoCodes: actions/* discoverPromoCodes */.kM,
4242
+ startWidgetLoading: actions/* startWidgetLoading */.Bq,
4243
+ stopWidgetLoading: actions/* stopWidgetLoading */.Rs
3768
4244
  })(RegistrationFormContent);
3769
4245
  RegistrationForm.defaultProps = {
3770
4246
  loginInitialEmailInputValue: '',
@@ -3902,10 +4378,16 @@ var formatErrorMessage = __webpack_require__(104);
3902
4378
  var utils = __webpack_require__(452);
3903
4379
  ;// CONCATENATED MODULE: ./src/helpers/getTicketMaxQuantity.js
3904
4380
 
3905
- const getTicketMaxQuantity = ticket => {
4381
+ const getTicketMaxQuantity = (ticket, remainingQuantityPerAccount) => {
3906
4382
  if (!ticket) return 0;
3907
4383
  if ((0,utils/* isPrePaidTicketType */.B6)(ticket)) return 1;
3908
- return Math.min((ticket.quantity_2_sell || Number.MAX_SAFE_INTEGER) - ticket.quantity_sold, ticket.max_quantity_per_order || Number.MAX_SAFE_INTEGER);
4384
+ let max = Math.min((ticket.quantity_2_sell ?? Number.MAX_SAFE_INTEGER) - ticket.quantity_sold, ticket.max_quantity_per_order ?? Number.MAX_SAFE_INTEGER);
4385
+
4386
+ if (remainingQuantityPerAccount != null) {
4387
+ max = Math.min(max, remainingQuantityPerAccount);
4388
+ }
4389
+
4390
+ return max;
3909
4391
  };
3910
4392
  ;// CONCATENATED MODULE: ./src/helpers/index.js
3911
4393
 
@@ -3933,6 +4415,7 @@ const getTicketMaxQuantity = ticket => {
3933
4415
  /* harmony export */ "ORDER_STATUS_PAID": () => (/* binding */ ORDER_STATUS_PAID),
3934
4416
  /* harmony export */ "PAYMENT_PROVIDER_LAWPAY": () => (/* binding */ PAYMENT_PROVIDER_LAWPAY),
3935
4417
  /* harmony export */ "PAYMENT_PROVIDER_STRIPE": () => (/* binding */ PAYMENT_PROVIDER_STRIPE),
4418
+ /* harmony export */ "PROMO_STATUS": () => (/* binding */ PROMO_STATUS),
3936
4419
  /* harmony export */ "PURCHASE_COMPLETE": () => (/* binding */ PURCHASE_COMPLETE),
3937
4420
  /* harmony export */ "RESEND_TIME": () => (/* binding */ RESEND_TIME),
3938
4421
  /* harmony export */ "STEP_COMPLETE": () => (/* binding */ STEP_COMPLETE),
@@ -3986,7 +4469,16 @@ const PURCHASE_COMPLETE = 'purchase_complete'; // ERRORS
3986
4469
 
3987
4470
  const ERROR_TYPE_ERROR = 'error_type_error';
3988
4471
  const ERROR_TYPE_VALIDATION = 'error_type_validation';
3989
- const ERROR_TYPE_PAYMENT = 'error_type_payment'; // PROVIDERS
4472
+ const ERROR_TYPE_PAYMENT = 'error_type_payment'; // PROMO CODE STATUS
4473
+
4474
+ const PROMO_STATUS = {
4475
+ IDLE: 'idle',
4476
+ SUGGESTED: 'suggested',
4477
+ APPLYING: 'applying',
4478
+ VALIDATING: 'validating',
4479
+ VALID: 'valid',
4480
+ INVALID: 'invalid'
4481
+ }; // PROVIDERS
3990
4482
 
3991
4483
  const PAYMENT_PROVIDER_STRIPE = 'Stripe';
3992
4484
  const PAYMENT_PROVIDER_LAWPAY = 'LawPay';
@@ -4250,7 +4742,11 @@ const DEFAULT_STATE = {
4250
4742
  userProfile: null
4251
4743
  },
4252
4744
  nowUtc: localNowUtc,
4253
- promoCode: ''
4745
+ promoCode: '',
4746
+ promoCodeVerified: null,
4747
+ promoCodeValidating: false,
4748
+ promoCodeAllowsReassign: true,
4749
+ discoveredPromoCodes: []
4254
4750
  };
4255
4751
 
4256
4752
  const RegistrationLiteReducer = (state = DEFAULT_STATE, action) => {
@@ -4258,7 +4754,6 @@ const RegistrationLiteReducer = (state = DEFAULT_STATE, action) => {
4258
4754
  type,
4259
4755
  payload
4260
4756
  } = action;
4261
- console.log(action);
4262
4757
 
4263
4758
  switch (type) {
4264
4759
  case actions/* CLEAR_WIDGET_STATE */.t$:
@@ -4301,6 +4796,10 @@ const RegistrationLiteReducer = (state = DEFAULT_STATE, action) => {
4301
4796
  requestedTicketTypes: false,
4302
4797
  taxTypes: [],
4303
4798
  invitation: null,
4799
+ promoCode: '',
4800
+ promoCodeVerified: null,
4801
+ promoCodeAllowsReassign: true,
4802
+ discoveredPromoCodes: [],
4304
4803
  passwordless: _objectSpread({}, DEFAULT_STATE.passwordless),
4305
4804
  settings: _objectSpread(_objectSpread({}, DEFAULT_STATE.settings), {}, {
4306
4805
  summitId: summitData.id,
@@ -4405,7 +4904,11 @@ const RegistrationLiteReducer = (state = DEFAULT_STATE, action) => {
4405
4904
  {
4406
4905
  return _objectSpread(_objectSpread({}, state), {}, {
4407
4906
  reservation: null,
4408
- promoCode: ''
4907
+ promoCode: '',
4908
+ promoCodeVerified: null,
4909
+ promoCodeValidating: false,
4910
+ promoCodeAllowsReassign: true,
4911
+ discoveredPromoCodes: []
4409
4912
  });
4410
4913
  }
4411
4914
 
@@ -4416,7 +4919,11 @@ const RegistrationLiteReducer = (state = DEFAULT_STATE, action) => {
4416
4919
  reservation: null,
4417
4920
  userProfile: null,
4418
4921
  invitation: null,
4419
- promoCode: ''
4922
+ promoCode: '',
4923
+ promoCodeVerified: null,
4924
+ promoCodeValidating: false,
4925
+ promoCodeAllowsReassign: true,
4926
+ discoveredPromoCodes: []
4420
4927
  });
4421
4928
  }
4422
4929
 
@@ -4447,7 +4954,10 @@ const RegistrationLiteReducer = (state = DEFAULT_STATE, action) => {
4447
4954
  case actions/* CLEAR_CURRENT_PROMO_CODE */.ZD:
4448
4955
  {
4449
4956
  return _objectSpread(_objectSpread({}, state), {}, {
4450
- promoCode: ''
4957
+ promoCode: '',
4958
+ promoCodeVerified: null,
4959
+ promoCodeValidating: false,
4960
+ promoCodeAllowsReassign: true
4451
4961
  });
4452
4962
  }
4453
4963
 
@@ -4457,7 +4967,52 @@ const RegistrationLiteReducer = (state = DEFAULT_STATE, action) => {
4457
4967
  currentPromoCode
4458
4968
  } = payload;
4459
4969
  return _objectSpread(_objectSpread({}, state), {}, {
4460
- promoCode: currentPromoCode
4970
+ promoCode: currentPromoCode,
4971
+ promoCodeVerified: null,
4972
+ promoCodeValidating: false,
4973
+ promoCodeAllowsReassign: true
4974
+ });
4975
+ }
4976
+
4977
+ case actions/* VALIDATE_PROMO_CODE */.j6:
4978
+ {
4979
+ return _objectSpread(_objectSpread({}, state), {}, {
4980
+ promoCodeValidating: true
4981
+ });
4982
+ }
4983
+
4984
+ case actions/* VALIDATE_PROMO_CODE_SUCCESS */.w6:
4985
+ {
4986
+ const {
4987
+ allows_to_reassign
4988
+ } = payload.response;
4989
+ return _objectSpread(_objectSpread({}, state), {}, {
4990
+ promoCodeVerified: true,
4991
+ promoCodeValidating: false,
4992
+ promoCodeAllowsReassign: allows_to_reassign ?? true
4993
+ });
4994
+ }
4995
+
4996
+ case actions/* VALIDATE_PROMO_CODE_ERROR */.o5:
4997
+ {
4998
+ return _objectSpread(_objectSpread({}, state), {}, {
4999
+ promoCodeVerified: false,
5000
+ promoCodeValidating: false,
5001
+ promoCodeAllowsReassign: true
5002
+ });
5003
+ }
5004
+
5005
+ case actions/* VALIDATE_PROMO_CODE_RATE_LIMITED */.Tk:
5006
+ {
5007
+ return _objectSpread(_objectSpread({}, state), {}, {
5008
+ promoCodeValidating: false
5009
+ });
5010
+ }
5011
+
5012
+ case actions/* DISCOVER_PROMO_CODES_SUCCESS */.dQ:
5013
+ {
5014
+ return _objectSpread(_objectSpread({}, state), {}, {
5015
+ discoveredPromoCodes: payload.response?.data || []
4461
5016
  });
4462
5017
  }
4463
5018
 
@@ -4575,13 +5130,6 @@ module.exports = "data:image/svg+xml,%3c!-- Generator: Adobe Illustrator 25.4.1,
4575
5130
 
4576
5131
  /***/ }),
4577
5132
 
4578
- /***/ 60:
4579
- /***/ ((module) => {
4580
-
4581
- module.exports = "data:image/svg+xml,%3csvg width='20' height='20' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3e %3cpath d='M8.11475 2.60519C9.64837 2.25868 11.2529 2.41721 12.6891 3.05713C13.0978 3.23923 13.5767 3.05555 13.7588 2.64686C13.9409 2.23817 13.7572 1.75925 13.3485 1.57714C11.5932 0.795024 9.6321 0.601267 7.75767 1.02477C5.88325 1.44827 4.19593 2.46634 2.94738 3.92714C1.69883 5.38795 0.955938 7.21321 0.829498 9.13072C0.703058 11.0482 1.19985 12.9552 2.24577 14.5673C3.2917 16.1794 4.83072 17.4103 6.6333 18.0762C8.43589 18.7422 10.4055 18.8076 12.2483 18.2627C14.0911 17.7179 15.7084 16.5919 16.859 15.0528C18.0096 13.5137 18.6319 11.6438 18.633 9.72216V8.97638C18.633 8.52896 18.2703 8.16625 17.8228 8.16625C17.3754 8.16625 17.0127 8.52896 17.0127 8.97638V9.72123C17.0118 11.2935 16.5027 12.8234 15.5613 14.0827C14.6199 15.342 13.2966 16.2632 11.7889 16.709C10.2811 17.1548 8.66965 17.1012 7.19481 16.5564C5.71996 16.0115 4.46077 15.0045 3.60501 13.6855C2.74925 12.3665 2.34279 10.8062 2.44624 9.23733C2.54969 7.66846 3.15751 6.17506 4.17905 4.97986C5.2006 3.78465 6.58112 2.95169 8.11475 2.60519Z' fill='%2392CD76'/%3e %3cpath d='M18.396 3.81325C18.7122 3.49672 18.7119 2.98378 18.3954 2.66756C18.0789 2.35134 17.5659 2.3516 17.2497 2.66813L9.72129 10.2041L7.86404 8.34683C7.54767 8.03046 7.03472 8.03046 6.71835 8.34683C6.40197 8.66321 6.40197 9.17615 6.71835 9.49253L9.14873 11.9229C9.30071 12.0749 9.50685 12.1602 9.72178 12.1602C9.93671 12.1601 10.1428 12.0747 10.2947 11.9226L18.396 3.81325Z' fill='%2392CD76'/%3e %3c/svg%3e"
4582
-
4583
- /***/ }),
4584
-
4585
5133
  /***/ 267:
4586
5134
  /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
4587
5135
 
@@ -4647,7 +5195,7 @@ module.exports = require("sweetalert2");
4647
5195
  /***/ ((module) => {
4648
5196
 
4649
5197
  "use strict";
4650
- module.exports = JSON.parse('{"purchase_complete_step":{"title":" Your order is complete","initial_order_complete_1st_paragraph_label":"A ticket has been assigned to {attendee}. To complete {adv} additional ticket details, please click the \\"{button}\\" button.","initial_order_complete_button":"Finish Now","order_complete_1st_paragraph_label":"You may visit the \\"My Orders/Tickets\\" tab in the top right-hand corner of the navigation bar to\\n assign/reassign tickets or to complete any required ticket details.","order_complete_button":"View My Orders/Tickets","access_event_button":"Access Event Now","initial_order_footer_label":"If you wish to transfer your assigned ticket, close this window and visit the \\"My Orders/Tickets\\" tab in the top navigation bar. ","footer_assistance_text":"For further assistance, please email <a href=\\"mailto:{supportEmail}\\">{supportEmail}</a>","event_will_start_text":"The event will start on {date} at {time} {time_zone_label}"},"ticket_type":{"ticket_quantity_tooltip":"Only one ticket type can be selected per order. To purchase multiple ticket types, please place a separate registration order for each ticket type."},"promo_code":{"promo_code_tooltip":"Only one promo code can be used per order; the code will be applied to all tickets in this order. If you\'d like to use multiple promo codes, please place a separate registration order for each promo code."}}');
5198
+ module.exports = JSON.parse('{"purchase_complete_step":{"title":" Your order is complete","initial_order_complete_1st_paragraph_label":"A ticket has been assigned to {attendee}. To complete {adv} additional ticket details, please click the \\"{button}\\" button.","initial_order_complete_button":"Finish Now","order_complete_1st_paragraph_label":"You may visit the \\"My Orders/Tickets\\" tab in the top right-hand corner of the navigation bar to\\n assign/reassign tickets or to complete any required ticket details.","order_complete_button":"View My Orders/Tickets","access_event_button":"Access Event Now","initial_order_footer_label":"If you wish to transfer your assigned ticket, close this window and visit the \\"My Orders/Tickets\\" tab in the top navigation bar. ","footer_assistance_text":"For further assistance, please email <a href=\\"mailto:{supportEmail}\\">{supportEmail}</a>","event_will_start_text":"The event will start on {date} at {time} {time_zone_label}"},"ticket_type":{"ticket_quantity_tooltip":"Only one ticket type can be selected per order. To purchase multiple ticket types, please place a separate registration order for each ticket type.","no_tickets_available":"There are no tickets currently available for purchase. If you have a promo code, enter it below to check for eligible tickets.","max_per_order_one":"This ticket type is limited to 1 per order.","max_per_order_other":"This ticket type is limited to {limit} per order."},"promo_code":{"promo_code_tooltip":"Only one promo code can be used per order; the code will be applied to all tickets in this order. If you\'d like to use multiple promo codes, please place a separate registration order for each promo code.","default_label":"Do you have a promo code?","auto_applied_label":"Following promo code was automatically applied:","applied_label":"Applied promo code:","applying_label":"Applying promo code...","suggestion_label":"You qualify for the following promo code:","per_account_limit_one":"Promo code limits {limit} ticket per account.","per_account_limit_other":"Promo code limits {limit} tickets per account.","non_transferable":"This ticket will be automatically assigned to you and cannot be reassigned.","unapplied_code_warning":"You entered a promo code but it hasn\'t been applied. Make sure to click the \'Apply\' button or remove it before continuing.","invalid_code":"Promo code entered is not valid.","validation_error":"An error occurred while validating the promo code."}}');
4651
5199
 
4652
5200
  /***/ })
4653
5201
 
@@ -4737,8 +5285,8 @@ var external_react_default = /*#__PURE__*/__webpack_require__.n(external_react_)
4737
5285
  // EXTERNAL MODULE: external "prop-types"
4738
5286
  var external_prop_types_ = __webpack_require__(580);
4739
5287
  var external_prop_types_default = /*#__PURE__*/__webpack_require__.n(external_prop_types_);
4740
- // EXTERNAL MODULE: ./src/components/registration-form/index.js + 43 modules
4741
- var registration_form = __webpack_require__(558);
5288
+ // EXTERNAL MODULE: ./src/components/registration-form/index.js + 46 modules
5289
+ var registration_form = __webpack_require__(95);
4742
5290
  // EXTERNAL MODULE: ./src/utils/withReduxProvider.js + 9 modules
4743
5291
  var withReduxProvider = __webpack_require__(974);
4744
5292
  ;// CONCATENATED MODULE: ./src/styles/general.module.scss
@@ -4853,6 +5401,7 @@ RegistrationModal.propTypes = {
4853
5401
  initialOrderComplete1stParagraph: (external_prop_types_default()).string,
4854
5402
  initialOrderComplete2ndParagraph: (external_prop_types_default()).string,
4855
5403
  initialOrderCompleteButton: (external_prop_types_default()).string,
5404
+ noTicketsAvailableMessage: (external_prop_types_default()).string,
4856
5405
  orderCompleteTitle: (external_prop_types_default()).string,
4857
5406
  orderComplete1stParagraph: (external_prop_types_default()).string,
4858
5407
  orderComplete2ndParagraph: (external_prop_types_default()).string,