@behindthemusictree/app-kit-ui 37.0.0 → 38.0.0

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.
@@ -200,7 +200,7 @@ function Toast({ message, onClose, durationMs = 2200 }) {
200
200
  role: "status",
201
201
  "aria-live": "polite",
202
202
  "aria-atomic": "true",
203
- className: "pointer-events-auto w-full max-w-md rounded-md bg-[var(--color-surface)] px-4 py-4 text-sm text-[var(--color-text)] shadow-lg ring-1 ring-black/10",
203
+ className: "pointer-events-auto w-full max-w-md rounded-md bg-[var(--color-surface)] px-4 py-4 text-body text-[var(--color-text)] shadow-lg ring-1 ring-black/10",
204
204
  children: message
205
205
  }
206
206
  ) }),
@@ -226,7 +226,7 @@ function InternalErrorPopup({ errorCode, contactEmail, ...rest }) {
226
226
  " ",
227
227
  /* @__PURE__ */ jsx5("a", { href: `mailto:${contactEmail}`, className: "text-[var(--color-primary)] hover:text-[var(--color-primary-hover)]", children: contactEmail })
228
228
  ] }) }),
229
- errorCode && /* @__PURE__ */ jsxs3("div", { className: "text-sm text-[var(--color-neutral-500)] bg-[var(--color-neutral-100)] p-3 rounded-lg border border-[var(--color-neutral-100)]", children: [
229
+ errorCode && /* @__PURE__ */ jsxs3("div", { className: "text-body text-[var(--color-neutral-500)] bg-[var(--color-neutral-100)] p-3 rounded-lg border border-[var(--color-neutral-100)]", children: [
230
230
  "Error Code: ",
231
231
  errorCode
232
232
  ] })
@@ -243,4 +243,4 @@ export {
243
243
  Toast,
244
244
  InternalErrorPopup
245
245
  };
246
- //# sourceMappingURL=chunk-DKQQLLMY.js.map
246
+ //# sourceMappingURL=chunk-2GH7CSCE.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/popup/PopupTitle.tsx","../src/popup/PopupButtons.tsx","../src/popup/BasePopup.tsx","../src/popup/Toast.tsx","../src/popup/InternalErrorPopup.tsx"],"sourcesContent":["\"use client\";\n\nimport { X } from \"lucide-react\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { LucideIcon } from \"lucide-react\";\n\ninterface PopupTitleProps {\n title: string;\n onClose: () => void;\n isDismissable?: boolean;\n icon?: LucideIcon;\n className?: string;\n titleClassName?: string;\n closeButtonClassName?: string;\n}\n\nexport function PopupTitle({\n title,\n onClose,\n isDismissable,\n icon: Icon,\n className = \"\",\n titleClassName = \"\",\n closeButtonClassName = \"\",\n}: PopupTitleProps) {\n return (\n <div className={`popup-title flex items-center justify-between bg-black p-4 rounded-t-lg ${className}`}>\n <div className=\"flex items-center gap-2\">\n {Icon && <Icon className=\"h-5 w-5 text-white\" />}\n <h3 id=\"popup-title\" className={`text-white font-semibold ${titleClassName}`}>\n {title}\n </h3>\n </div>\n {isDismissable && (\n <Button\n onClick={onClose}\n className={`h-8 w-8 text-white hover:text-white/80 ${closeButtonClassName}`}\n aria-label=\"Close popup\"\n >\n <X className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { ReactNode } from \"react\";\n\ninterface PopupButtonsProps {\n children: ReactNode;\n alignment?: \"left\" | \"center\" | \"right\";\n className?: string;\n}\n\nexport function PopupButtons({ children, alignment = \"center\", className = \"\" }: PopupButtonsProps) {\n const alignmentClasses = {\n left: \"justify-start\",\n center: \"justify-center\",\n right: \"justify-end\",\n };\n\n return <div className={`flex gap-3 ${alignmentClasses[alignment]} ${className}`}>{children}</div>;\n}\n","\"use client\";\n\nimport { createPortal } from \"react-dom\";\nimport { ReactNode, useEffect, useRef } from \"react\";\nimport { PopupTitle } from \"./PopupTitle\";\nimport { PopupButtons } from \"./PopupButtons\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { LucideIcon } from \"lucide-react\";\n\nexport interface BasePopupProps {\n title: string;\n children: ReactNode;\n onClose?: () => void;\n type?: \"default\" | \"success\" | \"error\" | \"warning\" | \"info\" | \"spotify\" | \"auth\";\n icon?: LucideIcon;\n isDismissable?: boolean;\n className?: string;\n overlayClassName?: string;\n titleClassName?: string;\n titleTextClassName?: string;\n closeButtonClassName?: string;\n /** Sizes the popup to its content (up to the type's max width) instead of filling it. */\n fitContent?: boolean;\n contentClassName?: string;\n showOkButton?: boolean;\n showCancelButton?: boolean;\n okButtonText?: string;\n okButtonDisabled?: boolean;\n cancelButtonText?: string;\n onOk?: () => void;\n onCancel?: () => void;\n buttonAlignment?: \"left\" | \"center\" | \"right\";\n /**\n * Distance (in px) reserved at the top of the viewport, e.g. for a fixed app header/banner.\n * Each consuming app passes its own value. Defaults to 0.\n */\n topOffset?: number;\n /** Keeps keyboard focus within the popup and restores it to the opener when dismissed. */\n trapFocus?: boolean;\n /** Closes a dismissable popup on Escape. */\n closeOnEscape?: boolean;\n /** Closes a dismissable popup when the backdrop is clicked. */\n closeOnBackdrop?: boolean;\n}\n\nconst typeClasses: Record<string, string> = {\n default: \"w-full max-w-lg\",\n success: \"w-full max-w-lg\",\n error: \"w-full max-w-lg\",\n warning: \"w-full max-w-lg\",\n info: \"w-full max-w-lg\",\n spotify: \"w-full max-w-md\",\n auth: \"w-full max-w-md\",\n};\n\nconst contentBgClasses: Record<string, string> = {\n spotify: \"bg-green-500\",\n auth: \"bg-gray-800\",\n};\n\nexport function BasePopup(props: BasePopupProps) {\n const {\n title,\n children,\n onClose,\n type = \"default\",\n icon,\n isDismissable = true,\n className,\n overlayClassName = \"\",\n titleClassName = \"\",\n titleTextClassName = \"\",\n closeButtonClassName = \"\",\n fitContent = false,\n contentClassName = \"\",\n showOkButton = false,\n showCancelButton = false,\n okButtonText = \"OK\",\n cancelButtonText = \"Cancel\",\n onOk,\n okButtonDisabled = false,\n onCancel,\n buttonAlignment = \"center\",\n topOffset = 0,\n trapFocus = false,\n closeOnEscape = false,\n closeOnBackdrop = false,\n } = props;\n const dialogRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n const dialog = dialogRef.current;\n const focusableSelector =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n const getFocusable = () =>\n Array.from(dialog?.querySelectorAll<HTMLElement>(focusableSelector) ?? []).filter(\n (element) => !element.hasAttribute(\"hidden\") && element.getAttribute(\"aria-hidden\") !== \"true\",\n );\n\n if (trapFocus) (getFocusable()[0] ?? dialog)?.focus();\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && closeOnEscape && isDismissable) {\n event.preventDefault();\n onClose?.();\n return;\n }\n if (!trapFocus || event.key !== \"Tab\") return;\n\n const focusable = getFocusable();\n if (focusable.length === 0) {\n event.preventDefault();\n dialog?.focus();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (event.shiftKey && (document.activeElement === first || !dialog?.contains(document.activeElement))) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (document.activeElement === last || !dialog?.contains(document.activeElement))) {\n event.preventDefault();\n first.focus();\n }\n };\n\n if (trapFocus || closeOnEscape) document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n if (trapFocus || closeOnEscape) document.removeEventListener(\"keydown\", handleKeyDown);\n if (trapFocus && previousFocus?.isConnected) previousFocus.focus();\n };\n }, [closeOnEscape, isDismissable, onClose, trapFocus]);\n\n const baseClasses = \"bg-white rounded-lg\";\n\n const renderButtons = () => {\n if (!showOkButton && !showCancelButton) return null;\n\n return (\n <div className=\"mt-4 pt-4 border-t\">\n <PopupButtons alignment={buttonAlignment}>\n {showCancelButton && (\n <Button onClick={onCancel || onClose || (() => {})} variant=\"secondary\">\n {cancelButtonText}\n </Button>\n )}\n {showOkButton && (\n <Button onClick={onOk || onClose || (() => {})} disabled={okButtonDisabled}>\n {okButtonText}\n </Button>\n )}\n </PopupButtons>\n </div>\n );\n };\n\n const popupComponent = (\n <div\n className={`fixed z-50 flex items-center justify-center bg-transparent ${overlayClassName}`}\n style={{\n top: topOffset,\n left: 0,\n right: 0,\n bottom: 0,\n }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"popup-title\"\n onClick={closeOnBackdrop && isDismissable ? onClose : undefined}\n >\n <div\n ref={dialogRef}\n tabIndex={trapFocus ? -1 : undefined}\n className={`${baseClasses} ${fitContent ? typeClasses[type].replace(\"w-full\", \"w-fit\") : typeClasses[type]} ${className || \"\"}`}\n onClick={(e) => e.stopPropagation()}\n >\n <PopupTitle\n title={title}\n onClose={onClose ?? (() => {})}\n isDismissable={isDismissable}\n icon={icon}\n className={titleClassName}\n titleClassName={titleTextClassName}\n closeButtonClassName={closeButtonClassName}\n />\n <div className={`popup-content rounded-b-lg p-4 ${contentBgClasses[type] ?? \"\"} ${contentClassName}`}>\n {children}\n {renderButtons()}\n </div>\n </div>\n </div>\n );\n\n return createPortal(popupComponent, document.body);\n}\n\nexport default BasePopup;\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\nexport type ToastProps = {\n message: string;\n onClose: () => void;\n durationMs?: number;\n};\n\nexport default function Toast({ message, onClose, durationMs = 2200 }: ToastProps) {\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n const timeout = window.setTimeout(onClose, durationMs);\n return () => window.clearTimeout(timeout);\n }, [durationMs, onClose]);\n\n if (!mounted) return null;\n\n return createPortal(\n <div className=\"pointer-events-none fixed inset-x-0 bottom-8 z-[60] flex justify-center px-4\">\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className=\"pointer-events-auto w-full max-w-md rounded-md bg-[var(--color-surface)] px-4 py-4 text-sm text-[var(--color-text)] shadow-lg ring-1 ring-black/10\"\n >\n {message}\n </div>\n </div>,\n document.body,\n );\n}\n","\"use client\";\n\nimport { AlertTriangle, AlertCircle } from \"lucide-react\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\n\ntype InternalErrorPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n errorCode: ErrorCode;\n /**\n * Support contact email shown in the \"if the problem persists\" message. Passed by the\n * consuming app (from whatever env var / config it uses) so this package doesn't assume a\n * specific env var name.\n */\n contactEmail?: string | null;\n};\n\nexport default function InternalErrorPopup({ errorCode, contactEmail, ...rest }: InternalErrorPopupProps) {\n return (\n <BasePopup\n {...rest}\n title=\"Internal Error\"\n isDismissable={false}\n icon={AlertTriangle}\n children={\n <div className=\"flex flex-col items-center space-y-6 py-4\">\n <AlertCircle className=\"h-16 w-16 text-[var(--color-red-500)]\" strokeWidth={1.5} />\n <div>\n <p className=\"text-center text-[var(--color-neutral-600)]\">\n Please try again. If the problem persists, contact us at{\" \"}\n <a href={`mailto:${contactEmail}`} className=\"text-[var(--color-primary)] hover:text-[var(--color-primary-hover)]\">\n {contactEmail}\n </a>\n </p>\n </div>\n {errorCode && (\n <div className=\"text-sm text-[var(--color-neutral-500)] bg-[var(--color-neutral-100)] p-3 rounded-lg border border-[var(--color-neutral-100)]\">\n Error Code: {errorCode}\n </div>\n )}\n </div>\n }\n />\n );\n}\n"],"mappings":"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","names":["jsx","Button","jsx","jsxs","useEffect","createPortal","jsx","jsx","jsxs"]}
1
+ {"version":3,"sources":["../src/popup/PopupTitle.tsx","../src/popup/PopupButtons.tsx","../src/popup/BasePopup.tsx","../src/popup/Toast.tsx","../src/popup/InternalErrorPopup.tsx"],"sourcesContent":["\"use client\";\n\nimport { X } from \"lucide-react\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { LucideIcon } from \"lucide-react\";\n\ninterface PopupTitleProps {\n title: string;\n onClose: () => void;\n isDismissable?: boolean;\n icon?: LucideIcon;\n className?: string;\n titleClassName?: string;\n closeButtonClassName?: string;\n}\n\nexport function PopupTitle({\n title,\n onClose,\n isDismissable,\n icon: Icon,\n className = \"\",\n titleClassName = \"\",\n closeButtonClassName = \"\",\n}: PopupTitleProps) {\n return (\n <div className={`popup-title flex items-center justify-between bg-black p-4 rounded-t-lg ${className}`}>\n <div className=\"flex items-center gap-2\">\n {Icon && <Icon className=\"h-5 w-5 text-white\" />}\n <h3 id=\"popup-title\" className={`text-white font-semibold ${titleClassName}`}>\n {title}\n </h3>\n </div>\n {isDismissable && (\n <Button\n onClick={onClose}\n className={`h-8 w-8 text-white hover:text-white/80 ${closeButtonClassName}`}\n aria-label=\"Close popup\"\n >\n <X className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { ReactNode } from \"react\";\n\ninterface PopupButtonsProps {\n children: ReactNode;\n alignment?: \"left\" | \"center\" | \"right\";\n className?: string;\n}\n\nexport function PopupButtons({ children, alignment = \"center\", className = \"\" }: PopupButtonsProps) {\n const alignmentClasses = {\n left: \"justify-start\",\n center: \"justify-center\",\n right: \"justify-end\",\n };\n\n return <div className={`flex gap-3 ${alignmentClasses[alignment]} ${className}`}>{children}</div>;\n}\n","\"use client\";\n\nimport { createPortal } from \"react-dom\";\nimport { ReactNode, useEffect, useRef } from \"react\";\nimport { PopupTitle } from \"./PopupTitle\";\nimport { PopupButtons } from \"./PopupButtons\";\nimport { Button } from \"@behindthemusictree/ui\";\nimport { LucideIcon } from \"lucide-react\";\n\nexport interface BasePopupProps {\n title: string;\n children: ReactNode;\n onClose?: () => void;\n type?: \"default\" | \"success\" | \"error\" | \"warning\" | \"info\" | \"spotify\" | \"auth\";\n icon?: LucideIcon;\n isDismissable?: boolean;\n className?: string;\n overlayClassName?: string;\n titleClassName?: string;\n titleTextClassName?: string;\n closeButtonClassName?: string;\n /** Sizes the popup to its content (up to the type's max width) instead of filling it. */\n fitContent?: boolean;\n contentClassName?: string;\n showOkButton?: boolean;\n showCancelButton?: boolean;\n okButtonText?: string;\n okButtonDisabled?: boolean;\n cancelButtonText?: string;\n onOk?: () => void;\n onCancel?: () => void;\n buttonAlignment?: \"left\" | \"center\" | \"right\";\n /**\n * Distance (in px) reserved at the top of the viewport, e.g. for a fixed app header/banner.\n * Each consuming app passes its own value. Defaults to 0.\n */\n topOffset?: number;\n /** Keeps keyboard focus within the popup and restores it to the opener when dismissed. */\n trapFocus?: boolean;\n /** Closes a dismissable popup on Escape. */\n closeOnEscape?: boolean;\n /** Closes a dismissable popup when the backdrop is clicked. */\n closeOnBackdrop?: boolean;\n}\n\nconst typeClasses: Record<string, string> = {\n default: \"w-full max-w-lg\",\n success: \"w-full max-w-lg\",\n error: \"w-full max-w-lg\",\n warning: \"w-full max-w-lg\",\n info: \"w-full max-w-lg\",\n spotify: \"w-full max-w-md\",\n auth: \"w-full max-w-md\",\n};\n\nconst contentBgClasses: Record<string, string> = {\n spotify: \"bg-green-500\",\n auth: \"bg-gray-800\",\n};\n\nexport function BasePopup(props: BasePopupProps) {\n const {\n title,\n children,\n onClose,\n type = \"default\",\n icon,\n isDismissable = true,\n className,\n overlayClassName = \"\",\n titleClassName = \"\",\n titleTextClassName = \"\",\n closeButtonClassName = \"\",\n fitContent = false,\n contentClassName = \"\",\n showOkButton = false,\n showCancelButton = false,\n okButtonText = \"OK\",\n cancelButtonText = \"Cancel\",\n onOk,\n okButtonDisabled = false,\n onCancel,\n buttonAlignment = \"center\",\n topOffset = 0,\n trapFocus = false,\n closeOnEscape = false,\n closeOnBackdrop = false,\n } = props;\n const dialogRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n const dialog = dialogRef.current;\n const focusableSelector =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n const getFocusable = () =>\n Array.from(dialog?.querySelectorAll<HTMLElement>(focusableSelector) ?? []).filter(\n (element) => !element.hasAttribute(\"hidden\") && element.getAttribute(\"aria-hidden\") !== \"true\",\n );\n\n if (trapFocus) (getFocusable()[0] ?? dialog)?.focus();\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && closeOnEscape && isDismissable) {\n event.preventDefault();\n onClose?.();\n return;\n }\n if (!trapFocus || event.key !== \"Tab\") return;\n\n const focusable = getFocusable();\n if (focusable.length === 0) {\n event.preventDefault();\n dialog?.focus();\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (event.shiftKey && (document.activeElement === first || !dialog?.contains(document.activeElement))) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && (document.activeElement === last || !dialog?.contains(document.activeElement))) {\n event.preventDefault();\n first.focus();\n }\n };\n\n if (trapFocus || closeOnEscape) document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n if (trapFocus || closeOnEscape) document.removeEventListener(\"keydown\", handleKeyDown);\n if (trapFocus && previousFocus?.isConnected) previousFocus.focus();\n };\n }, [closeOnEscape, isDismissable, onClose, trapFocus]);\n\n const baseClasses = \"bg-white rounded-lg\";\n\n const renderButtons = () => {\n if (!showOkButton && !showCancelButton) return null;\n\n return (\n <div className=\"mt-4 pt-4 border-t\">\n <PopupButtons alignment={buttonAlignment}>\n {showCancelButton && (\n <Button onClick={onCancel || onClose || (() => {})} variant=\"secondary\">\n {cancelButtonText}\n </Button>\n )}\n {showOkButton && (\n <Button onClick={onOk || onClose || (() => {})} disabled={okButtonDisabled}>\n {okButtonText}\n </Button>\n )}\n </PopupButtons>\n </div>\n );\n };\n\n const popupComponent = (\n <div\n className={`fixed z-50 flex items-center justify-center bg-transparent ${overlayClassName}`}\n style={{\n top: topOffset,\n left: 0,\n right: 0,\n bottom: 0,\n }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"popup-title\"\n onClick={closeOnBackdrop && isDismissable ? onClose : undefined}\n >\n <div\n ref={dialogRef}\n tabIndex={trapFocus ? -1 : undefined}\n className={`${baseClasses} ${fitContent ? typeClasses[type].replace(\"w-full\", \"w-fit\") : typeClasses[type]} ${className || \"\"}`}\n onClick={(e) => e.stopPropagation()}\n >\n <PopupTitle\n title={title}\n onClose={onClose ?? (() => {})}\n isDismissable={isDismissable}\n icon={icon}\n className={titleClassName}\n titleClassName={titleTextClassName}\n closeButtonClassName={closeButtonClassName}\n />\n <div className={`popup-content rounded-b-lg p-4 ${contentBgClasses[type] ?? \"\"} ${contentClassName}`}>\n {children}\n {renderButtons()}\n </div>\n </div>\n </div>\n );\n\n return createPortal(popupComponent, document.body);\n}\n\nexport default BasePopup;\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\nexport type ToastProps = {\n message: string;\n onClose: () => void;\n durationMs?: number;\n};\n\nexport default function Toast({ message, onClose, durationMs = 2200 }: ToastProps) {\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n setMounted(true);\n const timeout = window.setTimeout(onClose, durationMs);\n return () => window.clearTimeout(timeout);\n }, [durationMs, onClose]);\n\n if (!mounted) return null;\n\n return createPortal(\n <div className=\"pointer-events-none fixed inset-x-0 bottom-8 z-[60] flex justify-center px-4\">\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className=\"pointer-events-auto w-full max-w-md rounded-md bg-[var(--color-surface)] px-4 py-4 text-body text-[var(--color-text)] shadow-lg ring-1 ring-black/10\"\n >\n {message}\n </div>\n </div>,\n document.body,\n );\n}\n","\"use client\";\n\nimport { AlertTriangle, AlertCircle } from \"lucide-react\";\nimport { BasePopup, BasePopupProps } from \"./BasePopup\";\nimport { ErrorCode } from \"@behindthemusictree/app-kit/transport\";\n\ntype InternalErrorPopupProps = Omit<BasePopupProps, \"title\" | \"children\" | \"icon\" | \"isDismissable\"> & {\n errorCode: ErrorCode;\n /**\n * Support contact email shown in the \"if the problem persists\" message. Passed by the\n * consuming app (from whatever env var / config it uses) so this package doesn't assume a\n * specific env var name.\n */\n contactEmail?: string | null;\n};\n\nexport default function InternalErrorPopup({ errorCode, contactEmail, ...rest }: InternalErrorPopupProps) {\n return (\n <BasePopup\n {...rest}\n title=\"Internal Error\"\n isDismissable={false}\n icon={AlertTriangle}\n children={\n <div className=\"flex flex-col items-center space-y-6 py-4\">\n <AlertCircle className=\"h-16 w-16 text-[var(--color-red-500)]\" strokeWidth={1.5} />\n <div>\n <p className=\"text-center text-[var(--color-neutral-600)]\">\n Please try again. If the problem persists, contact us at{\" \"}\n <a href={`mailto:${contactEmail}`} className=\"text-[var(--color-primary)] hover:text-[var(--color-primary-hover)]\">\n {contactEmail}\n </a>\n </p>\n </div>\n {errorCode && (\n <div className=\"text-body text-[var(--color-neutral-500)] bg-[var(--color-neutral-100)] p-3 rounded-lg border border-[var(--color-neutral-100)]\">\n Error Code: {errorCode}\n </div>\n )}\n </div>\n }\n />\n );\n}\n"],"mappings":"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","names":["jsx","Button","jsx","jsxs","useEffect","createPortal","jsx","jsx","jsxs"]}