@eventlook/sdk 1.4.49-beta.5 → 1.4.49-beta.7

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 (73) hide show
  1. package/dist/cjs/form/PaymentOverviewBox.js +5 -4
  2. package/dist/cjs/form/PaymentOverviewBox.js.map +1 -1
  3. package/dist/cjs/form/PaymentOverviewDrawer.js +177 -109
  4. package/dist/cjs/form/PaymentOverviewDrawer.js.map +1 -1
  5. package/dist/cjs/form/TicketForm.js +5 -8
  6. package/dist/cjs/form/TicketForm.js.map +1 -1
  7. package/dist/cjs/form/merchandise/MerchandiseSlider.js +1 -1
  8. package/dist/cjs/form/merchandise/MerchandiseSlider.js.map +1 -1
  9. package/dist/cjs/form/product/ProductVariantsDialog.js +2 -2
  10. package/dist/cjs/form/product/ProductVariantsDialog.js.map +1 -1
  11. package/dist/cjs/form/style.js +5 -0
  12. package/dist/cjs/form/style.js.map +1 -1
  13. package/dist/cjs/form/tickets/TicketQuantityControl.js +5 -3
  14. package/dist/cjs/form/tickets/TicketQuantityControl.js.map +1 -1
  15. package/dist/cjs/hooks/useScrollToFirstError.js +19 -10
  16. package/dist/cjs/hooks/useScrollToFirstError.js.map +1 -1
  17. package/dist/cjs/locales/cs.js +4 -0
  18. package/dist/cjs/locales/cs.js.map +1 -1
  19. package/dist/cjs/locales/en.js +4 -0
  20. package/dist/cjs/locales/en.js.map +1 -1
  21. package/dist/cjs/locales/es.js +4 -0
  22. package/dist/cjs/locales/es.js.map +1 -1
  23. package/dist/cjs/locales/pl.js +4 -0
  24. package/dist/cjs/locales/pl.js.map +1 -1
  25. package/dist/cjs/locales/sk.js +4 -0
  26. package/dist/cjs/locales/sk.js.map +1 -1
  27. package/dist/cjs/locales/uk.js +4 -0
  28. package/dist/cjs/locales/uk.js.map +1 -1
  29. package/dist/esm/form/PaymentOverviewBox.js +5 -4
  30. package/dist/esm/form/PaymentOverviewBox.js.map +1 -1
  31. package/dist/esm/form/PaymentOverviewDrawer.js +177 -109
  32. package/dist/esm/form/PaymentOverviewDrawer.js.map +1 -1
  33. package/dist/esm/form/TicketForm.js +6 -9
  34. package/dist/esm/form/TicketForm.js.map +1 -1
  35. package/dist/esm/form/merchandise/MerchandiseSlider.js +1 -1
  36. package/dist/esm/form/merchandise/MerchandiseSlider.js.map +1 -1
  37. package/dist/esm/form/product/ProductVariantsDialog.js +2 -2
  38. package/dist/esm/form/product/ProductVariantsDialog.js.map +1 -1
  39. package/dist/esm/form/style.js +5 -0
  40. package/dist/esm/form/style.js.map +1 -1
  41. package/dist/esm/form/tickets/TicketQuantityControl.js +5 -3
  42. package/dist/esm/form/tickets/TicketQuantityControl.js.map +1 -1
  43. package/dist/esm/hooks/useScrollToFirstError.js +19 -10
  44. package/dist/esm/hooks/useScrollToFirstError.js.map +1 -1
  45. package/dist/esm/locales/cs.js +4 -0
  46. package/dist/esm/locales/cs.js.map +1 -1
  47. package/dist/esm/locales/en.js +4 -0
  48. package/dist/esm/locales/en.js.map +1 -1
  49. package/dist/esm/locales/es.js +4 -0
  50. package/dist/esm/locales/es.js.map +1 -1
  51. package/dist/esm/locales/pl.js +4 -0
  52. package/dist/esm/locales/pl.js.map +1 -1
  53. package/dist/esm/locales/sk.js +4 -0
  54. package/dist/esm/locales/sk.js.map +1 -1
  55. package/dist/esm/locales/uk.js +4 -0
  56. package/dist/esm/locales/uk.js.map +1 -1
  57. package/package.json +1 -3
  58. package/rollup.config.mjs +0 -1
  59. package/src/form/PaymentOverviewBox.tsx +19 -13
  60. package/src/form/PaymentOverviewDrawer.tsx +238 -128
  61. package/src/form/TicketForm.tsx +7 -8
  62. package/src/form/merchandise/MerchandiseSlider.tsx +1 -1
  63. package/src/form/product/ProductVariantsDialog.tsx +2 -0
  64. package/src/form/style.ts +6 -0
  65. package/src/form/tickets/TicketQuantityControl.tsx +6 -0
  66. package/src/hooks/useScrollToFirstError.ts +22 -17
  67. package/src/locales/cs.tsx +4 -0
  68. package/src/locales/en.tsx +4 -0
  69. package/src/locales/es.tsx +4 -0
  70. package/src/locales/pl.tsx +4 -0
  71. package/src/locales/sk.tsx +4 -0
  72. package/src/locales/uk.tsx +4 -0
  73. package/src/hooks/useConsentScrollOnDrawerOpen.ts +0 -73
@@ -10,60 +10,92 @@ import { EVENTLOOK_ORDER_FORM_ID } from '@utils/data/global.ts';
10
10
  interface Props {
11
11
  event: IEvent;
12
12
  totalPrice: number;
13
+ termsAndConditionsRef?: React.RefObject<HTMLElement | null>;
13
14
  onOpenChange?: (open: boolean) => void;
14
15
  }
15
16
 
16
17
  const swipeAreaWidth = 120;
17
18
 
18
- const PaymentOverviewDrawer: React.FC<Props> = ({ event, totalPrice, onOpenChange }) => {
19
+ const clearResetPulledPastTimeout = (timeoutRef: { current: number | null }) => {
20
+ if (timeoutRef.current !== null) {
21
+ window.clearTimeout(timeoutRef.current);
22
+ timeoutRef.current = null;
23
+ }
24
+ };
25
+
26
+ const PaymentOverviewDrawer: React.FC<Props> = ({
27
+ event,
28
+ totalPrice,
29
+ termsAndConditionsRef,
30
+ onOpenChange,
31
+ }) => {
19
32
  const { t, lang } = useGlobal();
20
- const [openDrawer, setOpenDrawer] = useState(false);
33
+ const [isDrawerOpened, setIsDrawerOpened] = useState(false);
21
34
  const [isOpeningGesture, setIsOpeningGesture] = useState(false);
22
35
  const [hasPulledPastClosedTabHeight, setHasPulledPastClosedTabHeight] = useState(false);
36
+ const [isDrawerHidden, setIsDrawerHidden] = useState(false);
23
37
  const [closedTabHeight, setClosedTabHeight] = useState(0);
24
38
  const openingTouchStartYRef = useRef<number | null>(null);
25
39
  const openDrawerRef = useRef(false);
40
+ const isOpeningGestureRef = useRef(false);
41
+ const shouldReopenOnScrollUpRef = useRef(false);
42
+ const lastScrollYRef = useRef(0);
43
+ const isScrollingDownRef = useRef(false);
44
+ const wasAtPageBottomRef = useRef(false);
26
45
  const resetPulledPastTimeoutRef = useRef<number | null>(null);
27
46
  const closedTabRef = useRef<HTMLDivElement>(null);
28
47
 
29
- const sentinelRef = useRef<HTMLDivElement>(null);
30
- const prevYRef = useRef<number>(Number.POSITIVE_INFINITY);
48
+ useEffect(() => {
49
+ openDrawerRef.current = isDrawerOpened;
50
+ }, [isDrawerOpened]);
31
51
 
32
- // Opens drawer when scrolling down
33
52
  useEffect(() => {
34
- const sentinel = sentinelRef.current;
35
- if (!sentinel) return;
53
+ isOpeningGestureRef.current = isOpeningGesture;
54
+ }, [isOpeningGesture]);
36
55
 
37
- const observer = new IntersectionObserver(
38
- (entries) => {
39
- entries.forEach((entry) => {
40
- if (totalPrice <= 0) return;
41
- const currentY = entry.boundingClientRect.y;
42
- const isScrollingDown = currentY < prevYRef.current;
56
+ // reopens the drawer when user scrolls up and the drawer was closed by scrolling down to the bottom of the page
57
+ useEffect(() => {
58
+ if (typeof window === 'undefined') {
59
+ return;
60
+ }
43
61
 
44
- if (entry.isIntersecting && isScrollingDown) {
45
- openDrawerHandler();
46
- }
62
+ const atBottomThreshold = 8;
63
+ lastScrollYRef.current = window.scrollY;
64
+ isScrollingDownRef.current = false;
65
+ wasAtPageBottomRef.current =
66
+ window.innerHeight + window.scrollY >=
67
+ document.documentElement.scrollHeight - atBottomThreshold;
47
68
 
48
- prevYRef.current = currentY;
49
- });
50
- },
51
- {
52
- threshold: 0,
53
- rootMargin: '0px 0px 0px 0px',
69
+ const handleScroll = () => {
70
+ const currentY = window.scrollY;
71
+ const isScrollingUp = currentY < lastScrollYRef.current;
72
+ const isScrollingDown = currentY > lastScrollYRef.current;
73
+ const isAtPageBottom =
74
+ window.innerHeight + currentY >= document.documentElement.scrollHeight - atBottomThreshold;
75
+
76
+ isScrollingDownRef.current = isScrollingDown;
77
+
78
+ if (
79
+ isScrollingUp &&
80
+ wasAtPageBottomRef.current &&
81
+ shouldReopenOnScrollUpRef.current &&
82
+ !openDrawerRef.current
83
+ ) {
84
+ shouldReopenOnScrollUpRef.current = false;
85
+ setIsDrawerHidden(false);
86
+ openDrawerHandler();
54
87
  }
55
- );
56
88
 
57
- observer.observe(sentinel);
89
+ wasAtPageBottomRef.current = isAtPageBottom;
90
+ lastScrollYRef.current = currentY;
91
+ };
92
+
93
+ window.addEventListener('scroll', handleScroll, { passive: true });
58
94
 
59
95
  return () => {
60
- observer.unobserve(sentinel);
96
+ window.removeEventListener('scroll', handleScroll);
61
97
  };
62
- }, [totalPrice]);
63
-
64
- useEffect(() => {
65
- openDrawerRef.current = openDrawer;
66
- }, [openDrawer]);
98
+ }, []);
67
99
 
68
100
  useEffect(() => {
69
101
  const closedTab = closedTabRef.current;
@@ -88,50 +120,129 @@ const PaymentOverviewDrawer: React.FC<Props> = ({ event, totalPrice, onOpenChang
88
120
 
89
121
  useEffect(
90
122
  () => () => {
91
- if (resetPulledPastTimeoutRef.current !== null) {
92
- window.clearTimeout(resetPulledPastTimeoutRef.current);
93
- }
123
+ clearResetPulledPastTimeout(resetPulledPastTimeoutRef);
94
124
  },
95
125
  []
96
126
  );
97
127
 
128
+ useEffect(() => {
129
+ const termsAndConditionsElement = termsAndConditionsRef?.current;
130
+ if (!termsAndConditionsElement) {
131
+ return;
132
+ }
133
+
134
+ if (!('IntersectionObserver' in window)) {
135
+ return;
136
+ }
137
+
138
+ const observer = new IntersectionObserver(
139
+ (entries) => {
140
+ const entry = entries[0];
141
+ const hasReachedTermsSection = entry?.isIntersecting ?? false;
142
+ const shouldAutoCloseDrawer = hasReachedTermsSection;
143
+ const shouldHideDrawerTab =
144
+ hasReachedTermsSection && !openDrawerRef.current && !isOpeningGestureRef.current;
145
+ setIsDrawerHidden(shouldHideDrawerTab);
146
+
147
+ if (shouldAutoCloseDrawer && openDrawerRef.current && isScrollingDownRef.current) {
148
+ closeDrawerHandler({ shouldReopenOnScrollUp: true });
149
+ setIsDrawerHidden(true);
150
+ }
151
+ },
152
+ {
153
+ root: null,
154
+ threshold: 0,
155
+ rootMargin: '0px 0px -40% 0px',
156
+ }
157
+ );
158
+
159
+ observer.observe(termsAndConditionsElement);
160
+
161
+ return () => {
162
+ observer.disconnect();
163
+ };
164
+ }, [termsAndConditionsRef]);
165
+
98
166
  const toggleDrawer = () => {
99
- setOpenDrawer((prev) => {
100
- const next = !prev;
101
- onOpenChange?.(next);
102
- return next;
103
- });
167
+ if (openDrawerRef.current) {
168
+ closeDrawerHandler();
169
+ return;
170
+ }
171
+
172
+ openDrawerHandler();
104
173
  };
105
174
 
106
175
  const openDrawerHandler = () => {
107
- if (resetPulledPastTimeoutRef.current !== null) {
108
- window.clearTimeout(resetPulledPastTimeoutRef.current);
109
- resetPulledPastTimeoutRef.current = null;
110
- }
176
+ clearResetPulledPastTimeout(resetPulledPastTimeoutRef);
111
177
  setIsOpeningGesture(false);
112
178
  setHasPulledPastClosedTabHeight(false);
113
179
  openingTouchStartYRef.current = null;
114
- setOpenDrawer(true);
180
+ shouldReopenOnScrollUpRef.current = false;
181
+ setIsDrawerOpened(true);
115
182
  onOpenChange?.(true);
116
183
  };
117
184
 
118
- const closeDrawerHandler = () => {
119
- if (resetPulledPastTimeoutRef.current !== null) {
120
- window.clearTimeout(resetPulledPastTimeoutRef.current);
121
- resetPulledPastTimeoutRef.current = null;
122
- }
185
+ const closeDrawerHandler = ({
186
+ shouldReopenOnScrollUp = false,
187
+ }: { shouldReopenOnScrollUp?: boolean } = {}) => {
188
+ clearResetPulledPastTimeout(resetPulledPastTimeoutRef);
123
189
  setIsOpeningGesture(false);
124
190
  setHasPulledPastClosedTabHeight(false);
125
191
  openingTouchStartYRef.current = null;
126
- setOpenDrawer(false);
192
+ shouldReopenOnScrollUpRef.current = shouldReopenOnScrollUp;
193
+ setIsDrawerOpened(false);
127
194
  onOpenChange?.(false);
128
195
  };
129
196
 
197
+ const scheduleResetPulledPastTimeout = () => {
198
+ clearResetPulledPastTimeout(resetPulledPastTimeoutRef);
199
+ resetPulledPastTimeoutRef.current = window.setTimeout(() => {
200
+ if (!openDrawerRef.current) {
201
+ setHasPulledPastClosedTabHeight(false);
202
+ }
203
+ }, 180);
204
+ };
205
+
206
+ const handleGestureStart = (startY: number | null) => {
207
+ openingTouchStartYRef.current = startY;
208
+ setIsOpeningGesture(true);
209
+ setHasPulledPastClosedTabHeight(false);
210
+ };
211
+
212
+ const handleGestureMove = (currentY?: number) => {
213
+ if (!isOpeningGestureRef.current) return;
214
+
215
+ const startY = openingTouchStartYRef.current;
216
+ if (startY === null || typeof currentY !== 'number') return;
217
+
218
+ const pullDistance = startY - currentY;
219
+ const currentClosedTabHeight = closedTabRef.current?.offsetHeight ?? 0;
220
+ setHasPulledPastClosedTabHeight(
221
+ currentClosedTabHeight > 0 && pullDistance >= currentClosedTabHeight
222
+ );
223
+ };
224
+
225
+ const handleGestureCancel = () => {
226
+ openingTouchStartYRef.current = null;
227
+ setIsOpeningGesture(false);
228
+ setHasPulledPastClosedTabHeight(false);
229
+ };
230
+
231
+ const handleGestureEnd = ({
232
+ shouldResetTouchStart = false,
233
+ }: { shouldResetTouchStart?: boolean } = {}) => {
234
+ if (shouldResetTouchStart) {
235
+ openingTouchStartYRef.current = null;
236
+ }
237
+
238
+ if (!isDrawerOpened) {
239
+ setIsOpeningGesture(false);
240
+ scheduleResetPulledPastTimeout();
241
+ }
242
+ };
243
+
130
244
  return (
131
245
  <Box sx={{ display: { md: 'none' } }}>
132
- {/* Sentinel element for intersection observer */}
133
- <Box ref={sentinelRef} sx={{ height: 1, visibility: 'hidden' }} />
134
-
135
246
  {/* Drawer Tab */}
136
247
  <Box
137
248
  ref={closedTabRef}
@@ -141,43 +252,65 @@ const PaymentOverviewDrawer: React.FC<Props> = ({ event, totalPrice, onOpenChang
141
252
  left: 0,
142
253
  right: 0,
143
254
  backgroundColor: (theme) => theme.palette.background.paper,
144
- borderTopLeftRadius: 16,
145
- borderTopRightRadius: 16,
146
- boxShadow: '0 -8px 10px 2px rgba(0,0,0,0.2)',
147
- zIndex: 1300,
148
- opacity: openDrawer || hasPulledPastClosedTabHeight ? 0 : 1,
149
- transform: openDrawer ? 'translateY(8px)' : 'translateY(0)',
150
- transition: 'opacity 180ms ease, transform 180ms ease',
255
+ borderTopLeftRadius: isOpeningGesture || isDrawerOpened ? 0 : 16,
256
+ borderTopRightRadius: isOpeningGesture || isDrawerOpened ? 0 : 16,
257
+ boxShadow:
258
+ isOpeningGesture || isDrawerOpened ? 'none' : '0 -8px 10px 2px rgba(0,0,0,0.2)',
259
+ zIndex: (theme) => theme.zIndex.drawer + 1,
260
+ opacity: isDrawerHidden ? 0 : 1,
261
+ transform: isDrawerHidden ? 'translateY(calc(100% + 12px))' : 'translateY(0)',
262
+ transition:
263
+ 'opacity 180ms ease, transform 180ms ease, border-radius 120ms ease, box-shadow 120ms ease',
151
264
  pointerEvents: 'none',
152
265
  }}
153
266
  >
154
- <Box
155
- sx={{
156
- display: 'flex',
157
- justifyContent: 'center',
158
- alignItems: 'center',
159
- width: '100%',
160
- py: 1,
161
- }}
162
- >
267
+ {!isDrawerOpened && (
163
268
  <Box
164
269
  sx={{
165
- width: 80,
166
- height: 6,
167
- backgroundColor: (theme) => theme.palette.grey[300],
168
- borderRadius: 3,
270
+ display: 'flex',
271
+ justifyContent: 'center',
272
+ alignItems: 'center',
273
+ width: '100%',
274
+ py: 1,
169
275
  }}
170
- />
171
- </Box>
276
+ >
277
+ <Box
278
+ sx={{
279
+ width: 80,
280
+ height: 6,
281
+ backgroundColor: (theme) => theme.palette.grey[300],
282
+ borderRadius: 3,
283
+ opacity: isOpeningGesture ? 0 : 1,
284
+ transition: 'opacity 120ms ease',
285
+ }}
286
+ />
287
+ </Box>
288
+ )}
172
289
  <Box
173
290
  role="button"
174
291
  aria-label={t('form.labels.payment_overview_open')}
175
292
  onClick={toggleDrawer}
293
+ onTouchStart={(event: React.TouchEvent<HTMLDivElement>) => {
294
+ handleGestureStart(event.touches[0]?.clientY ?? null);
295
+ }}
296
+ onMouseDown={() => {
297
+ handleGestureStart(null);
298
+ }}
299
+ onTouchMove={(event: React.TouchEvent<HTMLDivElement>) => {
300
+ handleGestureMove(event.touches[0]?.clientY);
301
+ }}
302
+ onTouchCancel={handleGestureCancel}
303
+ onTouchEnd={() => {
304
+ handleGestureEnd({ shouldResetTouchStart: true });
305
+ }}
306
+ onMouseUp={() => {
307
+ handleGestureEnd();
308
+ }}
176
309
  sx={{
177
310
  width: '100%',
178
311
  px: 2,
179
312
  pb: 2,
180
- pointerEvents: openDrawer ? 'none' : 'auto',
313
+ pointerEvents: isDrawerOpened || isDrawerHidden ? 'none' : 'auto',
181
314
  cursor: 'pointer',
182
315
  }}
183
316
  >
@@ -212,75 +345,51 @@ const PaymentOverviewDrawer: React.FC<Props> = ({ event, totalPrice, onOpenChang
212
345
  {/* Drawer Content */}
213
346
  <SwipeableDrawer
214
347
  anchor="bottom"
215
- open={openDrawer}
216
- onClose={closeDrawerHandler}
217
- onOpen={openDrawerHandler}
348
+ open={isDrawerOpened}
349
+ onClose={() => closeDrawerHandler()}
350
+ onOpen={() => openDrawerHandler()}
218
351
  swipeAreaWidth={swipeAreaWidth}
219
352
  allowSwipeInChildren
220
- disableSwipeToOpen={false}
353
+ disableSwipeToOpen={isDrawerHidden}
221
354
  keepMounted
222
355
  SwipeAreaProps={{
223
356
  onTouchStart: (event: React.TouchEvent<HTMLDivElement>) => {
224
- openingTouchStartYRef.current = event.touches[0]?.clientY ?? null;
225
- setIsOpeningGesture(true);
226
- setHasPulledPastClosedTabHeight(false);
357
+ handleGestureStart(event.touches[0]?.clientY ?? null);
227
358
  },
228
359
  onMouseDown: () => {
229
- setIsOpeningGesture(true);
230
- setHasPulledPastClosedTabHeight(false);
360
+ handleGestureStart(null);
231
361
  },
232
362
  onTouchMove: (event: React.TouchEvent<HTMLDivElement>) => {
233
- if (!isOpeningGesture) return;
234
-
235
- const startY = openingTouchStartYRef.current;
236
- const currentY = event.touches[0]?.clientY;
237
- if (startY === null || typeof currentY !== 'number') return;
238
-
239
- const pullDistance = startY - currentY;
240
- const closedTabHeight = closedTabRef.current?.offsetHeight ?? 0;
241
- setHasPulledPastClosedTabHeight(closedTabHeight > 0 && pullDistance >= closedTabHeight);
242
- },
243
- onTouchCancel: () => {
244
- openingTouchStartYRef.current = null;
245
- setIsOpeningGesture(false);
246
- setHasPulledPastClosedTabHeight(false);
363
+ handleGestureMove(event.touches[0]?.clientY);
247
364
  },
365
+ onTouchCancel: handleGestureCancel,
248
366
  onTouchEnd: () => {
249
- openingTouchStartYRef.current = null;
250
- if (!openDrawer) {
251
- setIsOpeningGesture(false);
252
- if (resetPulledPastTimeoutRef.current !== null) {
253
- window.clearTimeout(resetPulledPastTimeoutRef.current);
254
- }
255
- resetPulledPastTimeoutRef.current = window.setTimeout(() => {
256
- if (!openDrawerRef.current) {
257
- setHasPulledPastClosedTabHeight(false);
258
- }
259
- }, 180);
260
- }
367
+ handleGestureEnd({ shouldResetTouchStart: true });
261
368
  },
262
369
  onMouseUp: () => {
263
- if (!openDrawer) {
264
- setIsOpeningGesture(false);
265
- if (resetPulledPastTimeoutRef.current !== null) {
266
- window.clearTimeout(resetPulledPastTimeoutRef.current);
267
- }
268
- resetPulledPastTimeoutRef.current = window.setTimeout(() => {
269
- if (!openDrawerRef.current) {
270
- setHasPulledPastClosedTabHeight(false);
271
- }
272
- }, 180);
273
- }
370
+ handleGestureEnd();
371
+ },
372
+ }}
373
+ sx={{
374
+ display: { md: 'none' },
375
+ pointerEvents: isDrawerOpened ? 'none' : 'auto',
376
+ '& .MuiDrawer-paper': {
377
+ pointerEvents: 'auto',
378
+ },
379
+ '& .MuiSwipeArea-root': {
380
+ pointerEvents: 'auto',
274
381
  },
275
382
  }}
276
- sx={{ display: { md: 'none' } }}
277
383
  ModalProps={{
278
384
  disableScrollLock: true,
279
385
  keepMounted: false,
386
+ disableAutoFocus: true,
387
+ disableRestoreFocus: true,
388
+ disableEnforceFocus: true,
280
389
  BackdropProps: {
281
- sx: { background: 'linear-gradient(to top, rgba(0,0,0,0.6) 0%, rgba(0,0,0,0) 100%)' },
282
- onClick: (event: React.MouseEvent<HTMLDivElement>) => {
283
- event.stopPropagation();
390
+ sx: {
391
+ background: 'none',
392
+ pointerEvents: 'none',
284
393
  },
285
394
  },
286
395
  }}
@@ -291,10 +400,11 @@ const PaymentOverviewDrawer: React.FC<Props> = ({ event, totalPrice, onOpenChang
291
400
  boxShadow: '0 -8px 10px 2px rgba(0,0,0,0.2)',
292
401
  overflow: 'hidden',
293
402
  mb:
294
- !openDrawer && (isOpeningGesture || hasPulledPastClosedTabHeight)
403
+ isDrawerOpened ||
404
+ (!isDrawerOpened && (isOpeningGesture || hasPulledPastClosedTabHeight))
295
405
  ? `${closedTabHeight}px`
296
406
  : 0,
297
- transition: 'margin-bottom 120ms ease',
407
+ transition: 'none',
298
408
  },
299
409
  }}
300
410
  >
@@ -326,7 +436,7 @@ const PaymentOverviewDrawer: React.FC<Props> = ({ event, totalPrice, onOpenChang
326
436
  overflow: 'auto',
327
437
  }}
328
438
  >
329
- <PaymentOverviewBox event={event} />
439
+ <PaymentOverviewBox event={event} hideBuyButton />
330
440
  </Box>
331
441
  </SwipeableDrawer>
332
442
  </Box>
@@ -3,6 +3,7 @@ import PaymentSuccess from '@form/PaymentSuccess';
3
3
  import FormProvider, { RHFCheckbox } from '@components/hook-form';
4
4
  import {
5
5
  Box,
6
+ Divider,
6
7
  Grid,
7
8
  Link,
8
9
  LinkProps,
@@ -52,7 +53,6 @@ import useErrors from '@hooks/useErrors';
52
53
  import { EventType } from '@utils/data/event';
53
54
  import TimeslotSelection from '@form/TimeslotSelection';
54
55
  import useGlobal from '@hooks/useGlobal';
55
- import useConsentScrollOnDrawerOpen from '@hooks/useConsentScrollOnDrawerOpen';
56
56
  import useScrollToFirstError from '@hooks/useScrollToFirstError';
57
57
  import { Trans } from '@components/Trans';
58
58
  import { EVENTLOOK_ORDER_FORM_ID, EVENTLOOK_ORDER_FORM_CONTAINER_ID } from '@utils/data/global.ts';
@@ -115,11 +115,6 @@ const TicketForm: React.FC<Props> = ({
115
115
  const hasFiredPaymentMethod = useRef(false);
116
116
  const termsAndConditionsRef = useRef<HTMLDivElement | null>(null);
117
117
 
118
- useConsentScrollOnDrawerOpen({
119
- isDrawerOpen: isPaymentOverviewDrawerOpen,
120
- consentRef: termsAndConditionsRef,
121
- });
122
-
123
118
  const item: IEcommerce = {
124
119
  currency: event.currency,
125
120
  items: [
@@ -693,14 +688,18 @@ const TicketForm: React.FC<Props> = ({
693
688
  />
694
689
  </Stack>
695
690
  </Grid>
696
- <Grid item xs={12} md={4} sx={{ display: { xs: 'none', md: 'block' } }}>
697
- <PaymentOverviewBox event={event} />
691
+ <Grid item xs={12} sx={{ display: { xs: 'block', md: 'none' } }}>
692
+ <Divider sx={{ borderStyle: 'dashed' }} />
693
+ </Grid>
694
+ <Grid item xs={12} md={4} mt={{ xs: 0, md: 0 }}>
695
+ <PaymentOverviewBox event={event} withoutPadding />
698
696
  </Grid>
699
697
  </Grid>
700
698
 
701
699
  <PaymentOverviewDrawer
702
700
  event={event}
703
701
  totalPrice={values.total}
702
+ termsAndConditionsRef={termsAndConditionsRef}
704
703
  onOpenChange={setIsPaymentOverviewDrawerOpen}
705
704
  />
706
705
 
@@ -21,7 +21,7 @@ const MerchandiseSlider: React.FC<Props> = ({ eventProducts, eventId, sx }) => {
21
21
  sx={{
22
22
  overflow: 'hidden',
23
23
  padding: theme.spacing(8, 3),
24
- margin: theme.spacing(-8, -3),
24
+ margin: theme.spacing(-8, -2.5),
25
25
  }}
26
26
  >
27
27
  <Box ref={emblaRef} className="embla" sx={{ overflow: 'visible' }}>
@@ -294,6 +294,7 @@ const ProductVariantsDialog: React.FC<Props> = ({
294
294
  <Button
295
295
  variant="outlined"
296
296
  onClick={() => handleRemoveQuantity(variant)}
297
+ aria-label={t('components.product_variant_dialog.decrease_quantity')}
297
298
  disabled={draftQty < 1}
298
299
  sx={{
299
300
  minWidth: 0,
@@ -328,6 +329,7 @@ const ProductVariantsDialog: React.FC<Props> = ({
328
329
  <Button
329
330
  variant="contained"
330
331
  onClick={() => handleAddQuantity(variant)}
332
+ aria-label={t('components.product_variant_dialog.increase_quantity')}
331
333
  disabled={draftQty >= 10 || draftQty >= maxAvailable}
332
334
  sx={{ minWidth: 0, height: 36, borderRadius: 1, fontWeight: 700 }}
333
335
  >
package/src/form/style.ts CHANGED
@@ -5,6 +5,12 @@ export const OverviewCard = styled(Card)<{ stickyHeaderTop: number }>(
5
5
  ({ theme, stickyHeaderTop }) => ({
6
6
  position: 'sticky',
7
7
  top: stickyHeaderTop,
8
+ borderRadius: theme.spacing(2),
9
+
10
+ [theme.breakpoints.down('sm')]: {
11
+ borderRadius: theme.spacing(1),
12
+ boxShadow: 'none',
13
+ },
8
14
  })
9
15
  );
10
16
 
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { Box, Button, IconButton, Stack } from '@mui/material';
3
3
  import { Iconify } from '@components/iconify';
4
+ import useGlobal from '@hooks/useGlobal';
4
5
 
5
6
  interface TicketQuantityControlProps {
6
7
  quantity: number;
@@ -23,11 +24,14 @@ const TicketQuantityControl: React.FC<TicketQuantityControlProps> = ({
23
24
  onIncrement,
24
25
  onAddFirst,
25
26
  }) => {
27
+ const { t } = useGlobal();
28
+
26
29
  if (quantity > 0) {
27
30
  return (
28
31
  <Stack direction="row" spacing={{ xs: 0.5, md: 1 }} alignItems="center">
29
32
  <IconButton
30
33
  onClick={onDecrement}
34
+ aria-label={t('form.labels.ticket_quantity_decrease')}
31
35
  disabled={isDisabled || quantity <= 0}
32
36
  sx={{
33
37
  width: { xs: 36, md: 40 },
@@ -56,6 +60,7 @@ const TicketQuantityControl: React.FC<TicketQuantityControlProps> = ({
56
60
  </Box>
57
61
  <IconButton
58
62
  onClick={onIncrement}
63
+ aria-label={t('form.labels.ticket_quantity_increase')}
59
64
  disabled={isDisabled || !canAddMore}
60
65
  sx={{
61
66
  width: { xs: 36, md: 40 },
@@ -76,6 +81,7 @@ const TicketQuantityControl: React.FC<TicketQuantityControlProps> = ({
76
81
  <Button
77
82
  variant="contained"
78
83
  onClick={onAddFirst}
84
+ aria-label={addLabel}
79
85
  disabled={isDisabled || !canAddFirst}
80
86
  sx={{
81
87
  height: { xs: 36, md: 40 },
@@ -53,22 +53,23 @@ const getFirstError = <T extends FieldValues>(
53
53
  return null;
54
54
  };
55
55
 
56
- const escapeSelectorValue = (value: string) => {
56
+ const escapeSelectorValue = (value: string): string => {
57
57
  if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
58
58
  return CSS.escape(value);
59
59
  }
60
60
 
61
- return value.replace(/"/g, '\\"');
61
+ // Fallback: escape all CSS special characters manually
62
+ return value.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '\\$1');
62
63
  };
63
64
 
64
65
  type ScrollToFirstErrorMethods<T extends FieldValues> = Pick<
65
- UseFormReturn<T, any, any>,
66
+ UseFormReturn<T, object, undefined>,
66
67
  'setFocus'
67
68
  >;
68
69
 
69
- export default function useScrollToFirstError<T extends FieldValues>(
70
- methods: ScrollToFirstErrorMethods<T>
71
- ) {
70
+ export default function useScrollToFirstError<T extends FieldValues>({
71
+ setFocus,
72
+ }: ScrollToFirstErrorMethods<T>) {
72
73
  return useCallback(
73
74
  (errors: FieldErrors<T>) => {
74
75
  const firstError = getFirstError(errors);
@@ -76,19 +77,23 @@ export default function useScrollToFirstError<T extends FieldValues>(
76
77
  return;
77
78
  }
78
79
 
79
- methods.setFocus(firstError.name);
80
-
80
+ // Re-query the DOM instead of relying on potentially stale refs
81
+ const fieldName = String(firstError.name);
81
82
  const scrollTarget =
82
- firstError.ref ||
83
- document.querySelector<HTMLElement>(
84
- `[name="${escapeSelectorValue(String(firstError.name))}"]`
85
- );
83
+ document.querySelector<HTMLElement>(`[name="${escapeSelectorValue(fieldName)}"]`) ||
84
+ document.getElementById(fieldName);
86
85
 
87
- scrollTarget?.scrollIntoView({
88
- behavior: 'smooth',
89
- block: 'center',
90
- });
86
+ if (scrollTarget) {
87
+ scrollTarget.scrollIntoView({
88
+ behavior: 'smooth',
89
+ block: 'center',
90
+ });
91
+ scrollTarget.focus({ preventScroll: true });
92
+ } else {
93
+ // Fallback to RHF's setFocus if no DOM element found
94
+ setFocus(firstError.name);
95
+ }
91
96
  },
92
- [methods]
97
+ [setFocus]
93
98
  );
94
99
  }