@getmicdrop/svelte-components 5.20.1 → 5.20.2

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 (333) hide show
  1. package/dist/calendar/AboutShow/AboutShow.svelte +191 -191
  2. package/dist/calendar/Calendar/MiniMonthCalendar.svelte +803 -803
  3. package/dist/calendar/FAQs/FAQs.svelte +88 -88
  4. package/dist/calendar/MonthSwitcher/MonthSwitcher.svelte +140 -140
  5. package/dist/calendar/OrderSummary/OrderSummary.svelte +461 -461
  6. package/dist/calendar/PublicCard/PublicCard.svelte +164 -164
  7. package/dist/calendar/ShowCard/ShowCard.svelte +180 -180
  8. package/dist/calendar/ShowTimeCard/ShowTimeCard.svelte +80 -80
  9. package/dist/calendar/index.js +15 -15
  10. package/dist/components/Heading.spec.js +89 -89
  11. package/dist/components/Heading.svelte +66 -66
  12. package/dist/components/Layout/AppShell.svelte +104 -104
  13. package/dist/components/Layout/ContentSection.svelte +80 -80
  14. package/dist/components/Layout/Grid.svelte +101 -101
  15. package/dist/components/Layout/Heading.svelte +81 -81
  16. package/dist/components/Layout/PageContainer.svelte +69 -69
  17. package/dist/components/Layout/Responsive.svelte +75 -75
  18. package/dist/components/Layout/Section.svelte +80 -80
  19. package/dist/components/Layout/ShowOnDesktop.svelte +37 -37
  20. package/dist/components/Layout/ShowOnMobile.svelte +37 -37
  21. package/dist/components/Layout/Sidebar.svelte +108 -108
  22. package/dist/components/Layout/Stack.spec.js +1 -1
  23. package/dist/components/Layout/Stack.svelte +52 -52
  24. package/dist/components/Layout/Text.svelte +87 -87
  25. package/dist/components/Layout/TwoColumn.svelte +108 -108
  26. package/dist/components/Text.spec.js +89 -89
  27. package/dist/components/Text.svelte +64 -64
  28. package/dist/config.js +160 -160
  29. package/dist/config.spec.js +29 -29
  30. package/dist/constants/formOptions.js +48 -48
  31. package/dist/constants/validation.js +91 -91
  32. package/dist/constants/validation.spec.js +64 -64
  33. package/dist/datetime/README.md +323 -323
  34. package/dist/datetime/__tests__/format.test.js +1 -1
  35. package/dist/datetime/__tests__/parse.test.js +1 -1
  36. package/dist/datetime/__tests__/timezone.test.js +1 -1
  37. package/dist/datetime/parse.js +1 -1
  38. package/dist/forms/createFormStore.svelte.js +1 -0
  39. package/dist/forms/createFormStore.svelte.spec.js +1 -0
  40. package/dist/index.js +85 -85
  41. package/dist/index.spec.js +369 -369
  42. package/dist/patterns/chat/ChatActivityNotice.spec.js +59 -59
  43. package/dist/patterns/chat/ChatActivityNotice.svelte +41 -41
  44. package/dist/patterns/chat/ChatBubble.spec.js +91 -91
  45. package/dist/patterns/chat/ChatBubble.svelte +103 -103
  46. package/dist/patterns/chat/ChatContainer.spec.js +30 -30
  47. package/dist/patterns/chat/ChatContainer.svelte +46 -46
  48. package/dist/patterns/chat/ChatDateDivider.spec.js +30 -30
  49. package/dist/patterns/chat/ChatDateDivider.svelte +27 -27
  50. package/dist/patterns/chat/ChatInvitationBubble.spec.js +46 -46
  51. package/dist/patterns/chat/ChatInvitationBubble.svelte +46 -46
  52. package/dist/patterns/chat/ChatInvitationNotice.spec.js +32 -32
  53. package/dist/patterns/chat/ChatInvitationNotice.svelte +36 -36
  54. package/dist/patterns/chat/ChatMessageGroup.spec.js +58 -58
  55. package/dist/patterns/chat/ChatMessageGroup.svelte +57 -57
  56. package/dist/patterns/chat/ChatSlotUpdate.spec.js +65 -65
  57. package/dist/patterns/chat/ChatSlotUpdate.svelte +46 -46
  58. package/dist/patterns/chat/ChatStatusBadge.spec.js +79 -79
  59. package/dist/patterns/chat/ChatStatusBadge.svelte +91 -91
  60. package/dist/patterns/chat/ChatStatusTransition.spec.js +81 -81
  61. package/dist/patterns/chat/ChatStatusTransition.svelte +64 -64
  62. package/dist/patterns/chat/ChatTextBubble.spec.js +35 -35
  63. package/dist/patterns/chat/ChatTextBubble.svelte +41 -41
  64. package/dist/patterns/chat/index.js +22 -22
  65. package/dist/patterns/data/DataGrid.svelte +45 -45
  66. package/dist/patterns/data/DataList.svelte +24 -24
  67. package/dist/patterns/data/DataTable.svelte +45 -45
  68. package/dist/patterns/data/index.js +4 -4
  69. package/dist/patterns/forms/FormActions.spec.js +95 -95
  70. package/dist/patterns/forms/FormActions.stories.svelte +97 -97
  71. package/dist/patterns/forms/FormActions.svelte +46 -46
  72. package/dist/patterns/forms/FormGrid.svelte +33 -33
  73. package/dist/patterns/forms/FormSection.svelte +33 -33
  74. package/dist/patterns/forms/FormValidationSummary.stories.svelte +97 -97
  75. package/dist/patterns/forms/FormValidationSummary.svelte +82 -82
  76. package/dist/patterns/forms/index.js +5 -5
  77. package/dist/patterns/index.js +21 -21
  78. package/dist/patterns/layout/Sidebar.svelte +39 -39
  79. package/dist/patterns/layout/SidebarTestWrapper.svelte +34 -34
  80. package/dist/patterns/layout/Stack.svelte +61 -61
  81. package/dist/patterns/layout/index.js +29 -29
  82. package/dist/patterns/navigation/BottomNav.stories.svelte +117 -117
  83. package/dist/patterns/navigation/BottomNav.svelte +82 -82
  84. package/dist/patterns/navigation/Header.stories.svelte +77 -77
  85. package/dist/patterns/navigation/Header.svelte +263 -263
  86. package/dist/patterns/navigation/index.js +3 -3
  87. package/dist/patterns/page/PageHeader.svelte +49 -49
  88. package/dist/patterns/page/PageLayout.svelte +40 -40
  89. package/dist/patterns/page/PageLoader.spec.js +57 -57
  90. package/dist/patterns/page/PageLoader.stories.svelte +137 -137
  91. package/dist/patterns/page/PageLoader.svelte +62 -62
  92. package/dist/patterns/page/SectionHeader.svelte +51 -51
  93. package/dist/patterns/page/index.js +5 -5
  94. package/dist/presets/badges.js +112 -112
  95. package/dist/presets/buttons.js +76 -76
  96. package/dist/presets/index.js +9 -9
  97. package/dist/primitives/Accordion/Accordion.stories.svelte +75 -75
  98. package/dist/primitives/Accordion/Accordion.svelte +62 -62
  99. package/dist/primitives/Accordion/AccordionItem.svelte +103 -103
  100. package/dist/primitives/Accordion/AccordionItemWrapper.test.svelte +107 -107
  101. package/dist/primitives/Accordion/AccordionToggleWrapper.test.svelte +28 -28
  102. package/dist/primitives/Alert/Alert.spec.js +173 -173
  103. package/dist/primitives/Alert/Alert.stories.svelte +88 -88
  104. package/dist/primitives/Alert/Alert.svelte +72 -72
  105. package/dist/primitives/Avatar/Avatar.stories.svelte +94 -94
  106. package/dist/primitives/Avatar/Avatar.svelte +66 -66
  107. package/dist/primitives/AvatarButton/AvatarButton.svelte +57 -57
  108. package/dist/primitives/Badges/Badge.spec.js +144 -144
  109. package/dist/primitives/Badges/Badge.stories.svelte +86 -86
  110. package/dist/primitives/Badges/Badge.svelte +99 -99
  111. package/dist/primitives/BottomSheet/BottomSheet.spec.js +238 -238
  112. package/dist/primitives/BottomSheet/BottomSheet.stories.svelte +83 -83
  113. package/dist/primitives/BottomSheet/BottomSheet.svelte +115 -115
  114. package/dist/primitives/BottomSheet/BottomSheetWithActions.test.svelte +20 -20
  115. package/dist/primitives/Breadcrumb/Breadcrumb.spec.js +123 -123
  116. package/dist/primitives/Breadcrumb/Breadcrumb.stories.svelte +23 -23
  117. package/dist/primitives/Breadcrumb/Breadcrumb.svelte +107 -107
  118. package/dist/primitives/Button/Button.spec.js +225 -225
  119. package/dist/primitives/Button/Button.stories.svelte +76 -76
  120. package/dist/primitives/Button/Button.svelte +278 -278
  121. package/dist/primitives/Button/ButtonGroup.spec.js +44 -44
  122. package/dist/primitives/Button/ButtonGroup.svelte +50 -50
  123. package/dist/primitives/Button/ButtonSaveDemo.spec.js +146 -146
  124. package/dist/primitives/Button/ButtonSaveDemo.svelte +25 -25
  125. package/dist/primitives/Button/ButtonVariantShowcase.svelte +129 -129
  126. package/dist/primitives/Card.spec.js +49 -49
  127. package/dist/primitives/Card.stories.svelte +22 -22
  128. package/dist/primitives/Card.svelte +28 -28
  129. package/dist/primitives/CardAction/CardAction.svelte +68 -68
  130. package/dist/primitives/Checkbox/Checkbox.stories.svelte +84 -84
  131. package/dist/primitives/Checkbox/Checkbox.svelte +88 -88
  132. package/dist/primitives/DarkModeToggle.spec.js +390 -390
  133. package/dist/primitives/DarkModeToggle.stories.svelte +57 -57
  134. package/dist/primitives/DarkModeToggle.svelte +147 -147
  135. package/dist/primitives/Drawer/Drawer.stories.svelte +100 -100
  136. package/dist/primitives/Drawer/Drawer.svelte +224 -224
  137. package/dist/primitives/Drawer/DrawerTestWrapper.svelte +86 -86
  138. package/dist/primitives/Dropdown/Dropdown.stories.svelte +137 -137
  139. package/dist/primitives/Dropdown/Dropdown.svelte +179 -179
  140. package/dist/primitives/Dropdown/DropdownDivider.spec.js +30 -30
  141. package/dist/primitives/Dropdown/DropdownDivider.svelte +9 -9
  142. package/dist/primitives/Dropdown/DropdownItem.svelte +80 -80
  143. package/dist/primitives/Dropdown/DropdownItemTestWrapper.svelte +43 -43
  144. package/dist/primitives/Helper/Helper.spec.js +57 -57
  145. package/dist/primitives/Helper/Helper.svelte +33 -33
  146. package/dist/primitives/Icons/ArrowLeft.svelte +21 -21
  147. package/dist/primitives/Icons/ArrowRight.svelte +21 -21
  148. package/dist/primitives/Icons/Availability.svelte +27 -27
  149. package/dist/primitives/Icons/Back.svelte +27 -27
  150. package/dist/primitives/Icons/CheckCircle.svelte +19 -19
  151. package/dist/primitives/Icons/CheckCircleOutline.svelte +28 -28
  152. package/dist/primitives/Icons/ChevronLeft.svelte +17 -17
  153. package/dist/primitives/Icons/ChevronRight.svelte +17 -17
  154. package/dist/primitives/Icons/Copy.svelte +28 -28
  155. package/dist/primitives/Icons/Cross.svelte +18 -18
  156. package/dist/primitives/Icons/DownArrow.svelte +21 -21
  157. package/dist/primitives/Icons/ErrorCircle.svelte +19 -19
  158. package/dist/primitives/Icons/FacebookIcon.svelte +15 -15
  159. package/dist/primitives/Icons/Home.svelte +28 -28
  160. package/dist/primitives/Icons/Icon.spec.js +175 -175
  161. package/dist/primitives/Icons/Icon.stories.svelte +100 -100
  162. package/dist/primitives/Icons/Icon.svelte +79 -79
  163. package/dist/primitives/Icons/IconGallery.stories.svelte +235 -235
  164. package/dist/primitives/Icons/ImageOutline.svelte +21 -21
  165. package/dist/primitives/Icons/Info.svelte +20 -20
  166. package/dist/primitives/Icons/InstagramIcon.svelte +21 -21
  167. package/dist/primitives/Icons/LogoInstagram.svelte +15 -15
  168. package/dist/primitives/Icons/Message.svelte +28 -28
  169. package/dist/primitives/Icons/MoonIcon.svelte +18 -18
  170. package/dist/primitives/Icons/More.svelte +34 -34
  171. package/dist/primitives/Icons/MoreHori.spec.js +67 -67
  172. package/dist/primitives/Icons/MoreHori.svelte +35 -35
  173. package/dist/primitives/Icons/Notification.svelte +27 -27
  174. package/dist/primitives/Icons/Payment.svelte +27 -27
  175. package/dist/primitives/Icons/Profile.svelte +34 -34
  176. package/dist/primitives/Icons/Reload.svelte +42 -42
  177. package/dist/primitives/Icons/Shows.svelte +34 -34
  178. package/dist/primitives/Icons/Signout.svelte +34 -34
  179. package/dist/primitives/Icons/SunIcon.svelte +21 -21
  180. package/dist/primitives/Icons/TiktokIcon.svelte +15 -15
  181. package/dist/primitives/Icons/TrashBinOutline.svelte +21 -21
  182. package/dist/primitives/Icons/TwitterIcon.svelte +15 -15
  183. package/dist/primitives/Icons/WarningIcon.spec.js +30 -30
  184. package/dist/primitives/Icons/WarningIcon.svelte +24 -24
  185. package/dist/primitives/Input/Input.spec.js +1237 -1237
  186. package/dist/primitives/Input/Input.stories.svelte +139 -139
  187. package/dist/primitives/Input/Input.svelte +444 -444
  188. package/dist/primitives/Input/Select.spec.js +632 -632
  189. package/dist/primitives/Input/Select.stories.svelte +112 -112
  190. package/dist/primitives/Input/Select.svelte +252 -252
  191. package/dist/primitives/Input/Textarea.stories.svelte +137 -137
  192. package/dist/primitives/Input/Textarea.svelte +105 -105
  193. package/dist/primitives/Label/Label.svelte +37 -37
  194. package/dist/primitives/LandingButton/LandingButton.spec.js +61 -61
  195. package/dist/primitives/LandingButton/LandingButton.svelte +92 -92
  196. package/dist/primitives/MenuItem/MenuItem.spec.js +130 -130
  197. package/dist/primitives/MenuItem/MenuItem.svelte +85 -85
  198. package/dist/primitives/Modal/Modal.spec.js +314 -314
  199. package/dist/primitives/Modal/Modal.stories.svelte +86 -86
  200. package/dist/primitives/Modal/Modal.svelte +181 -181
  201. package/dist/primitives/NavItem/NavItem.spec.js +97 -97
  202. package/dist/primitives/NavItem/NavItem.svelte +75 -75
  203. package/dist/primitives/NumberInput/NumberInput.svelte +113 -113
  204. package/dist/primitives/Pagination/DotIndicator.svelte +66 -66
  205. package/dist/primitives/Pagination/Pagination.stories.svelte +76 -76
  206. package/dist/primitives/Pagination/Pagination.svelte +275 -275
  207. package/dist/primitives/Radio/Radio.stories.svelte +80 -80
  208. package/dist/primitives/Radio/Radio.svelte +67 -67
  209. package/dist/primitives/SearchResultItem/SearchResultItem.spec.js +78 -78
  210. package/dist/primitives/SearchResultItem/SearchResultItem.svelte +109 -109
  211. package/dist/primitives/SidebarToggle/SidebarToggle.spec.js +61 -61
  212. package/dist/primitives/SidebarToggle/SidebarToggle.svelte +55 -55
  213. package/dist/primitives/Skeleton/CardPlaceholder.svelte +96 -96
  214. package/dist/primitives/Skeleton/ImagePlaceholder.svelte +68 -68
  215. package/dist/primitives/Skeleton/ListPlaceholder.svelte +85 -85
  216. package/dist/primitives/Skeleton/Skeleton.stories.svelte +151 -151
  217. package/dist/primitives/Skeleton/Skeleton.svelte +55 -55
  218. package/dist/primitives/Spinner/Spinner.spec.js +84 -84
  219. package/dist/primitives/Spinner/Spinner.stories.svelte +29 -29
  220. package/dist/primitives/Spinner/Spinner.svelte +52 -52
  221. package/dist/primitives/Tabs/TabItem.svelte +52 -52
  222. package/dist/primitives/Tabs/Tabs.stories.svelte +112 -112
  223. package/dist/primitives/Tabs/Tabs.svelte +137 -137
  224. package/dist/primitives/Toggle.spec.js +221 -221
  225. package/dist/primitives/Toggle.stories.svelte +92 -92
  226. package/dist/primitives/Toggle.svelte +141 -141
  227. package/dist/primitives/ToggleTestWrapper.svelte +30 -30
  228. package/dist/primitives/Tooltip/Tooltip.spec.js +126 -126
  229. package/dist/primitives/Tooltip/Tooltip.svelte +83 -83
  230. package/dist/primitives/Typography/Typography.svelte +53 -53
  231. package/dist/primitives/ValidationError.spec.js +103 -103
  232. package/dist/primitives/ValidationError.stories.svelte +112 -112
  233. package/dist/primitives/ValidationError.svelte +29 -29
  234. package/dist/primitives/index.js +114 -114
  235. package/dist/recipes/CropImage/CropImage.spec.js +208 -208
  236. package/dist/recipes/CropImage/CropImage.stories.svelte +104 -104
  237. package/dist/recipes/CropImage/CropImage.svelte +241 -241
  238. package/dist/recipes/ImageUploader/ImageUploader.stories.svelte +125 -125
  239. package/dist/recipes/ImageUploader/ImageUploader.svelte +994 -994
  240. package/dist/recipes/SuperLogin/SuperLogin.svelte +1 -1
  241. package/dist/recipes/Toaster/Toaster.stories.svelte +62 -62
  242. package/dist/recipes/feedback/EmptyState/EmptyState.svelte +75 -75
  243. package/dist/recipes/feedback/ErrorDisplay.spec.js +69 -69
  244. package/dist/recipes/feedback/ErrorDisplay.stories.svelte +113 -113
  245. package/dist/recipes/feedback/ErrorDisplay.svelte +67 -67
  246. package/dist/recipes/feedback/StatusIndicator/StatusIndicator.spec.js +133 -133
  247. package/dist/recipes/feedback/StatusIndicator/StatusIndicator.svelte +176 -176
  248. package/dist/recipes/feedback/index.js +4 -4
  249. package/dist/recipes/fields/CheckboxField.svelte +85 -85
  250. package/dist/recipes/fields/FormField.svelte +58 -58
  251. package/dist/recipes/fields/RadioGroup.svelte +95 -95
  252. package/dist/recipes/fields/SelectField.svelte +82 -82
  253. package/dist/recipes/fields/TextareaField.svelte +101 -101
  254. package/dist/recipes/fields/ToggleField.svelte +60 -60
  255. package/dist/recipes/fields/index.js +7 -7
  256. package/dist/recipes/index.js +24 -24
  257. package/dist/recipes/inputs/MultiSelect.spec.js +263 -263
  258. package/dist/recipes/inputs/MultiSelect.stories.svelte +133 -133
  259. package/dist/recipes/inputs/MultiSelect.svelte +291 -291
  260. package/dist/recipes/inputs/OTPInput.spec.js +251 -251
  261. package/dist/recipes/inputs/OTPInput.stories.svelte +162 -162
  262. package/dist/recipes/inputs/OTPInput.svelte +128 -128
  263. package/dist/recipes/inputs/PasswordInput.svelte +130 -130
  264. package/dist/recipes/inputs/PasswordStrengthIndicator/PasswordStrengthIndicator.svelte +142 -142
  265. package/dist/recipes/inputs/PhoneInput.svelte +254 -254
  266. package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.stories.svelte +170 -170
  267. package/dist/recipes/inputs/PlaceAutocomplete/PlaceAutocomplete.svelte +349 -349
  268. package/dist/recipes/inputs/Search.svelte +110 -110
  269. package/dist/recipes/inputs/index.js +8 -8
  270. package/dist/recipes/inputs/phoneInput/CountrySelector.svelte +240 -240
  271. package/dist/recipes/modals/AlertModal.svelte +139 -139
  272. package/dist/recipes/modals/ConfirmationModal.spec.js +396 -396
  273. package/dist/recipes/modals/ConfirmationModal.stories.svelte +119 -119
  274. package/dist/recipes/modals/ConfirmationModal.svelte +169 -169
  275. package/dist/recipes/modals/FeedbackModal.svelte +205 -205
  276. package/dist/recipes/modals/InputModal.svelte +194 -194
  277. package/dist/recipes/modals/ModalStateManager.spec.js +100 -100
  278. package/dist/recipes/modals/ModalStateManager.svelte +77 -77
  279. package/dist/recipes/modals/ModalTestWrapper.svelte +65 -65
  280. package/dist/recipes/modals/StatusModal.svelte +216 -216
  281. package/dist/recipes/modals/index.js +8 -8
  282. package/dist/schemas/common.js +1 -1
  283. package/dist/schemas/event.js +1 -1
  284. package/dist/schemas/order.js +1 -0
  285. package/dist/schemas/performer.js +1 -1
  286. package/dist/schemas/promo.js +2 -2
  287. package/dist/schemas/ticket.js +1 -1
  288. package/dist/schemas/user.js +2 -1
  289. package/dist/services/show.service.d.ts +46 -46
  290. package/dist/stores/auth.d.ts +8 -8
  291. package/dist/stores/auth.svelte.spec.js +1 -1
  292. package/dist/stores/index.js +9 -9
  293. package/dist/stores/toaster.d.ts +3 -3
  294. package/dist/stores/toaster.js +13 -13
  295. package/dist/stores/toaster.spec.js +59 -59
  296. package/dist/stories/ButtonAuditReview.stories.svelte +14 -14
  297. package/dist/stories/ButtonAuditReview.svelte +427 -427
  298. package/dist/stories/PatternsGallery.stories.svelte +19 -19
  299. package/dist/stories/PatternsGallery.svelte +399 -399
  300. package/dist/stories/PrimitivesGallery.stories.svelte +19 -19
  301. package/dist/stories/PrimitivesGallery.svelte +756 -756
  302. package/dist/stories/RecipesGallery.stories.svelte +19 -19
  303. package/dist/stories/RecipesGallery.svelte +454 -454
  304. package/dist/stories/button-audit-manifest.json +11186 -11186
  305. package/dist/tailwind/preset.cjs +87 -87
  306. package/dist/telemetry.js +401 -401
  307. package/dist/telemetry.server.js +212 -212
  308. package/dist/telemetry.server.spec.js +437 -437
  309. package/dist/telemetry.spec.js +1273 -1273
  310. package/dist/tokens/tokens.css +87 -87
  311. package/dist/tokens/typography-base.css +163 -163
  312. package/dist/tokens/utilities.css +430 -430
  313. package/dist/utils/__tests__/auth.test.js +431 -431
  314. package/dist/utils/apiConfig.d.ts +29 -29
  315. package/dist/utils/apiConfig.js +142 -142
  316. package/dist/utils/auth.d.ts +46 -46
  317. package/dist/utils/auth.js +195 -195
  318. package/dist/utils/greetings.js +187 -187
  319. package/dist/utils/greetings.spec.js +337 -337
  320. package/dist/utils/haptic.spec.js +1 -1
  321. package/dist/utils/imageValidation.js +121 -121
  322. package/dist/utils/imageValidation.spec.js +223 -223
  323. package/dist/utils/portal.d.ts +11 -11
  324. package/dist/utils/portal.js +25 -25
  325. package/dist/utils/portal.spec.js +143 -143
  326. package/dist/utils/transitions.js +16 -16
  327. package/dist/utils/utils/utils.d.ts +2 -2
  328. package/dist/utils/utils/utils.js +3 -3
  329. package/dist/utils/utils/utils.spec.js +698 -698
  330. package/dist/utils/utils.d.ts +41 -41
  331. package/dist/utils/utils.js +59 -59
  332. package/dist/utils/utils.spec.js +643 -643
  333. package/package.json +306 -306
@@ -1,461 +1,461 @@
1
- <script>
2
- import { fly, fade } from 'svelte/transition';
3
- import { cubicOut } from 'svelte/easing';
4
- import { ChevronDownOutline, CloseOutline } from '../../primitives/Icons';
5
- import Spinner from '../../primitives/Spinner/Spinner.svelte';
6
- import { typography } from '../../tokens/typography';
7
- import { roundCurrency } from '../../utils/formatters';
8
-
9
- /**
10
- * Default labels for all user-visible strings in OrderSummary.
11
- * Consumers can override any string via the `labels` prop.
12
- */
13
- const defaultLabels = {
14
- orderSummary: 'Order summary',
15
- selectTickets: 'Select tickets to continue',
16
- free: 'Free',
17
- fullPrice: 'Full Price',
18
- discount: 'Discount',
19
- subtotal: 'Subtotal',
20
- fees: 'Fees',
21
- taxes: 'Taxes',
22
- giftCardAppliedLabel: 'Gift Card Applied',
23
- giftCardBalanceAfterOrder: 'Gift card balance after order',
24
- amountDue: 'Amount Due',
25
- fullyCoveredByGiftCard: 'Fully covered by gift card',
26
- total: 'Total',
27
- checkoutButtonText: 'Checkout',
28
- placeOrderButtonText: 'Place order',
29
- placeOrderTos: 'By selecting Place order, I agree to the',
30
- termsOfService: 'terms of service',
31
- checkout: 'Checkout',
32
- ticketSingular: 'ticket',
33
- ticketPlural: 'tickets',
34
- closeOrderSummary: 'Close order summary',
35
- };
36
-
37
- let {
38
- loading = false,
39
- quantities = {},
40
- donationAmounts = {}, // Map of ticketId -> donation amount string
41
- eventTickets = [],
42
- checkoutTicket = null,
43
- isAgreed = true,
44
- btnText = undefined,
45
- showTerms = false,
46
- promoApplied = false,
47
- promoDiscount = 0,
48
- currentPromoRule = null,
49
- executePurchase = null,
50
- elements = null,
51
- venueServiceCharge = {
52
- serviceFeeCents: 0,
53
- serviceFeePercentage: 0,
54
- serviceFeeChargeType: 'both',
55
- maxServiceFeeCents: 0,
56
- taxPercentage: 0,
57
- },
58
- onPriceUpdate,
59
- giftCardApplied = null, // { code, giftCardAmount, giftCardBalance, stripeAmount, paymentType, requiresStripe }
60
- /** @type {Partial<typeof defaultLabels>} Override any user-visible string */
61
- labels: userLabels = {},
62
- } = $props();
63
-
64
- let labels = $derived({ ...defaultLabels, ...userLabels });
65
-
66
- // Helper to get effective price for a ticket (handles donation tickets)
67
- function getEffectivePrice(ticket) {
68
- // Donation ticket (type 2): use user-entered donation amount
69
- if (isDonationTicket(ticket)) {
70
- const donationAmount = donationAmounts[ticket.ID];
71
- return parseFloat(donationAmount) || 0;
72
- }
73
- // Regular ticket: use ticket price
74
- return parseFloat(ticket.price) || 0;
75
- }
76
-
77
- // Check if ticket is a donation ticket
78
- // Handle both 'type' and 'ticketType' fields, and coerce to number for comparison
79
- function isDonationTicket(ticket) {
80
- const ticketType = ticket.type ?? ticket.ticketType ?? 0;
81
- return Number(ticketType) === 2;
82
- }
83
-
84
- let showOrderSummaryOnMobile = $state(false);
85
-
86
- function feeFor(price) {
87
- const chargeType = venueServiceCharge.serviceFeeChargeType || 'both';
88
- let fee = 0;
89
-
90
- if (chargeType === 'flat' || chargeType === 'both') {
91
- fee += (venueServiceCharge.serviceFeeCents || 0) / 100;
92
- }
93
- if (chargeType === 'percent' || chargeType === 'both') {
94
- fee += ((venueServiceCharge.serviceFeePercentage || 0) / 100) * price;
95
- }
96
-
97
- const maxFee = venueServiceCharge.maxServiceFeeCents || 0;
98
- if (maxFee > 0 && fee > maxFee / 100) {
99
- fee = maxFee / 100;
100
- }
101
-
102
- return roundCurrency(fee);
103
- }
104
-
105
- function makeOrderSummaryVisible() {
106
- showOrderSummaryOnMobile = !showOrderSummaryOnMobile;
107
- }
108
-
109
- function getDiscountedPrice(ticket) {
110
- if (!currentPromoRule?.provideDiscount) return null;
111
-
112
- const discountTicketIds = currentPromoRule.discountTicketIds || [];
113
- if (discountTicketIds.length > 0 && !discountTicketIds.includes(ticket.ID)) {
114
- return null;
115
- }
116
-
117
- const basePrice = parseFloat(ticket.price) || 0;
118
- const discountAmount = parseFloat(currentPromoRule?.amount || '0') || 0;
119
- if (currentPromoRule.discountType === '%') {
120
- return (basePrice * (1 - discountAmount / 100)).toFixed(2);
121
- } else if (currentPromoRule.discountType === '$') {
122
- return Math.max(0, basePrice - discountAmount).toFixed(2);
123
- }
124
- return null;
125
- }
126
-
127
- let totalQuantity = $derived(Object.values(quantities).reduce((sum, q) => sum + q, 0));
128
- let showFooter = $derived(totalQuantity > 0);
129
-
130
- let subtotalWithoutDiscount = $derived(roundCurrency(Object.keys(quantities).reduce((acc, key) => {
131
- const ticket = eventTickets.find(t => t.ID == key);
132
- if (!ticket) return acc;
133
- // For donation tickets, use the donation amount; otherwise use ticket price
134
- const effectivePrice = getEffectivePrice(ticket);
135
- return acc + quantities[key] * effectivePrice;
136
- }, 0)));
137
-
138
- let subtotal = $derived(roundCurrency(Object.keys(quantities).reduce((acc, key) => {
139
- const ticket = eventTickets.find(t => t.ID == key);
140
- if (!ticket) return acc;
141
- // For donation tickets, use the donation amount (no discounts apply)
142
- if (isDonationTicket(ticket)) {
143
- const effectivePrice = getEffectivePrice(ticket);
144
- return acc + quantities[key] * effectivePrice;
145
- }
146
- // For regular tickets, apply discounts as usual
147
- const discountedPrice = getDiscountedPrice(ticket);
148
- const priceToUse = discountedPrice !== null ? parseFloat(discountedPrice) : ticket.price;
149
- return acc + quantities[key] * priceToUse;
150
- }, 0)));
151
-
152
- let promoSavings = $derived(currentPromoRule?.provideDiscount ? (subtotalWithoutDiscount - subtotal) : 0);
153
-
154
- let fees = $derived(roundCurrency(Object.keys(quantities).reduce((acc, key) => {
155
- const ticket = eventTickets.find(t => t.ID == key);
156
- // Skip fees for: no ticket, free tickets, and donation tickets (type 2)
157
- if (!ticket || ticket.price == 0 || isDonationTicket(ticket)) return acc;
158
- const discountedPrice = getDiscountedPrice(ticket);
159
- const priceToUse = discountedPrice !== null ? parseFloat(discountedPrice) : ticket.price;
160
- return acc + quantities[key] * feeFor(priceToUse);
161
- }, 0)));
162
-
163
- let taxRate = $derived((venueServiceCharge.taxPercentage || 0) / 100);
164
- let taxes = $derived(subtotal > 0 ? roundCurrency(subtotal * taxRate) : 0);
165
-
166
- // Gift card derived values
167
- let giftCardAmountDisplay = $derived(
168
- giftCardApplied ? (giftCardApplied.giftCardAmount / 100).toFixed(2) : null
169
- );
170
- let giftCardRemainingBalance = $derived(
171
- giftCardApplied ? ((giftCardApplied.giftCardBalance - giftCardApplied.giftCardAmount) / 100).toFixed(2) : null
172
- );
173
-
174
- // Total accounts for gift card deduction
175
- let total = $derived(
176
- roundCurrency(Math.max(0, subtotal + fees + taxes
177
- - (promoApplied && !currentPromoRule?.provideDiscount ? promoDiscount : 0)
178
- - (giftCardApplied ? giftCardApplied.giftCardAmount / 100 : 0)
179
- ))
180
- );
181
-
182
- $effect(() => {
183
- onPriceUpdate?.({ subtotal, fees, taxes, total, promoSavings });
184
- });
185
- </script>
186
-
187
- <div
188
- id="orderSummary"
189
- class="hidden min-[872px]:block h-fit rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600"
190
- >
191
- <div class="px-5 py-4 border-b border-gray-200 dark:border-gray-600">
192
- <h3 class={typography.h3}>
193
- {labels.orderSummary}
194
- </h3>
195
- </div>
196
-
197
- <div class="px-5 pb-5">
198
- {#if totalQuantity === 0}
199
- <div class="py-8 text-center">
200
- <p class="{typography.smMuted}">{labels.selectTickets}</p>
201
- </div>
202
- {:else}
203
- {#each Object.keys(quantities) as key}
204
- {#if quantities[key] > 0}
205
- {#each eventTickets as ticket}
206
- {#if ticket.ID == key}
207
- {@const effectivePrice = getEffectivePrice(ticket)}
208
- {@const isDonation = isDonationTicket(ticket)}
209
- <div class="flex justify-between py-3 border-b border-gray-200/50 dark:border-gray-600/50">
210
- <div>
211
- <p class="{typography.label}">{ticket.name}</p>
212
- <p class="{typography.smMuted}">
213
- {#if ticket.price === 0 && !isDonation}
214
- {labels.free} x {quantities[key]}
215
- {:else if isDonation}
216
- ${effectivePrice.toFixed(2)} x {quantities[key]}
217
- {:else}
218
- ${ticket.price.toFixed(2)} x {quantities[key]}
219
- {/if}
220
- </p>
221
- </div>
222
- <div class="{typography.label}">
223
- {#if ticket.price === 0 && !isDonation}
224
- {labels.free}
225
- {:else}
226
- ${(effectivePrice * quantities[key]).toFixed(2)}
227
- {/if}
228
- </div>
229
- </div>
230
- {/if}
231
- {/each}
232
- {/if}
233
- {/each}
234
-
235
- <div class="{`${typography.sm} flex flex-col py-3 gap-2`}">
236
- {#if promoSavings > 0 || (promoDiscount > 0 && !currentPromoRule?.provideDiscount)}
237
- <div class="flex justify-between text-gray-600 dark:text-gray-300">
238
- <span>{labels.fullPrice}</span><span>${subtotalWithoutDiscount.toFixed(2)}</span>
239
- </div>
240
- {/if}
241
- {#if promoSavings > 0}
242
- <div class="flex justify-between text-green-600 dark:text-green-500">
243
- <span>{labels.discount}</span><span>-${promoSavings.toFixed(2)}</span>
244
- </div>
245
- {:else if promoDiscount > 0 && !currentPromoRule?.provideDiscount}
246
- <div class="flex justify-between text-green-600 dark:text-green-500">
247
- <span>{labels.discount}</span><span>-${promoDiscount.toFixed(2)}</span>
248
- </div>
249
- {/if}
250
- <div class="flex justify-between text-gray-600 dark:text-gray-300">
251
- <span>{labels.subtotal}</span><span>${subtotal.toFixed(2)}</span>
252
- </div>
253
- <div class="flex justify-between text-gray-600 dark:text-gray-300">
254
- <span>{labels.fees}</span><span>${fees.toFixed(2)}</span>
255
- </div>
256
- <div class="flex justify-between text-gray-600 dark:text-gray-300">
257
- <span>{labels.taxes}</span><span>${taxes.toFixed(2)}</span>
258
- </div>
259
- {#if giftCardApplied}
260
- <div class="flex justify-between text-green-600 dark:text-green-500">
261
- <span>{labels.giftCardAppliedLabel}</span>
262
- <span>-${giftCardAmountDisplay}</span>
263
- </div>
264
- {#if giftCardRemainingBalance && parseFloat(giftCardRemainingBalance) > 0}
265
- <div class="flex justify-between text-gray-500 dark:text-gray-400 text-xs">
266
- <span>{labels.giftCardBalanceAfterOrder}</span>
267
- <span>${giftCardRemainingBalance}</span>
268
- </div>
269
- {/if}
270
- {/if}
271
- </div>
272
-
273
- {#if giftCardApplied?.paymentType === 'gift_card_only'}
274
- <div class="flex justify-between py-4 text-lg border-t border-gray-200 dark:border-gray-600">
275
- <span class="font-semibold text-gray-900 dark:text-white">{labels.amountDue}</span>
276
- <span class="font-bold text-green-600 dark:text-green-400">$0.00</span>
277
- </div>
278
- <p class="text-xs text-gray-500 dark:text-gray-400 text-center -mt-2 mb-2">
279
- {labels.fullyCoveredByGiftCard}
280
- </p>
281
- {:else}
282
- <div class="flex justify-between {typography.h3} py-4 text-lg border-t border-gray-200 dark:border-gray-600">
283
- <span>{labels.total}</span><span>${total.toFixed(2)}</span>
284
- </div>
285
- {/if}
286
- {/if}
287
-
288
- {#if totalQuantity > 0 && showTerms}
289
- <p class="{typography.xsMuted} text-center mb-3">
290
- {labels.placeOrderTos} <a href="https://get-micdrop.com/tos" class="text-blue-700 dark:text-blue-500 underline hover:opacity-80" target="_blank" rel="noopener noreferrer">{labels.termsOfService}</a>
291
- </p>
292
- {/if}
293
-
294
- <button
295
- class="w-full h-12 font-semibold rounded-lg flex items-center justify-center transition-colors select-none touch-manipulation {totalQuantity === 0 ? 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed' : 'bg-blue-700 dark:bg-blue-600 text-white hover:bg-blue-800 dark:hover:bg-blue-700'}"
296
- onclick={() => {
297
- if (totalQuantity === 0) return;
298
- if (executePurchase) {
299
- executePurchase(elements);
300
- } else if (checkoutTicket) {
301
- checkoutTicket();
302
- }
303
- }}
304
- disabled={totalQuantity === 0}
305
- >
306
- {#if loading}
307
- <Spinner size="sm" color="white" />
308
- {:else}
309
- <span translate="no">{btnText ?? (showTerms ? labels.placeOrderButtonText : labels.checkoutButtonText)}</span>
310
- {/if}
311
- </button>
312
- </div>
313
- </div>
314
-
315
- {#if showOrderSummaryOnMobile}
316
- <button
317
- class="min-[872px]:hidden fixed inset-0 bg-black/50 z-40 border-none cursor-pointer"
318
- onclick={() => (showOrderSummaryOnMobile = false)}
319
- aria-label={labels.closeOrderSummary}
320
- transition:fade={{ duration: 200 }}
321
- ></button>
322
- <div
323
- in:fly={{ y: 800, duration: 300, easing: cubicOut }}
324
- out:fly={{ y: 800, duration: 300, easing: cubicOut }}
325
- class="min-[872px]:hidden fixed bottom-0 left-0 w-full overflow-x-hidden overflow-y-auto z-50 max-h-[80vh] rounded-t-lg shadow-xl bg-white dark:bg-gray-800"
326
- >
327
- <div class="flex flex-row justify-between items-center px-5 py-4 border-b border-gray-200 dark:border-gray-600">
328
- <h2 class="{typography.h2} text-xl">
329
- {labels.orderSummary}
330
- </h2>
331
- <button
332
- onclick={() => (showOrderSummaryOnMobile = false)}
333
- class="transition-colors p-2 rounded-lg text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-700"
334
- aria-label={labels.closeOrderSummary}
335
- >
336
- <CloseOutline class="w-7 h-7" />
337
- </button>
338
- </div>
339
-
340
- <div class="px-5 pb-5">
341
- {#each Object.keys(quantities) as key}
342
- {#if quantities[key] > 0}
343
- {#each eventTickets as ticket}
344
- {#if ticket.ID == key}
345
- {@const effectivePrice = getEffectivePrice(ticket)}
346
- {@const isDonation = isDonationTicket(ticket)}
347
- <div class="flex justify-between py-4 border-b border-gray-200 dark:border-gray-600">
348
- <div>
349
- <p class="{typography.h5}">{ticket.name}</p>
350
- <p class="{typography.smMuted}">
351
- {#if ticket.price === 0 && !isDonation}
352
- {labels.free} x {quantities[key]}
353
- {:else if isDonation}
354
- ${effectivePrice.toFixed(2)} x {quantities[key]}
355
- {:else}
356
- ${ticket.price.toFixed(2)} x {quantities[key]}
357
- {/if}
358
- </p>
359
- </div>
360
- <div class="{typography.h5}">
361
- {#if ticket.price === 0 && !isDonation}
362
- {labels.free}
363
- {:else}
364
- ${(effectivePrice * quantities[key]).toFixed(2)}
365
- {/if}
366
- </div>
367
- </div>
368
- {/if}
369
- {/each}
370
- {/if}
371
- {/each}
372
-
373
- <div class="flex flex-col py-4 gap-3 text-gray-600 dark:text-gray-300">
374
- {#if promoSavings > 0 || (promoDiscount > 0 && !currentPromoRule?.provideDiscount)}
375
- <div class="flex justify-between">
376
- <span>{labels.fullPrice}</span><span>${subtotalWithoutDiscount.toFixed(2)}</span>
377
- </div>
378
- {/if}
379
- {#if promoSavings > 0}
380
- <div class="flex justify-between text-green-600 dark:text-green-500">
381
- <span>{labels.discount}</span><span>-${promoSavings.toFixed(2)}</span>
382
- </div>
383
- {:else if promoDiscount > 0 && !currentPromoRule?.provideDiscount}
384
- <div class="flex justify-between text-green-600 dark:text-green-500">
385
- <span>{labels.discount}</span><span>-${promoDiscount.toFixed(2)}</span>
386
- </div>
387
- {/if}
388
- <div class="flex justify-between">
389
- <span>{labels.subtotal}</span><span>${subtotal.toFixed(2)}</span>
390
- </div>
391
- <div class="flex justify-between">
392
- <span>{labels.fees}</span><span>${fees.toFixed(2)}</span>
393
- </div>
394
- <div class="flex justify-between">
395
- <span>{labels.taxes}</span><span>${taxes.toFixed(2)}</span>
396
- </div>
397
- {#if giftCardApplied}
398
- <div class="flex justify-between text-green-600 dark:text-green-500">
399
- <span>{labels.giftCardAppliedLabel}</span>
400
- <span>-${giftCardAmountDisplay}</span>
401
- </div>
402
- {#if giftCardRemainingBalance && parseFloat(giftCardRemainingBalance) > 0}
403
- <div class="flex justify-between text-gray-500 dark:text-gray-400 text-xs">
404
- <span>{labels.giftCardBalanceAfterOrder}</span>
405
- <span>${giftCardRemainingBalance}</span>
406
- </div>
407
- {/if}
408
- {/if}
409
- </div>
410
-
411
- {#if giftCardApplied?.paymentType === 'gift_card_only'}
412
- <div class="flex justify-between py-5 border-t border-gray-200 dark:border-gray-600">
413
- <span class="font-semibold text-gray-900 dark:text-white">{labels.amountDue}</span>
414
- <span class="font-bold text-green-600 dark:text-green-400">$0.00</span>
415
- </div>
416
- <p class="text-xs text-gray-500 dark:text-gray-400 text-center -mt-2 mb-2">
417
- {labels.fullyCoveredByGiftCard}
418
- </p>
419
- {:else}
420
- <div class="flex justify-between {typography.h3} py-5 border-t border-gray-200 dark:border-gray-600">
421
- <span>{labels.total}</span><span>${total.toFixed(2)}</span>
422
- </div>
423
- {/if}
424
- </div>
425
- </div>
426
- {/if}
427
-
428
- {#if showFooter}
429
- <div
430
- transition:fly={{ y: 100, duration: 200 }}
431
- class="min-[872px]:hidden fixed bottom-0 left-0 right-0 z-40 bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-600 shadow-xl p-4 pb-[max(16px,calc(env(safe-area-inset-bottom)+8px))] select-none touch-manipulation"
432
- >
433
- <div class="flex items-stretch gap-3">
434
- <button class="flex flex-col justify-between items-start shrink-0 whitespace-nowrap bg-transparent border-none p-0 cursor-pointer touch-manipulation" onclick={makeOrderSummaryVisible}>
435
- <span class="{`${typography.sm} flex items-center gap-1 text-gray-600 dark:text-gray-300`}">
436
- {totalQuantity} {totalQuantity > 1 ? labels.ticketPlural : labels.ticketSingular}
437
- <ChevronDownOutline class={`w-4 h-4 ${typography.iconMuted} transition-transform duration-200 ${showOrderSummaryOnMobile ? 'rotate-180' : ''}`} />
438
- </span>
439
- <span class="{typography.h2} text-xl">${total.toFixed(2)}</span>
440
- </button>
441
-
442
- <button
443
- class="{`${typography.label} flex-1 min-w-36 h-12 rounded-lg touch-manipulation flex items-center justify-center ${totalQuantity === 0 || !isAgreed ? 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed' : 'bg-blue-700 dark:bg-blue-600 text-white hover:bg-blue-800 dark:hover:bg-blue-700'}`}"
444
- onclick={() => {
445
- if (executePurchase) {
446
- executePurchase(elements);
447
- } else if (checkoutTicket) {
448
- checkoutTicket();
449
- }
450
- }}
451
- disabled={totalQuantity === 0 || !isAgreed}
452
- >
453
- {#if loading}
454
- <Spinner size="sm" color="white" />
455
- {:else}
456
- <span translate="no">{labels.checkout}</span>
457
- {/if}
458
- </button>
459
- </div>
460
- </div>
461
- {/if}
1
+ <script>
2
+ import { fly, fade } from 'svelte/transition';
3
+ import { cubicOut } from 'svelte/easing';
4
+ import { ChevronDownOutline, CloseOutline } from '../../primitives/Icons';
5
+ import Spinner from '../../primitives/Spinner/Spinner.svelte';
6
+ import { typography } from '../../tokens/typography';
7
+ import { roundCurrency } from '../../utils/formatters';
8
+
9
+ /**
10
+ * Default labels for all user-visible strings in OrderSummary.
11
+ * Consumers can override any string via the `labels` prop.
12
+ */
13
+ const defaultLabels = {
14
+ orderSummary: 'Order summary',
15
+ selectTickets: 'Select tickets to continue',
16
+ free: 'Free',
17
+ fullPrice: 'Full Price',
18
+ discount: 'Discount',
19
+ subtotal: 'Subtotal',
20
+ fees: 'Fees',
21
+ taxes: 'Taxes',
22
+ giftCardAppliedLabel: 'Gift Card Applied',
23
+ giftCardBalanceAfterOrder: 'Gift card balance after order',
24
+ amountDue: 'Amount Due',
25
+ fullyCoveredByGiftCard: 'Fully covered by gift card',
26
+ total: 'Total',
27
+ checkoutButtonText: 'Checkout',
28
+ placeOrderButtonText: 'Place order',
29
+ placeOrderTos: 'By selecting Place order, I agree to the',
30
+ termsOfService: 'terms of service',
31
+ checkout: 'Checkout',
32
+ ticketSingular: 'ticket',
33
+ ticketPlural: 'tickets',
34
+ closeOrderSummary: 'Close order summary',
35
+ };
36
+
37
+ let {
38
+ loading = false,
39
+ quantities = {},
40
+ donationAmounts = {}, // Map of ticketId -> donation amount string
41
+ eventTickets = [],
42
+ checkoutTicket = null,
43
+ isAgreed = true,
44
+ btnText = undefined,
45
+ showTerms = false,
46
+ promoApplied = false,
47
+ promoDiscount = 0,
48
+ currentPromoRule = null,
49
+ executePurchase = null,
50
+ elements = null,
51
+ venueServiceCharge = {
52
+ serviceFeeCents: 0,
53
+ serviceFeePercentage: 0,
54
+ serviceFeeChargeType: 'both',
55
+ maxServiceFeeCents: 0,
56
+ taxPercentage: 0,
57
+ },
58
+ onPriceUpdate,
59
+ giftCardApplied = null, // { code, giftCardAmount, giftCardBalance, stripeAmount, paymentType, requiresStripe }
60
+ /** @type {Partial<typeof defaultLabels>} Override any user-visible string */
61
+ labels: userLabels = {},
62
+ } = $props();
63
+
64
+ let labels = $derived({ ...defaultLabels, ...userLabels });
65
+
66
+ // Helper to get effective price for a ticket (handles donation tickets)
67
+ function getEffectivePrice(ticket) {
68
+ // Donation ticket (type 2): use user-entered donation amount
69
+ if (isDonationTicket(ticket)) {
70
+ const donationAmount = donationAmounts[ticket.ID];
71
+ return parseFloat(donationAmount) || 0;
72
+ }
73
+ // Regular ticket: use ticket price
74
+ return parseFloat(ticket.price) || 0;
75
+ }
76
+
77
+ // Check if ticket is a donation ticket
78
+ // Handle both 'type' and 'ticketType' fields, and coerce to number for comparison
79
+ function isDonationTicket(ticket) {
80
+ const ticketType = ticket.type ?? ticket.ticketType ?? 0;
81
+ return Number(ticketType) === 2;
82
+ }
83
+
84
+ let showOrderSummaryOnMobile = $state(false);
85
+
86
+ function feeFor(price) {
87
+ const chargeType = venueServiceCharge.serviceFeeChargeType || 'both';
88
+ let fee = 0;
89
+
90
+ if (chargeType === 'flat' || chargeType === 'both') {
91
+ fee += (venueServiceCharge.serviceFeeCents || 0) / 100;
92
+ }
93
+ if (chargeType === 'percent' || chargeType === 'both') {
94
+ fee += ((venueServiceCharge.serviceFeePercentage || 0) / 100) * price;
95
+ }
96
+
97
+ const maxFee = venueServiceCharge.maxServiceFeeCents || 0;
98
+ if (maxFee > 0 && fee > maxFee / 100) {
99
+ fee = maxFee / 100;
100
+ }
101
+
102
+ return roundCurrency(fee);
103
+ }
104
+
105
+ function makeOrderSummaryVisible() {
106
+ showOrderSummaryOnMobile = !showOrderSummaryOnMobile;
107
+ }
108
+
109
+ function getDiscountedPrice(ticket) {
110
+ if (!currentPromoRule?.provideDiscount) return null;
111
+
112
+ const discountTicketIds = currentPromoRule.discountTicketIds || [];
113
+ if (discountTicketIds.length > 0 && !discountTicketIds.includes(ticket.ID)) {
114
+ return null;
115
+ }
116
+
117
+ const basePrice = parseFloat(ticket.price) || 0;
118
+ const discountAmount = parseFloat(currentPromoRule?.amount || '0') || 0;
119
+ if (currentPromoRule.discountType === '%') {
120
+ return (basePrice * (1 - discountAmount / 100)).toFixed(2);
121
+ } else if (currentPromoRule.discountType === '$') {
122
+ return Math.max(0, basePrice - discountAmount).toFixed(2);
123
+ }
124
+ return null;
125
+ }
126
+
127
+ let totalQuantity = $derived(Object.values(quantities).reduce((sum, q) => sum + q, 0));
128
+ let showFooter = $derived(totalQuantity > 0);
129
+
130
+ let subtotalWithoutDiscount = $derived(roundCurrency(Object.keys(quantities).reduce((acc, key) => {
131
+ const ticket = eventTickets.find(t => t.ID == key);
132
+ if (!ticket) return acc;
133
+ // For donation tickets, use the donation amount; otherwise use ticket price
134
+ const effectivePrice = getEffectivePrice(ticket);
135
+ return acc + quantities[key] * effectivePrice;
136
+ }, 0)));
137
+
138
+ let subtotal = $derived(roundCurrency(Object.keys(quantities).reduce((acc, key) => {
139
+ const ticket = eventTickets.find(t => t.ID == key);
140
+ if (!ticket) return acc;
141
+ // For donation tickets, use the donation amount (no discounts apply)
142
+ if (isDonationTicket(ticket)) {
143
+ const effectivePrice = getEffectivePrice(ticket);
144
+ return acc + quantities[key] * effectivePrice;
145
+ }
146
+ // For regular tickets, apply discounts as usual
147
+ const discountedPrice = getDiscountedPrice(ticket);
148
+ const priceToUse = discountedPrice !== null ? parseFloat(discountedPrice) : ticket.price;
149
+ return acc + quantities[key] * priceToUse;
150
+ }, 0)));
151
+
152
+ let promoSavings = $derived(currentPromoRule?.provideDiscount ? (subtotalWithoutDiscount - subtotal) : 0);
153
+
154
+ let fees = $derived(roundCurrency(Object.keys(quantities).reduce((acc, key) => {
155
+ const ticket = eventTickets.find(t => t.ID == key);
156
+ // Skip fees for: no ticket, free tickets, and donation tickets (type 2)
157
+ if (!ticket || ticket.price == 0 || isDonationTicket(ticket)) return acc;
158
+ const discountedPrice = getDiscountedPrice(ticket);
159
+ const priceToUse = discountedPrice !== null ? parseFloat(discountedPrice) : ticket.price;
160
+ return acc + quantities[key] * feeFor(priceToUse);
161
+ }, 0)));
162
+
163
+ let taxRate = $derived((venueServiceCharge.taxPercentage || 0) / 100);
164
+ let taxes = $derived(subtotal > 0 ? roundCurrency(subtotal * taxRate) : 0);
165
+
166
+ // Gift card derived values
167
+ let giftCardAmountDisplay = $derived(
168
+ giftCardApplied ? (giftCardApplied.giftCardAmount / 100).toFixed(2) : null
169
+ );
170
+ let giftCardRemainingBalance = $derived(
171
+ giftCardApplied ? ((giftCardApplied.giftCardBalance - giftCardApplied.giftCardAmount) / 100).toFixed(2) : null
172
+ );
173
+
174
+ // Total accounts for gift card deduction
175
+ let total = $derived(
176
+ roundCurrency(Math.max(0, subtotal + fees + taxes
177
+ - (promoApplied && !currentPromoRule?.provideDiscount ? promoDiscount : 0)
178
+ - (giftCardApplied ? giftCardApplied.giftCardAmount / 100 : 0)
179
+ ))
180
+ );
181
+
182
+ $effect(() => {
183
+ onPriceUpdate?.({ subtotal, fees, taxes, total, promoSavings });
184
+ });
185
+ </script>
186
+
187
+ <div
188
+ id="orderSummary"
189
+ class="hidden min-[872px]:block h-fit rounded-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600"
190
+ >
191
+ <div class="px-5 py-4 border-b border-gray-200 dark:border-gray-600">
192
+ <h3 class={typography.h3}>
193
+ {labels.orderSummary}
194
+ </h3>
195
+ </div>
196
+
197
+ <div class="px-5 pb-5">
198
+ {#if totalQuantity === 0}
199
+ <div class="py-8 text-center">
200
+ <p class="{typography.smMuted}">{labels.selectTickets}</p>
201
+ </div>
202
+ {:else}
203
+ {#each Object.keys(quantities) as key}
204
+ {#if quantities[key] > 0}
205
+ {#each eventTickets as ticket}
206
+ {#if ticket.ID == key}
207
+ {@const effectivePrice = getEffectivePrice(ticket)}
208
+ {@const isDonation = isDonationTicket(ticket)}
209
+ <div class="flex justify-between py-3 border-b border-gray-200/50 dark:border-gray-600/50">
210
+ <div>
211
+ <p class="{typography.label}">{ticket.name}</p>
212
+ <p class="{typography.smMuted}">
213
+ {#if ticket.price === 0 && !isDonation}
214
+ {labels.free} x {quantities[key]}
215
+ {:else if isDonation}
216
+ ${effectivePrice.toFixed(2)} x {quantities[key]}
217
+ {:else}
218
+ ${ticket.price.toFixed(2)} x {quantities[key]}
219
+ {/if}
220
+ </p>
221
+ </div>
222
+ <div class="{typography.label}">
223
+ {#if ticket.price === 0 && !isDonation}
224
+ {labels.free}
225
+ {:else}
226
+ ${(effectivePrice * quantities[key]).toFixed(2)}
227
+ {/if}
228
+ </div>
229
+ </div>
230
+ {/if}
231
+ {/each}
232
+ {/if}
233
+ {/each}
234
+
235
+ <div class="{`${typography.sm} flex flex-col py-3 gap-2`}">
236
+ {#if promoSavings > 0 || (promoDiscount > 0 && !currentPromoRule?.provideDiscount)}
237
+ <div class="flex justify-between text-gray-600 dark:text-gray-300">
238
+ <span>{labels.fullPrice}</span><span>${subtotalWithoutDiscount.toFixed(2)}</span>
239
+ </div>
240
+ {/if}
241
+ {#if promoSavings > 0}
242
+ <div class="flex justify-between text-green-600 dark:text-green-500">
243
+ <span>{labels.discount}</span><span>-${promoSavings.toFixed(2)}</span>
244
+ </div>
245
+ {:else if promoDiscount > 0 && !currentPromoRule?.provideDiscount}
246
+ <div class="flex justify-between text-green-600 dark:text-green-500">
247
+ <span>{labels.discount}</span><span>-${promoDiscount.toFixed(2)}</span>
248
+ </div>
249
+ {/if}
250
+ <div class="flex justify-between text-gray-600 dark:text-gray-300">
251
+ <span>{labels.subtotal}</span><span>${subtotal.toFixed(2)}</span>
252
+ </div>
253
+ <div class="flex justify-between text-gray-600 dark:text-gray-300">
254
+ <span>{labels.fees}</span><span>${fees.toFixed(2)}</span>
255
+ </div>
256
+ <div class="flex justify-between text-gray-600 dark:text-gray-300">
257
+ <span>{labels.taxes}</span><span>${taxes.toFixed(2)}</span>
258
+ </div>
259
+ {#if giftCardApplied}
260
+ <div class="flex justify-between text-green-600 dark:text-green-500">
261
+ <span>{labels.giftCardAppliedLabel}</span>
262
+ <span>-${giftCardAmountDisplay}</span>
263
+ </div>
264
+ {#if giftCardRemainingBalance && parseFloat(giftCardRemainingBalance) > 0}
265
+ <div class="flex justify-between text-gray-500 dark:text-gray-400 text-xs">
266
+ <span>{labels.giftCardBalanceAfterOrder}</span>
267
+ <span>${giftCardRemainingBalance}</span>
268
+ </div>
269
+ {/if}
270
+ {/if}
271
+ </div>
272
+
273
+ {#if giftCardApplied?.paymentType === 'gift_card_only'}
274
+ <div class="flex justify-between py-4 text-lg border-t border-gray-200 dark:border-gray-600">
275
+ <span class="font-semibold text-gray-900 dark:text-white">{labels.amountDue}</span>
276
+ <span class="font-bold text-green-600 dark:text-green-400">$0.00</span>
277
+ </div>
278
+ <p class="text-xs text-gray-500 dark:text-gray-400 text-center -mt-2 mb-2">
279
+ {labels.fullyCoveredByGiftCard}
280
+ </p>
281
+ {:else}
282
+ <div class="flex justify-between {typography.h3} py-4 text-lg border-t border-gray-200 dark:border-gray-600">
283
+ <span>{labels.total}</span><span>${total.toFixed(2)}</span>
284
+ </div>
285
+ {/if}
286
+ {/if}
287
+
288
+ {#if totalQuantity > 0 && showTerms}
289
+ <p class="{typography.xsMuted} text-center mb-3">
290
+ {labels.placeOrderTos} <a href="https://get-micdrop.com/tos" class="text-blue-700 dark:text-blue-500 underline hover:opacity-80" target="_blank" rel="noopener noreferrer">{labels.termsOfService}</a>
291
+ </p>
292
+ {/if}
293
+
294
+ <button
295
+ class="w-full h-12 font-semibold rounded-lg flex items-center justify-center transition-colors select-none touch-manipulation {totalQuantity === 0 ? 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed' : 'bg-blue-700 dark:bg-blue-600 text-white hover:bg-blue-800 dark:hover:bg-blue-700'}"
296
+ onclick={() => {
297
+ if (totalQuantity === 0) return;
298
+ if (executePurchase) {
299
+ executePurchase(elements);
300
+ } else if (checkoutTicket) {
301
+ checkoutTicket();
302
+ }
303
+ }}
304
+ disabled={totalQuantity === 0}
305
+ >
306
+ {#if loading}
307
+ <Spinner size="sm" color="white" />
308
+ {:else}
309
+ <span translate="no">{btnText ?? (showTerms ? labels.placeOrderButtonText : labels.checkoutButtonText)}</span>
310
+ {/if}
311
+ </button>
312
+ </div>
313
+ </div>
314
+
315
+ {#if showOrderSummaryOnMobile}
316
+ <button
317
+ class="min-[872px]:hidden fixed inset-0 bg-black/50 z-40 border-none cursor-pointer"
318
+ onclick={() => (showOrderSummaryOnMobile = false)}
319
+ aria-label={labels.closeOrderSummary}
320
+ transition:fade={{ duration: 200 }}
321
+ ></button>
322
+ <div
323
+ in:fly={{ y: 800, duration: 300, easing: cubicOut }}
324
+ out:fly={{ y: 800, duration: 300, easing: cubicOut }}
325
+ class="min-[872px]:hidden fixed bottom-0 left-0 w-full overflow-x-hidden overflow-y-auto z-50 max-h-[80vh] rounded-t-lg shadow-xl bg-white dark:bg-gray-800"
326
+ >
327
+ <div class="flex flex-row justify-between items-center px-5 py-4 border-b border-gray-200 dark:border-gray-600">
328
+ <h2 class="{typography.h2} text-xl">
329
+ {labels.orderSummary}
330
+ </h2>
331
+ <button
332
+ onclick={() => (showOrderSummaryOnMobile = false)}
333
+ class="transition-colors p-2 rounded-lg text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-700"
334
+ aria-label={labels.closeOrderSummary}
335
+ >
336
+ <CloseOutline class="w-7 h-7" />
337
+ </button>
338
+ </div>
339
+
340
+ <div class="px-5 pb-5">
341
+ {#each Object.keys(quantities) as key}
342
+ {#if quantities[key] > 0}
343
+ {#each eventTickets as ticket}
344
+ {#if ticket.ID == key}
345
+ {@const effectivePrice = getEffectivePrice(ticket)}
346
+ {@const isDonation = isDonationTicket(ticket)}
347
+ <div class="flex justify-between py-4 border-b border-gray-200 dark:border-gray-600">
348
+ <div>
349
+ <p class="{typography.h5}">{ticket.name}</p>
350
+ <p class="{typography.smMuted}">
351
+ {#if ticket.price === 0 && !isDonation}
352
+ {labels.free} x {quantities[key]}
353
+ {:else if isDonation}
354
+ ${effectivePrice.toFixed(2)} x {quantities[key]}
355
+ {:else}
356
+ ${ticket.price.toFixed(2)} x {quantities[key]}
357
+ {/if}
358
+ </p>
359
+ </div>
360
+ <div class="{typography.h5}">
361
+ {#if ticket.price === 0 && !isDonation}
362
+ {labels.free}
363
+ {:else}
364
+ ${(effectivePrice * quantities[key]).toFixed(2)}
365
+ {/if}
366
+ </div>
367
+ </div>
368
+ {/if}
369
+ {/each}
370
+ {/if}
371
+ {/each}
372
+
373
+ <div class="flex flex-col py-4 gap-3 text-gray-600 dark:text-gray-300">
374
+ {#if promoSavings > 0 || (promoDiscount > 0 && !currentPromoRule?.provideDiscount)}
375
+ <div class="flex justify-between">
376
+ <span>{labels.fullPrice}</span><span>${subtotalWithoutDiscount.toFixed(2)}</span>
377
+ </div>
378
+ {/if}
379
+ {#if promoSavings > 0}
380
+ <div class="flex justify-between text-green-600 dark:text-green-500">
381
+ <span>{labels.discount}</span><span>-${promoSavings.toFixed(2)}</span>
382
+ </div>
383
+ {:else if promoDiscount > 0 && !currentPromoRule?.provideDiscount}
384
+ <div class="flex justify-between text-green-600 dark:text-green-500">
385
+ <span>{labels.discount}</span><span>-${promoDiscount.toFixed(2)}</span>
386
+ </div>
387
+ {/if}
388
+ <div class="flex justify-between">
389
+ <span>{labels.subtotal}</span><span>${subtotal.toFixed(2)}</span>
390
+ </div>
391
+ <div class="flex justify-between">
392
+ <span>{labels.fees}</span><span>${fees.toFixed(2)}</span>
393
+ </div>
394
+ <div class="flex justify-between">
395
+ <span>{labels.taxes}</span><span>${taxes.toFixed(2)}</span>
396
+ </div>
397
+ {#if giftCardApplied}
398
+ <div class="flex justify-between text-green-600 dark:text-green-500">
399
+ <span>{labels.giftCardAppliedLabel}</span>
400
+ <span>-${giftCardAmountDisplay}</span>
401
+ </div>
402
+ {#if giftCardRemainingBalance && parseFloat(giftCardRemainingBalance) > 0}
403
+ <div class="flex justify-between text-gray-500 dark:text-gray-400 text-xs">
404
+ <span>{labels.giftCardBalanceAfterOrder}</span>
405
+ <span>${giftCardRemainingBalance}</span>
406
+ </div>
407
+ {/if}
408
+ {/if}
409
+ </div>
410
+
411
+ {#if giftCardApplied?.paymentType === 'gift_card_only'}
412
+ <div class="flex justify-between py-5 border-t border-gray-200 dark:border-gray-600">
413
+ <span class="font-semibold text-gray-900 dark:text-white">{labels.amountDue}</span>
414
+ <span class="font-bold text-green-600 dark:text-green-400">$0.00</span>
415
+ </div>
416
+ <p class="text-xs text-gray-500 dark:text-gray-400 text-center -mt-2 mb-2">
417
+ {labels.fullyCoveredByGiftCard}
418
+ </p>
419
+ {:else}
420
+ <div class="flex justify-between {typography.h3} py-5 border-t border-gray-200 dark:border-gray-600">
421
+ <span>{labels.total}</span><span>${total.toFixed(2)}</span>
422
+ </div>
423
+ {/if}
424
+ </div>
425
+ </div>
426
+ {/if}
427
+
428
+ {#if showFooter}
429
+ <div
430
+ transition:fly={{ y: 100, duration: 200 }}
431
+ class="min-[872px]:hidden fixed bottom-0 left-0 right-0 z-40 bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-600 shadow-xl p-4 pb-[max(16px,calc(env(safe-area-inset-bottom)+8px))] select-none touch-manipulation"
432
+ >
433
+ <div class="flex items-stretch gap-3">
434
+ <button class="flex flex-col justify-between items-start shrink-0 whitespace-nowrap bg-transparent border-none p-0 cursor-pointer touch-manipulation" onclick={makeOrderSummaryVisible}>
435
+ <span class="{`${typography.sm} flex items-center gap-1 text-gray-600 dark:text-gray-300`}">
436
+ {totalQuantity} {totalQuantity > 1 ? labels.ticketPlural : labels.ticketSingular}
437
+ <ChevronDownOutline class={`w-4 h-4 ${typography.iconMuted} transition-transform duration-200 ${showOrderSummaryOnMobile ? 'rotate-180' : ''}`} />
438
+ </span>
439
+ <span class="{typography.h2} text-xl">${total.toFixed(2)}</span>
440
+ </button>
441
+
442
+ <button
443
+ class="{`${typography.label} flex-1 min-w-36 h-12 rounded-lg touch-manipulation flex items-center justify-center ${totalQuantity === 0 || !isAgreed ? 'bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400 cursor-not-allowed' : 'bg-blue-700 dark:bg-blue-600 text-white hover:bg-blue-800 dark:hover:bg-blue-700'}`}"
444
+ onclick={() => {
445
+ if (executePurchase) {
446
+ executePurchase(elements);
447
+ } else if (checkoutTicket) {
448
+ checkoutTicket();
449
+ }
450
+ }}
451
+ disabled={totalQuantity === 0 || !isAgreed}
452
+ >
453
+ {#if loading}
454
+ <Spinner size="sm" color="white" />
455
+ {:else}
456
+ <span translate="no">{labels.checkout}</span>
457
+ {/if}
458
+ </button>
459
+ </div>
460
+ </div>
461
+ {/if}