@sudobility/building_blocks 0.0.137 → 0.0.138

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.
package/dist/index.js CHANGED
@@ -1,20 +1,19 @@
1
+ import { b, a, S, u } from "./SafeSubscriptionContext-CNuEKeqJ.js";
1
2
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
3
  import React, { useState, useRef, useMemo, useCallback, useEffect } from "react";
4
+ import { SubscriptionLayout, SubscriptionTile, SegmentedControl } from "@sudobility/subscription-components";
5
+ import { useSubscriptionPeriods, useSubscriptionForPeriod, useSubscribable, useUserSubscription, refreshSubscription } from "@sudobility/subscription_lib";
3
6
  import { TopbarProvider, Topbar, TopbarLeft, TopbarNavigation, TopbarLogo, Logo, TopbarCenter, TopbarRight, TopbarActions, TopbarMobileContent, Footer, FooterCompact, FooterCompactLeft, FooterVersion, FooterCopyright, FooterCompactRight, FooterGrid, FooterLinkSection, FooterLink, FooterBottom, FooterBrand, FooterSocialLinks, LayoutProvider, AspectFitView, Label, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MasterListItem, Section, MasterDetailLayout } from "@sudobility/components";
4
7
  import { clsx } from "clsx";
5
8
  import { twMerge } from "tailwind-merge";
6
- import { ChevronDownIcon, CalendarDaysIcon, PaintBrushIcon, LanguageIcon, ChevronRightIcon, EnvelopeIcon, DocumentTextIcon, CogIcon, HomeIcon } from "@heroicons/react/24/outline";
7
- import { GRADIENT_CLASSES, textVariants } from "@sudobility/design";
8
- import { cva } from "class-variance-authority";
9
- import { createUserWithEmailAndPassword, signInWithEmailAndPassword, GoogleAuthProvider, signInWithPopup } from "firebase/auth";
10
- import { b, a, S, u } from "./SafeSubscriptionContext-CNuEKeqJ.js";
11
- import { SubscriptionLayout, SubscriptionTile, SegmentedControl } from "@sudobility/subscription-components";
12
- import { useSubscriptionPeriods, useSubscriptionForPeriod, useSubscribable, useUserSubscription, refreshSubscription } from "@sudobility/subscription_lib";
13
9
  import i18n from "i18next";
14
10
  import { default as default2 } from "i18next";
15
11
  import { initReactI18next } from "react-i18next";
16
12
  import Backend from "i18next-http-backend";
17
13
  import LanguageDetector from "i18next-browser-languagedetector";
14
+ import { ChevronDownIcon, CalendarDaysIcon, PaintBrushIcon, LanguageIcon, ChevronRightIcon, EnvelopeIcon, DocumentTextIcon, CogIcon, HomeIcon } from "@heroicons/react/24/outline";
15
+ import { GRADIENT_CLASSES, textVariants } from "@sudobility/design";
16
+ import { cva } from "class-variance-authority";
18
17
  function cn(...inputs) {
19
18
  return twMerge(clsx(inputs));
20
19
  }
@@ -223,7 +222,7 @@ const LanguageSelector = ({
223
222
  helperText && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: helperText })
224
223
  ] });
225
224
  };
226
- const DefaultLinkComponent$2 = ({
225
+ const DefaultLinkComponent$1 = ({
227
226
  href,
228
227
  className,
229
228
  children,
@@ -241,7 +240,7 @@ const AppTopBar = ({
241
240
  renderAccountSection,
242
241
  renderCenterSection,
243
242
  renderMobileContent,
244
- LinkComponent = DefaultLinkComponent$2,
243
+ LinkComponent = DefaultLinkComponent$1,
245
244
  sticky = true,
246
245
  variant = "default",
247
246
  mobileMenuLabel = "Menu",
@@ -483,7 +482,11 @@ const ShareDropdown = ({
483
482
  const [shareUrl, setShareUrl] = useState("");
484
483
  const [isPreparingShare, setIsPreparingShare] = useState(false);
485
484
  const [showCopiedFeedback, setShowCopiedFeedback] = useState(false);
486
- React.useEffect(() => {
485
+ const [focusedIndex, setFocusedIndex] = useState(-1);
486
+ const dropdownRef = useRef(null);
487
+ const triggerRef = useRef(null);
488
+ const itemRefs = useRef([]);
489
+ useEffect(() => {
487
490
  const onBeforeShare = shareConfig.onBeforeShare;
488
491
  if (onBeforeShare && !shareUrl) {
489
492
  const prepareUrl = async () => {
@@ -502,26 +505,37 @@ const ShareDropdown = ({
502
505
  prepareUrl();
503
506
  }
504
507
  }, [shareConfig, shareUrl]);
505
- const url = shareUrl || (typeof window !== "undefined" ? window.location.href : "");
506
- const copyToClipboard = async () => {
507
- try {
508
- await navigator.clipboard.writeText(url);
509
- setShowCopiedFeedback(true);
510
- setTimeout(() => {
511
- setShowCopiedFeedback(false);
508
+ useEffect(() => {
509
+ if (!isOpen) return;
510
+ const handleClickOutside = (event) => {
511
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
512
512
  setIsOpen(false);
513
- }, 1500);
514
- } catch {
513
+ setFocusedIndex(-1);
514
+ }
515
+ };
516
+ document.addEventListener("mousedown", handleClickOutside);
517
+ return () => document.removeEventListener("mousedown", handleClickOutside);
518
+ }, [isOpen]);
519
+ useEffect(() => {
520
+ if (!isOpen) return;
521
+ const handleKeyDown = (event) => {
522
+ var _a;
523
+ if (event.key === "Escape") {
524
+ setIsOpen(false);
525
+ setFocusedIndex(-1);
526
+ (_a = triggerRef.current) == null ? void 0 : _a.focus();
527
+ }
528
+ };
529
+ document.addEventListener("keydown", handleKeyDown);
530
+ return () => document.removeEventListener("keydown", handleKeyDown);
531
+ }, [isOpen]);
532
+ useEffect(() => {
533
+ var _a;
534
+ if (focusedIndex >= 0 && itemRefs.current[focusedIndex]) {
535
+ (_a = itemRefs.current[focusedIndex]) == null ? void 0 : _a.focus();
515
536
  }
516
- };
517
- const handleSocialShare = (platformUrl) => {
518
- window.open(
519
- platformUrl,
520
- "_blank",
521
- "noopener,noreferrer,width=600,height=400"
522
- );
523
- setIsOpen(false);
524
- };
537
+ }, [focusedIndex]);
538
+ const url = shareUrl || (typeof window !== "undefined" ? window.location.href : "");
525
539
  const sharePlatforms = [
526
540
  {
527
541
  name: "Twitter",
@@ -558,14 +572,73 @@ const ShareDropdown = ({
558
572
  color: "text-gray-600"
559
573
  }
560
574
  ];
561
- return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
575
+ const totalItems = sharePlatforms.length + 1;
576
+ const copyToClipboard = async () => {
577
+ try {
578
+ await navigator.clipboard.writeText(url);
579
+ setShowCopiedFeedback(true);
580
+ setTimeout(() => {
581
+ setShowCopiedFeedback(false);
582
+ setIsOpen(false);
583
+ }, 1500);
584
+ } catch {
585
+ }
586
+ };
587
+ const handleSocialShare = (platformUrl) => {
588
+ window.open(
589
+ platformUrl,
590
+ "_blank",
591
+ "noopener,noreferrer,width=600,height=400"
592
+ );
593
+ setIsOpen(false);
594
+ };
595
+ const handleToggle = () => {
596
+ const next = !isOpen;
597
+ setIsOpen(next);
598
+ if (next) {
599
+ setFocusedIndex(0);
600
+ } else {
601
+ setFocusedIndex(-1);
602
+ }
603
+ };
604
+ const handleMenuKeyDown = useCallback(
605
+ (event) => {
606
+ switch (event.key) {
607
+ case "ArrowDown":
608
+ event.preventDefault();
609
+ setFocusedIndex((prev) => (prev + 1) % totalItems);
610
+ break;
611
+ case "ArrowUp":
612
+ event.preventDefault();
613
+ setFocusedIndex((prev) => (prev - 1 + totalItems) % totalItems);
614
+ break;
615
+ case "Home":
616
+ event.preventDefault();
617
+ setFocusedIndex(0);
618
+ break;
619
+ case "End":
620
+ event.preventDefault();
621
+ setFocusedIndex(totalItems - 1);
622
+ break;
623
+ case "Tab":
624
+ setIsOpen(false);
625
+ setFocusedIndex(-1);
626
+ break;
627
+ }
628
+ },
629
+ [totalItems]
630
+ );
631
+ return /* @__PURE__ */ jsxs("div", { className: "relative", ref: dropdownRef, children: [
562
632
  /* @__PURE__ */ jsx(
563
633
  "button",
564
634
  {
565
- onClick: () => setIsOpen(!isOpen),
635
+ ref: triggerRef,
636
+ onClick: handleToggle,
566
637
  disabled: isPreparingShare,
567
638
  className: "flex items-center justify-center w-8 h-8 bg-blue-50 hover:bg-blue-100 dark:bg-blue-900/30 dark:hover:bg-blue-900/50 text-blue-600 dark:text-blue-400 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
568
- title: "Share this page",
639
+ "aria-label": "Share this page",
640
+ "aria-expanded": isOpen,
641
+ "aria-haspopup": "menu",
569
642
  children: isPreparingShare ? /* @__PURE__ */ jsx("div", { className: "w-4 h-4 border-2 border-blue-600 border-t-transparent rounded-full animate-spin" }) : /* @__PURE__ */ jsx(
570
643
  "svg",
571
644
  {
@@ -573,6 +646,7 @@ const ShareDropdown = ({
573
646
  fill: "none",
574
647
  stroke: "currentColor",
575
648
  viewBox: "0 0 24 24",
649
+ "aria-hidden": "true",
576
650
  children: /* @__PURE__ */ jsx(
577
651
  "path",
578
652
  {
@@ -586,35 +660,51 @@ const ShareDropdown = ({
586
660
  )
587
661
  }
588
662
  ),
589
- isOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
590
- /* @__PURE__ */ jsx(
591
- "div",
592
- {
593
- className: "fixed inset-0 z-[999998]",
594
- onClick: () => setIsOpen(false)
595
- }
596
- ),
597
- /* @__PURE__ */ jsxs("div", { className: "absolute right-0 top-10 z-[999999] w-40 bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 py-1", children: [
598
- sharePlatforms.map((platform) => /* @__PURE__ */ jsx(
599
- "button",
600
- {
601
- onClick: () => handleSocialShare(platform.url),
602
- className: "w-full flex items-center px-3 py-1.5 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors",
603
- children: /* @__PURE__ */ jsx("span", { className: `text-sm ${platform.color}`, children: platform.name })
604
- },
605
- platform.name
606
- )),
607
- /* @__PURE__ */ jsx("div", { className: "border-t border-gray-200 dark:border-gray-700 my-1" }),
608
- /* @__PURE__ */ jsx(
609
- "button",
610
- {
611
- onClick: copyToClipboard,
612
- className: "w-full flex items-center px-3 py-1.5 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors",
613
- children: /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: showCopiedFeedback ? "Copied!" : "Copy Link" })
614
- }
615
- )
616
- ] })
617
- ] })
663
+ isOpen && /* @__PURE__ */ jsxs(
664
+ "div",
665
+ {
666
+ className: "absolute right-0 top-10 z-[999999] w-40 bg-white dark:bg-gray-800 rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 py-1",
667
+ role: "menu",
668
+ "aria-label": "Share options",
669
+ onKeyDown: handleMenuKeyDown,
670
+ children: [
671
+ sharePlatforms.map((platform, index) => /* @__PURE__ */ jsx(
672
+ "button",
673
+ {
674
+ ref: (el) => {
675
+ itemRefs.current[index] = el;
676
+ },
677
+ onClick: () => handleSocialShare(platform.url),
678
+ className: "w-full flex items-center px-3 py-1.5 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors focus:bg-gray-50 dark:focus:bg-gray-700 focus:outline-none",
679
+ role: "menuitem",
680
+ tabIndex: focusedIndex === index ? 0 : -1,
681
+ children: /* @__PURE__ */ jsx("span", { className: `text-sm ${platform.color}`, children: platform.name })
682
+ },
683
+ platform.name
684
+ )),
685
+ /* @__PURE__ */ jsx(
686
+ "div",
687
+ {
688
+ className: "border-t border-gray-200 dark:border-gray-700 my-1",
689
+ role: "separator"
690
+ }
691
+ ),
692
+ /* @__PURE__ */ jsx(
693
+ "button",
694
+ {
695
+ ref: (el) => {
696
+ itemRefs.current[sharePlatforms.length] = el;
697
+ },
698
+ onClick: copyToClipboard,
699
+ className: "w-full flex items-center px-3 py-1.5 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors focus:bg-gray-50 dark:focus:bg-gray-700 focus:outline-none",
700
+ role: "menuitem",
701
+ tabIndex: focusedIndex === sharePlatforms.length ? 0 : -1,
702
+ children: /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-700 dark:text-gray-300", children: showCopiedFeedback ? "Copied!" : "Copy Link" })
703
+ }
704
+ )
705
+ ]
706
+ }
707
+ )
618
708
  ] });
619
709
  };
620
710
  const TalkToFounderButton = ({
@@ -674,13 +764,13 @@ const AppBreadcrumbs = ({
674
764
  ] })
675
765
  ] }) }) });
676
766
  };
677
- const DefaultLinkComponent$1 = ({
767
+ const DefaultLinkComponent = ({
678
768
  href,
679
769
  className,
680
770
  children,
681
771
  onClick
682
772
  }) => /* @__PURE__ */ jsx("a", { href, className, onClick, children });
683
- function getCopyrightYear$1(startYear = 2025) {
773
+ function getCopyrightYear(startYear = 2025) {
684
774
  const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
685
775
  if (currentYear === startYear) {
686
776
  return String(startYear);
@@ -698,13 +788,13 @@ const AppFooter = ({
698
788
  statusIndicator,
699
789
  StatusIndicatorComponent,
700
790
  links = [],
701
- LinkComponent = DefaultLinkComponent$1,
791
+ LinkComponent = DefaultLinkComponent,
702
792
  sticky = true,
703
793
  isNetworkOnline = true,
704
794
  className,
705
795
  onTrack
706
796
  }) => {
707
- const year = copyrightYear || getCopyrightYear$1();
797
+ const year = copyrightYear || getCopyrightYear();
708
798
  const track = useCallback(
709
799
  (label, params) => {
710
800
  onTrack == null ? void 0 : onTrack({
@@ -788,21 +878,6 @@ const AppFooter = ({
788
878
  }
789
879
  );
790
880
  };
791
- const DefaultLinkComponent = ({
792
- href,
793
- className,
794
- children,
795
- onClick
796
- }) => /* @__PURE__ */ jsx("a", { href, className, onClick, children });
797
- function getCopyrightYear(startYear = 2025) {
798
- const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
799
- if (currentYear === startYear) {
800
- return String(startYear);
801
- } else if (currentYear > startYear) {
802
- return `${startYear}-${currentYear}`;
803
- }
804
- return String(startYear);
805
- }
806
881
  function getGridColumnsClass(sectionCount, explicit) {
807
882
  const cols = explicit || Math.min(sectionCount, 4);
808
883
  switch (cols) {
@@ -1004,6 +1079,13 @@ const AppPageLayout = ({
1004
1079
  mainClassName,
1005
1080
  aspectRatio
1006
1081
  }) => {
1082
+ if (process.env.NODE_ENV !== "production") {
1083
+ if (!topBar) {
1084
+ console.warn(
1085
+ "[AppPageLayout] No topBar provided. The layout will render without a navigation bar. Pass an AppTopBar variant or custom component via the topBar prop."
1086
+ );
1087
+ }
1088
+ }
1007
1089
  const content = aspectRatio ? /* @__PURE__ */ jsx(AspectFitView, { aspectRatio, children }) : children;
1008
1090
  return /* @__PURE__ */ jsx(LayoutProvider, { mode: layoutMode, children: /* @__PURE__ */ jsxs("div", { className: cn(layoutVariants({ background }), className), children: [
1009
1091
  /* @__PURE__ */ jsx("header", { children: topBar }),
@@ -1172,6 +1254,20 @@ const GlobalSettingsPage = ({
1172
1254
  showAppearanceInfoBox = true,
1173
1255
  onTrack
1174
1256
  }) => {
1257
+ if (process.env.NODE_ENV !== "production") {
1258
+ const ids = additionalSections.map((s) => s.id);
1259
+ const duplicateIds = ids.filter((id, i) => ids.indexOf(id) !== i);
1260
+ if (duplicateIds.length > 0) {
1261
+ console.warn(
1262
+ `[GlobalSettingsPage] Duplicate section IDs found: ${duplicateIds.join(", ")}. Each section must have a unique id.`
1263
+ );
1264
+ }
1265
+ if (ids.includes("appearance")) {
1266
+ console.warn(
1267
+ '[GlobalSettingsPage] additionalSections contains a section with id "appearance" which conflicts with the built-in Appearance section. Use a different id to avoid unexpected behavior.'
1268
+ );
1269
+ }
1270
+ }
1175
1271
  const [selectedSection, setSelectedSection] = useState("appearance");
1176
1272
  const [mobileView, setMobileView] = useState(
1177
1273
  "navigation"
@@ -1545,17 +1641,82 @@ const USER_ACTION_ERROR_CODES = [
1545
1641
  "auth/cancelled-popup-request",
1546
1642
  "auth/user-cancelled"
1547
1643
  ];
1644
+ const colorVariantClasses = {
1645
+ primary: {
1646
+ title: "text-primary-600",
1647
+ inputFocus: "focus:ring-primary-500 focus:border-primary-500",
1648
+ submitButton: "bg-primary-600 hover:bg-primary-700 focus:ring-primary-500 disabled:bg-primary-300",
1649
+ googleButtonFocusRing: "focus:ring-primary-500",
1650
+ toggleLink: "text-primary-600 hover:text-primary-500"
1651
+ },
1652
+ blue: {
1653
+ title: "text-blue-600",
1654
+ inputFocus: "focus:ring-blue-500 focus:border-blue-500",
1655
+ submitButton: "bg-blue-600 hover:bg-blue-700 focus:ring-blue-500 disabled:bg-blue-300",
1656
+ googleButtonFocusRing: "focus:ring-blue-500",
1657
+ toggleLink: "text-blue-600 hover:text-blue-500"
1658
+ },
1659
+ indigo: {
1660
+ title: "text-indigo-600",
1661
+ inputFocus: "focus:ring-indigo-500 focus:border-indigo-500",
1662
+ submitButton: "bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500 disabled:bg-indigo-300",
1663
+ googleButtonFocusRing: "focus:ring-indigo-500",
1664
+ toggleLink: "text-indigo-600 hover:text-indigo-500"
1665
+ },
1666
+ violet: {
1667
+ title: "text-violet-600",
1668
+ inputFocus: "focus:ring-violet-500 focus:border-violet-500",
1669
+ submitButton: "bg-violet-600 hover:bg-violet-700 focus:ring-violet-500 disabled:bg-violet-300",
1670
+ googleButtonFocusRing: "focus:ring-violet-500",
1671
+ toggleLink: "text-violet-600 hover:text-violet-500"
1672
+ },
1673
+ orange: {
1674
+ title: "text-orange-600",
1675
+ inputFocus: "focus:ring-orange-500 focus:border-orange-500",
1676
+ submitButton: "bg-orange-600 hover:bg-orange-700 focus:ring-orange-500 disabled:bg-orange-300",
1677
+ googleButtonFocusRing: "focus:ring-orange-500",
1678
+ toggleLink: "text-orange-600 hover:text-orange-500"
1679
+ },
1680
+ emerald: {
1681
+ title: "text-emerald-600",
1682
+ inputFocus: "focus:ring-emerald-500 focus:border-emerald-500",
1683
+ submitButton: "bg-emerald-600 hover:bg-emerald-700 focus:ring-emerald-500 disabled:bg-emerald-300",
1684
+ googleButtonFocusRing: "focus:ring-emerald-500",
1685
+ toggleLink: "text-emerald-600 hover:text-emerald-500"
1686
+ },
1687
+ rose: {
1688
+ title: "text-rose-600",
1689
+ inputFocus: "focus:ring-rose-500 focus:border-rose-500",
1690
+ submitButton: "bg-rose-600 hover:bg-rose-700 focus:ring-rose-500 disabled:bg-rose-300",
1691
+ googleButtonFocusRing: "focus:ring-rose-500",
1692
+ toggleLink: "text-rose-600 hover:text-rose-500"
1693
+ }
1694
+ };
1548
1695
  function LoginPage({
1549
1696
  appName,
1550
1697
  logo,
1551
- auth,
1698
+ onEmailSignIn,
1699
+ onEmailSignUp,
1700
+ onGoogleSignIn,
1552
1701
  onSuccess,
1553
1702
  onAuthError,
1554
1703
  showGoogleSignIn = true,
1555
1704
  showSignUp = true,
1556
1705
  className = "",
1557
- primaryColorClass = "primary"
1706
+ colorVariant = "primary"
1558
1707
  }) {
1708
+ if (process.env.NODE_ENV !== "production") {
1709
+ if (showSignUp && !onEmailSignUp) {
1710
+ console.warn(
1711
+ "[LoginPage] showSignUp is true but onEmailSignUp handler is not provided. Sign-up will fall back to onEmailSignIn. Provide onEmailSignUp for proper account creation."
1712
+ );
1713
+ }
1714
+ if (showGoogleSignIn && !onGoogleSignIn) {
1715
+ console.warn(
1716
+ "[LoginPage] showGoogleSignIn is true but onGoogleSignIn handler is not provided. Google sign-in button will not be rendered. Provide onGoogleSignIn or set showGoogleSignIn to false."
1717
+ );
1718
+ }
1719
+ }
1559
1720
  const [isSignUp, setIsSignUp] = useState(false);
1560
1721
  const [email, setEmail] = useState("");
1561
1722
  const [password, setPassword] = useState("");
@@ -1563,6 +1724,7 @@ function LoginPage({
1563
1724
  const [isLoading, setIsLoading] = useState(false);
1564
1725
  const [isGoogleSignInPending, setIsGoogleSignInPending] = useState(false);
1565
1726
  const googleSignInStartTime = useRef(null);
1727
+ const colors = colorVariantClasses[colorVariant];
1566
1728
  useEffect(() => {
1567
1729
  const handleFocus = () => {
1568
1730
  if (isGoogleSignInPending && googleSignInStartTime.current) {
@@ -1593,11 +1755,10 @@ function LoginPage({
1593
1755
  setError(null);
1594
1756
  setIsLoading(true);
1595
1757
  try {
1596
- if (!auth) throw new Error("Firebase not configured");
1597
- if (isSignUp && showSignUp) {
1598
- await createUserWithEmailAndPassword(auth, email, password);
1758
+ if (isSignUp && showSignUp && onEmailSignUp) {
1759
+ await onEmailSignUp(email, password);
1599
1760
  } else {
1600
- await signInWithEmailAndPassword(auth, email, password);
1761
+ await onEmailSignIn(email, password);
1601
1762
  }
1602
1763
  onSuccess();
1603
1764
  } catch (err) {
@@ -1607,14 +1768,13 @@ function LoginPage({
1607
1768
  }
1608
1769
  };
1609
1770
  const handleGoogleSignIn = async () => {
1771
+ if (!onGoogleSignIn) return;
1610
1772
  setError(null);
1611
1773
  setIsLoading(true);
1612
1774
  setIsGoogleSignInPending(true);
1613
1775
  googleSignInStartTime.current = Date.now();
1614
1776
  try {
1615
- if (!auth) throw new Error("Firebase not configured");
1616
- const provider = new GoogleAuthProvider();
1617
- await signInWithPopup(auth, provider);
1777
+ await onGoogleSignIn();
1618
1778
  onSuccess();
1619
1779
  } catch (err) {
1620
1780
  handleAuthError(err);
@@ -1628,27 +1788,31 @@ function LoginPage({
1628
1788
  return /* @__PURE__ */ jsx(
1629
1789
  "div",
1630
1790
  {
1631
- className: `min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8 ${className}`,
1791
+ className: cn(
1792
+ "min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8",
1793
+ className
1794
+ ),
1632
1795
  children: /* @__PURE__ */ jsxs("div", { className: "max-w-md w-full space-y-8", children: [
1633
1796
  /* @__PURE__ */ jsxs("div", { children: [
1634
1797
  logo && /* @__PURE__ */ jsx("div", { className: "flex justify-center mb-4", children: logo }),
1635
- /* @__PURE__ */ jsx(
1636
- "h1",
1637
- {
1638
- className: `text-center text-3xl font-bold text-${primaryColorClass}-600`,
1639
- children: appName
1640
- }
1641
- ),
1798
+ /* @__PURE__ */ jsx("h1", { className: cn("text-center text-3xl font-bold", colors.title), children: appName }),
1642
1799
  /* @__PURE__ */ jsx("h2", { className: "mt-6 text-center text-2xl font-semibold text-gray-900", children: isSignUp && showSignUp ? text.createAccount : text.signInToAccount })
1643
1800
  ] }),
1644
1801
  /* @__PURE__ */ jsxs("form", { className: "mt-8 space-y-6", onSubmit: handleSubmit, children: [
1645
- error && /* @__PURE__ */ jsx("div", { className: "bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-md text-sm", children: error }),
1802
+ error && /* @__PURE__ */ jsx(
1803
+ "div",
1804
+ {
1805
+ className: "bg-red-50 border border-red-200 text-red-600 px-4 py-3 rounded-md text-sm",
1806
+ role: "alert",
1807
+ children: error
1808
+ }
1809
+ ),
1646
1810
  /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1647
1811
  /* @__PURE__ */ jsxs("div", { children: [
1648
1812
  /* @__PURE__ */ jsx(
1649
1813
  "label",
1650
1814
  {
1651
- htmlFor: "email",
1815
+ htmlFor: "login-email",
1652
1816
  className: "block text-sm font-medium text-gray-700",
1653
1817
  children: text.emailLabel
1654
1818
  }
@@ -1656,7 +1820,7 @@ function LoginPage({
1656
1820
  /* @__PURE__ */ jsx(
1657
1821
  "input",
1658
1822
  {
1659
- id: "email",
1823
+ id: "login-email",
1660
1824
  name: "email",
1661
1825
  type: "email",
1662
1826
  autoComplete: "email",
@@ -1664,7 +1828,10 @@ function LoginPage({
1664
1828
  value: email,
1665
1829
  onChange: (e) => setEmail(e.target.value),
1666
1830
  placeholder: text.emailPlaceholder,
1667
- className: `mt-1 appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-${primaryColorClass}-500 focus:border-${primaryColorClass}-500 sm:text-sm`
1831
+ className: cn(
1832
+ "mt-1 appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none sm:text-sm",
1833
+ colors.inputFocus
1834
+ )
1668
1835
  }
1669
1836
  )
1670
1837
  ] }),
@@ -1672,7 +1839,7 @@ function LoginPage({
1672
1839
  /* @__PURE__ */ jsx(
1673
1840
  "label",
1674
1841
  {
1675
- htmlFor: "password",
1842
+ htmlFor: "login-password",
1676
1843
  className: "block text-sm font-medium text-gray-700",
1677
1844
  children: text.passwordLabel
1678
1845
  }
@@ -1680,7 +1847,7 @@ function LoginPage({
1680
1847
  /* @__PURE__ */ jsx(
1681
1848
  "input",
1682
1849
  {
1683
- id: "password",
1850
+ id: "login-password",
1684
1851
  name: "password",
1685
1852
  type: "password",
1686
1853
  autoComplete: isSignUp ? "new-password" : "current-password",
@@ -1688,7 +1855,10 @@ function LoginPage({
1688
1855
  value: password,
1689
1856
  onChange: (e) => setPassword(e.target.value),
1690
1857
  placeholder: text.passwordPlaceholder,
1691
- className: `mt-1 appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-${primaryColorClass}-500 focus:border-${primaryColorClass}-500 sm:text-sm`
1858
+ className: cn(
1859
+ "mt-1 appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none sm:text-sm",
1860
+ colors.inputFocus
1861
+ )
1692
1862
  }
1693
1863
  )
1694
1864
  ] })
@@ -1698,7 +1868,10 @@ function LoginPage({
1698
1868
  {
1699
1869
  type: "submit",
1700
1870
  disabled: isLoading,
1701
- className: `w-full inline-flex items-center justify-center font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-${primaryColorClass}-600 text-white hover:bg-${primaryColorClass}-700 focus:ring-${primaryColorClass}-500 disabled:bg-${primaryColorClass}-300 px-4 py-2 text-sm`,
1871
+ className: cn(
1872
+ "w-full inline-flex items-center justify-center font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 text-white px-4 py-2 text-sm",
1873
+ colors.submitButton
1874
+ ),
1702
1875
  children: [
1703
1876
  isLoading && /* @__PURE__ */ jsxs(
1704
1877
  "svg",
@@ -1707,6 +1880,7 @@ function LoginPage({
1707
1880
  xmlns: "http://www.w3.org/2000/svg",
1708
1881
  fill: "none",
1709
1882
  viewBox: "0 0 24 24",
1883
+ "aria-hidden": "true",
1710
1884
  children: [
1711
1885
  /* @__PURE__ */ jsx(
1712
1886
  "circle",
@@ -1734,7 +1908,7 @@ function LoginPage({
1734
1908
  ]
1735
1909
  }
1736
1910
  ) }),
1737
- showGoogleSignIn && /* @__PURE__ */ jsxs(Fragment, { children: [
1911
+ showGoogleSignIn && onGoogleSignIn && /* @__PURE__ */ jsxs(Fragment, { children: [
1738
1912
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1739
1913
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center", children: /* @__PURE__ */ jsx("div", { className: "w-full border-t border-gray-300" }) }),
1740
1914
  /* @__PURE__ */ jsx("div", { className: "relative flex justify-center text-sm", children: /* @__PURE__ */ jsx("span", { className: "px-2 bg-gray-50 text-gray-500", children: text.orContinueWith }) })
@@ -1745,7 +1919,10 @@ function LoginPage({
1745
1919
  type: "button",
1746
1920
  onClick: handleGoogleSignIn,
1747
1921
  disabled: isLoading,
1748
- className: `w-full inline-flex items-center justify-center font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-white text-gray-700 border border-gray-300 hover:bg-gray-50 focus:ring-${primaryColorClass}-500 disabled:bg-gray-100 px-4 py-2 text-sm`,
1922
+ className: cn(
1923
+ "w-full inline-flex items-center justify-center font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 bg-white text-gray-700 border border-gray-300 hover:bg-gray-50 disabled:bg-gray-100 px-4 py-2 text-sm",
1924
+ colors.googleButtonFocusRing
1925
+ ),
1749
1926
  children: [
1750
1927
  isLoading ? /* @__PURE__ */ jsxs(
1751
1928
  "svg",
@@ -1754,6 +1931,7 @@ function LoginPage({
1754
1931
  xmlns: "http://www.w3.org/2000/svg",
1755
1932
  fill: "none",
1756
1933
  viewBox: "0 0 24 24",
1934
+ "aria-hidden": "true",
1757
1935
  children: [
1758
1936
  /* @__PURE__ */ jsx(
1759
1937
  "circle",
@@ -1791,7 +1969,7 @@ function LoginPage({
1791
1969
  {
1792
1970
  type: "button",
1793
1971
  onClick: () => setIsSignUp(!isSignUp),
1794
- className: `font-medium text-${primaryColorClass}-600 hover:text-${primaryColorClass}-500`,
1972
+ className: cn("font-medium", colors.toggleLink),
1795
1973
  children: isSignUp ? text.signIn : text.signUp
1796
1974
  }
1797
1975
  )
@@ -2618,6 +2796,7 @@ export {
2618
2796
  AuthStatus,
2619
2797
  ChainType,
2620
2798
  DEFAULT_LANGUAGES,
2799
+ DefaultLinkComponent,
2621
2800
  FontSize,
2622
2801
  GlobalSettingsPage,
2623
2802
  LanguageSelector,
@@ -2628,6 +2807,7 @@ export {
2628
2807
  S as SudobilityApp,
2629
2808
  Theme,
2630
2809
  cn,
2810
+ getCopyrightYear,
2631
2811
  getI18n,
2632
2812
  default2 as i18n,
2633
2813
  initializeI18n,