@the-portland-company/shell 0.82.0 → 0.84.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.
package/dist/chrome.css CHANGED
@@ -354,6 +354,11 @@
354
354
  "3 unread" badge or a green low-fuel warning is worse than a red one — so
355
355
  they must not follow a Mode accent. Same value as before. */
356
356
  --chrome-danger: var(--color-race-red);
357
+ /* Credits icon health (UI-Platform → Top bar → Credits; Pricing & Metering
358
+ §7.5): Good is green, Low is yellow, Out is --chrome-danger. The yellow is
359
+ a darker tread-yellow so a 17px stroke stays legible on the light bar. */
360
+ --credits-good: var(--color-green-flag);
361
+ --credits-low: #B7791F;
357
362
 
358
363
  /* Nav row hover sweep. Was hardcoded #EA4751 → #D32F3B in native/index.tsx
359
364
  (race-red 400 → 500); same two stops, now overridable. */
@@ -1,7 +1,7 @@
1
1
  import { readThemeCookie, THEME_STORAGE_KEY, isThemePreference, applyThemePreference, readPendingThemeChoice, writeThemeCookie, clearPendingThemeChoice, markThemeChoicePending } from './chunk-BHRVZPZ7.js';
2
2
  import clsx from 'clsx';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { createContext, useState, useEffect, useRef, useContext, useCallback, useMemo, useSyncExternalStore } from 'react';
4
+ import { createContext, useState, useEffect, useRef, useContext, useMemo, useCallback, useSyncExternalStore } from 'react';
5
5
  import { FiUser, FiSettings, FiMessageSquare, FiMail, FiInfo, FiSun, FiMoon, FiHelpCircle, FiLifeBuoy, FiAlertCircle, FiAlertTriangle, FiBell, FiGrid, FiZap, FiBatteryCharging, FiBattery } from 'react-icons/fi';
6
6
  import { createPortal } from 'react-dom';
7
7
 
@@ -70,6 +70,81 @@ function resolveBrandGlyph(name) {
70
70
  return GLYPHS[name.trim().toLowerCase()] ?? null;
71
71
  }
72
72
 
73
+ // src/native/credits.ts
74
+ var CREDIT_TYPE_ORDER = [
75
+ "signal",
76
+ "broadcast",
77
+ "ai",
78
+ "enhancement"
79
+ ];
80
+ var CREDIT_TYPE_LABELS = {
81
+ signal: "Signal Credits",
82
+ broadcast: "Broadcast Credits",
83
+ ai: "AI Credits",
84
+ enhancement: "Enhancement Credits"
85
+ };
86
+ var RANK = {
87
+ unknown: 0,
88
+ good: 1,
89
+ low: 2,
90
+ out: 3
91
+ };
92
+ function creditsIconState(channels) {
93
+ let worst = "unknown";
94
+ for (const channel of channels) {
95
+ const state = channel?.state ?? null;
96
+ if (state === null || state === "not_in_use") continue;
97
+ if (RANK[state] > RANK[worst]) worst = state;
98
+ }
99
+ return worst;
100
+ }
101
+ function creditGaugeRatio(channel) {
102
+ if (!channel || channel.state == null || channel.state === "not_in_use")
103
+ return null;
104
+ const fullMark = channel.fullMarkUnits ?? 0;
105
+ if (!(fullMark > 0)) return null;
106
+ return Math.min(1, Math.max(0, channel.remainingUnits / fullMark));
107
+ }
108
+ function creditStateWord(state) {
109
+ switch (state) {
110
+ case "good":
111
+ return "Good";
112
+ case "low":
113
+ return "Low";
114
+ case "out":
115
+ return "Out";
116
+ case "not_in_use":
117
+ return "Not in use";
118
+ default:
119
+ return "Unknown";
120
+ }
121
+ }
122
+ function daysPhrase(days) {
123
+ if (days < 1) return "less than a day at your recent pace";
124
+ const rounded = Math.round(days);
125
+ return `about ${rounded} ${rounded === 1 ? "day" : "days"} at your recent pace`;
126
+ }
127
+ function creditTooltipLine(type, channel) {
128
+ const label = CREDIT_TYPE_LABELS[type];
129
+ if (!channel) return `${label}: unavailable`;
130
+ const left = `${Math.max(0, channel.remainingUnits).toLocaleString()} left`;
131
+ if (channel.state === "not_in_use") return `${label}: not in use`;
132
+ if (channel.state == null) return `${label}: ${left}`;
133
+ if (channel.state === "out") return `${label}: ${left}, out`;
134
+ const days = channel.daysOfUseLeft;
135
+ const pace = typeof days === "number" && Number.isFinite(days) ? `, ${daysPhrase(days)}` : "";
136
+ return `${label}: ${left}${pace}`;
137
+ }
138
+ function creditsAriaLabel(channels) {
139
+ const worst = creditsIconState(CREDIT_TYPE_ORDER.map((t) => channels[t]));
140
+ if (worst === "unknown") return "Credits";
141
+ if (worst === "good") return "Credits: all good";
142
+ const named = CREDIT_TYPE_ORDER.filter(
143
+ (t) => channels[t]?.state === worst
144
+ ).map((t) => CREDIT_TYPE_LABELS[t]);
145
+ return `Credits: ${worst === "low" ? "low on" : "out of"} ${named.join(", ")}`;
146
+ }
147
+
73
148
  // src/notifications/adapter.ts
74
149
  function emptyNotificationsFeed() {
75
150
  return { notifications: [], badge: 0 };
@@ -164,7 +239,7 @@ function resolveOrgLogoUrl(url) {
164
239
  }
165
240
  var TOPBAR_GLYPH_SIZE = 17;
166
241
  function topBarIconButtonClass(tone = "default") {
167
- const toneClass = tone === "danger" || tone === "warning" ? "text-[color:var(--chrome-danger)] hover:text-[color:var(--chrome-danger)]" : "text-[color:var(--topbar-icon-color,var(--muted))] hover:text-[color:var(--topbar-icon-hover-color,var(--foreground))] hover:bg-[color:var(--topbar-icon-hover-bg,transparent)]";
242
+ const toneClass = tone === "danger" || tone === "warning" ? "text-[color:var(--chrome-danger)] hover:text-[color:var(--chrome-danger)]" : tone === "ok" ? "text-[color:var(--credits-good,#00783B)] hover:text-[color:var(--credits-good,#00783B)]" : tone === "low" ? "text-[color:var(--credits-low,#B7791F)] hover:text-[color:var(--credits-low,#B7791F)]" : "text-[color:var(--topbar-icon-color,var(--muted))] hover:text-[color:var(--topbar-icon-hover-color,var(--foreground))] hover:bg-[color:var(--topbar-icon-hover-bg,transparent)]";
168
243
  return [
169
244
  // `group/tbtip` powers the shared <TopBarTooltip> rendered inside the trigger.
170
245
  "group/tbtip relative flex items-center justify-center rounded-[var(--topbar-icon-button-radius,5px)] border border-transparent transition",
@@ -393,6 +468,9 @@ function ValueSkeleton({ className }) {
393
468
  }
394
469
  );
395
470
  }
471
+ function IconGlyph({ icon: Icon, size = TOPBAR_GLYPH_SIZE }) {
472
+ return /* @__PURE__ */ jsx(Icon, { size, className: "text-current" });
473
+ }
396
474
  function SlotGlyph({
397
475
  slot,
398
476
  fallback: Fallback,
@@ -405,9 +483,12 @@ function SlotGlyph({
405
483
  }
406
484
  function TopBarPopover({
407
485
  label,
486
+ ariaLabel,
408
487
  description,
488
+ tooltip,
409
489
  slot,
410
490
  fallbackIcon,
491
+ icon,
411
492
  badge,
412
493
  tone,
413
494
  onOpen,
@@ -457,7 +538,7 @@ function TopBarPopover({
457
538
  "button",
458
539
  {
459
540
  type: "button",
460
- "aria-label": label,
541
+ "aria-label": ariaLabel ?? label,
461
542
  "aria-expanded": open,
462
543
  className: topBarIconButtonClass(tone),
463
544
  onClick: () => {
@@ -468,9 +549,9 @@ function TopBarPopover({
468
549
  });
469
550
  },
470
551
  children: [
471
- /* @__PURE__ */ jsx(SlotGlyph, { slot, fallback: fallbackIcon }),
552
+ icon ? /* @__PURE__ */ jsx(IconGlyph, { icon }) : /* @__PURE__ */ jsx(SlotGlyph, { slot, fallback: fallbackIcon }),
472
553
  typeof badge === "number" ? /* @__PURE__ */ jsx(TopBarCountBadge, { count: badge }) : null,
473
- !open && !hideTooltip ? /* @__PURE__ */ jsx(TopBarTooltip, { title: label, description }) : null
554
+ !open && !hideTooltip ? /* @__PURE__ */ jsx(TopBarTooltip, { title: label, description: tooltip ?? description }) : null
474
555
  ]
475
556
  }
476
557
  ),
@@ -544,147 +625,145 @@ function XGlyph({ size = 14, className }) {
544
625
  function CheckGlyph({ size = 14, className }) {
545
626
  return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", className, "aria-hidden": true, children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) });
546
627
  }
547
- var LOW_FUEL_HINT_DISMISSED_KEY = "politogy_low_fuel_hint_dismissed";
548
- function FuelRow({
549
- label,
628
+ function batteryFillWidth(state) {
629
+ switch (state) {
630
+ case "good":
631
+ return 12;
632
+ case "low":
633
+ return 5;
634
+ default:
635
+ return 0;
636
+ }
637
+ }
638
+ function creditsBatteryGlyph(state) {
639
+ const fill = batteryFillWidth(state);
640
+ const Glyph = ({ size = TOPBAR_GLYPH_SIZE, className }) => /* @__PURE__ */ jsxs(
641
+ "svg",
642
+ {
643
+ width: size,
644
+ height: size,
645
+ viewBox: "0 0 24 24",
646
+ fill: "none",
647
+ stroke: "currentColor",
648
+ strokeWidth: 2,
649
+ strokeLinecap: "round",
650
+ strokeLinejoin: "round",
651
+ className,
652
+ "data-credits-state": state,
653
+ "aria-hidden": true,
654
+ children: [
655
+ /* @__PURE__ */ jsx("rect", { x: "1", y: "6", width: "18", height: "12", rx: "2", ry: "2" }),
656
+ /* @__PURE__ */ jsx("line", { x1: "23", y1: "10", x2: "23", y2: "14" }),
657
+ fill > 0 ? /* @__PURE__ */ jsx("rect", { x: "4", y: "9", width: fill, height: "6", rx: "1", fill: "currentColor", stroke: "none", opacity: 0.35 }) : null,
658
+ /* @__PURE__ */ jsx("path", { d: "M11.5 8 8.5 12.5h3L10.5 16l3.5-4.5h-3L11.5 8z", fill: "currentColor", stroke: "none" })
659
+ ]
660
+ }
661
+ );
662
+ Glyph.displayName = `CreditsBatteryGlyph(${state})`;
663
+ return Glyph;
664
+ }
665
+ function stateTone(state) {
666
+ switch (state) {
667
+ case "good":
668
+ return "ok";
669
+ case "low":
670
+ return "low";
671
+ case "out":
672
+ return "danger";
673
+ default:
674
+ return "default";
675
+ }
676
+ }
677
+ function stateColorVar(state) {
678
+ switch (state) {
679
+ case "good":
680
+ return "var(--credits-good,#00783B)";
681
+ case "low":
682
+ return "var(--credits-low,#B7791F)";
683
+ case "out":
684
+ return "var(--chrome-danger)";
685
+ default:
686
+ return "var(--muted)";
687
+ }
688
+ }
689
+ function CreditGauge({ channel, className }) {
690
+ const ratio = creditGaugeRatio(channel);
691
+ return /* @__PURE__ */ jsx(
692
+ "div",
693
+ {
694
+ role: "meter",
695
+ "aria-label": `${creditStateWord(channel?.state)} level`,
696
+ "aria-valuemin": 0,
697
+ "aria-valuemax": 100,
698
+ "aria-valuenow": ratio === null ? void 0 : Math.round(ratio * 100),
699
+ className: ["h-1.5 w-full overflow-hidden rounded-full bg-[color:var(--panel)]", className ?? ""].join(" "),
700
+ children: ratio === null ? null : /* @__PURE__ */ jsx(
701
+ "div",
702
+ {
703
+ className: "h-full rounded-full transition-[width]",
704
+ style: { width: `${Math.round(ratio * 100)}%`, backgroundColor: stateColorVar(channel?.state) }
705
+ }
706
+ )
707
+ }
708
+ );
709
+ }
710
+ function formatGrantDate(iso) {
711
+ if (!iso) return null;
712
+ const t = Date.parse(iso);
713
+ if (!Number.isFinite(t)) return null;
714
+ return new Date(t).toLocaleDateString("en-US", { month: "short", day: "numeric" });
715
+ }
716
+ function CreditRow({
717
+ type,
550
718
  channel,
551
719
  loading,
552
- addHref,
553
- addLabel
720
+ addHref
554
721
  }) {
722
+ const label = CREDIT_TYPE_LABELS[type];
555
723
  const remaining = channel?.remainingUnits ?? 0;
556
- const total = channel ? Math.max(channel.rawBalanceUnits, channel.remainingUnits) : 0;
557
- const ratio = total > 0 ? Math.min(100, Math.max(0, remaining / total * 100)) : 0;
558
- const low = channel?.lowFuel ?? false;
559
- return /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
724
+ const state = channel?.state ?? null;
725
+ const unavailable = !loading && !channel;
726
+ const nextGrant = formatGrantDate(channel?.nextGrantAt);
727
+ const pools = channel && typeof channel.includedUnits === "number" && typeof channel.purchasedUnits === "number" ? `Included ${channel.includedUnits.toLocaleString()}${nextGrant ? ` (next ${nextGrant})` : ""} \xB7 Purchased ${channel.purchasedUnits.toLocaleString()}` : null;
728
+ return /* @__PURE__ */ jsxs("div", { className: "w-full", "data-credit-type": type, children: [
560
729
  /* @__PURE__ */ jsxs("div", { className: "mb-1 flex items-center justify-between gap-2", children: [
561
730
  /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-[color:var(--foreground)]", children: label }),
562
731
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
563
- loading ? /* @__PURE__ */ jsx(ValueSkeleton, { className: "h-3.5 w-14" }) : /* @__PURE__ */ jsxs(
564
- "span",
565
- {
566
- className: [
567
- "text-xs font-semibold tabular-nums",
568
- low ? "text-[color:var(--chrome-danger)]" : "text-[color:var(--muted)]"
569
- ].join(" "),
570
- children: [
571
- remaining.toLocaleString(),
572
- " left"
573
- ]
574
- }
575
- ),
576
- addHref ? /* @__PURE__ */ jsx(
732
+ loading ? /* @__PURE__ */ jsx(ValueSkeleton, { className: "h-3.5 w-14" }) : unavailable ? /* @__PURE__ */ jsx("span", { className: "text-xs text-[color:var(--muted)]", children: "Unavailable" }) : /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold tabular-nums", style: { color: stateColorVar(state) }, children: [
733
+ remaining.toLocaleString(),
734
+ " left",
735
+ state === "not_in_use" ? null : /* @__PURE__ */ jsxs("span", { className: "font-normal text-[color:var(--muted)]", children: [
736
+ " \xB7 ",
737
+ creditStateWord(state)
738
+ ] })
739
+ ] }),
740
+ addHref && !unavailable ? /* @__PURE__ */ jsx(
577
741
  "a",
578
742
  {
579
743
  href: addHref,
580
- "aria-label": addLabel,
744
+ "aria-label": `Add ${label}`,
581
745
  className: "flex h-5 w-5 items-center justify-center rounded-full border border-[color:var(--line)] text-[color:var(--muted)] transition hover:border-[color:var(--foreground)] hover:text-[color:var(--foreground)]",
582
746
  children: /* @__PURE__ */ jsx(PlusGlyph, {})
583
747
  }
584
748
  ) : null
585
749
  ] })
586
750
  ] }),
587
- /* @__PURE__ */ jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-[color:var(--panel)]", children: loading ? /* @__PURE__ */ jsx("div", { className: "h-full w-full animate-pulse bg-[color:var(--line)]" }) : /* @__PURE__ */ jsx(
588
- "div",
589
- {
590
- className: [
591
- "h-full rounded-full transition-[width]",
592
- low ? "bg-[color:var(--chrome-danger)]" : "bg-[color:var(--color-ok,#00783B)]"
593
- ].join(" "),
594
- style: { width: `${ratio}%` }
595
- }
596
- ) })
751
+ loading ? /* @__PURE__ */ jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-[color:var(--panel)]", children: /* @__PURE__ */ jsx("div", { className: "h-full w-full animate-pulse bg-[color:var(--line)]" }) }) : /* @__PURE__ */ jsx(CreditGauge, { channel: unavailable ? null : channel }),
752
+ !loading && state === "not_in_use" ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-[color:var(--muted)]", children: "Not in use" }) : pools ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-[color:var(--muted)]", children: pools }) : null
597
753
  ] });
598
754
  }
599
- var lowFuelHintVisible = false;
600
- var lowFuelHintListeners = /* @__PURE__ */ new Set();
601
- function setLowFuelHintVisible(visible) {
602
- if (lowFuelHintVisible === visible) return;
603
- lowFuelHintVisible = visible;
604
- lowFuelHintListeners.forEach((l) => l());
605
- }
606
- function subscribeLowFuelHintVisible(onChange) {
607
- lowFuelHintListeners.add(onChange);
608
- return () => lowFuelHintListeners.delete(onChange);
755
+ function CreditsTooltip({ fuel }) {
756
+ return /* @__PURE__ */ jsx("span", { className: "flex w-56 flex-col gap-1.5", "data-testid": "credits-tooltip", children: CREDIT_TYPE_ORDER.map((type) => {
757
+ const channel = fuel ? fuel[type] ?? null : null;
758
+ return /* @__PURE__ */ jsxs("span", { className: "flex flex-col gap-0.5", children: [
759
+ /* @__PURE__ */ jsx("span", { children: creditTooltipLine(type, channel) }),
760
+ /* @__PURE__ */ jsx(CreditGauge, { channel, className: "bg-[#FFFFFF]/20" })
761
+ ] }, type);
762
+ }) });
609
763
  }
610
- function useLowFuelHintVisible() {
611
- return useSyncExternalStore(
612
- subscribeLowFuelHintVisible,
613
- () => lowFuelHintVisible,
614
- () => false
615
- );
616
- }
617
- var LOW_FUEL_HINT_CLEARANCE_PX = 96;
618
- function LowFuelHint({ lowFuel, orgId }) {
619
- const [dismissed, setDismissed] = useState(() => {
620
- if (typeof window === "undefined") return false;
621
- try {
622
- return window.sessionStorage.getItem(LOW_FUEL_HINT_DISMISSED_KEY) === "1";
623
- } catch {
624
- return false;
625
- }
626
- });
627
- useEffect(() => {
628
- if (lowFuel) return;
629
- try {
630
- window.sessionStorage.removeItem(LOW_FUEL_HINT_DISMISSED_KEY);
631
- } catch {
632
- }
633
- }, [lowFuel]);
634
- const panelOpen = useAnyTopBarPanelOpen();
635
- const showing = lowFuel && !dismissed && !panelOpen;
636
- useEffect(() => {
637
- setLowFuelHintVisible(showing);
638
- return () => setLowFuelHintVisible(false);
639
- }, [showing]);
640
- if (!showing) return null;
641
- const dismiss = () => {
642
- try {
643
- window.sessionStorage.setItem(LOW_FUEL_HINT_DISMISSED_KEY, "1");
644
- } catch {
645
- }
646
- setDismissed(true);
647
- };
648
- return /* @__PURE__ */ jsxs(
649
- "div",
650
- {
651
- role: "status",
652
- className: "absolute right-0 top-[calc(100%+10px)] z-[60] w-60 rounded-lg border border-[color:var(--chrome-danger)] bg-[color:var(--chrome-danger)] px-3 py-2 text-[#FFFFFF] shadow-[var(--shadow-soft)]",
653
- onClick: (e) => e.stopPropagation(),
654
- children: [
655
- /* @__PURE__ */ jsx(
656
- "span",
657
- {
658
- "aria-hidden": true,
659
- className: "absolute -top-[5px] right-[11px] h-2.5 w-2.5 rotate-45 bg-[color:var(--chrome-danger)]"
660
- }
661
- ),
662
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
663
- /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
664
- /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: "You\u2019re low on fuel" }),
665
- /* @__PURE__ */ jsx(
666
- "a",
667
- {
668
- href: withOrgParam(CORE_BILLING_URL, orgId),
669
- className: "mt-1 inline-block text-sm font-bold underline hover:opacity-90",
670
- children: "Buy More Fuel"
671
- }
672
- )
673
- ] }),
674
- /* @__PURE__ */ jsx(
675
- "button",
676
- {
677
- type: "button",
678
- "aria-label": "Dismiss low fuel notice",
679
- onClick: dismiss,
680
- className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[#FFFFFF]/90 transition hover:bg-[#FFFFFF]/20",
681
- children: /* @__PURE__ */ jsx(XGlyph, {})
682
- }
683
- )
684
- ] })
685
- ]
686
- }
687
- );
764
+ function channelsOf(fuel) {
765
+ if (!fuel) return {};
766
+ return { signal: fuel.signal, broadcast: fuel.broadcast, ai: fuel.ai, enhancement: fuel.enhancement };
688
767
  }
689
768
  function FuelAction({ orgId }) {
690
769
  const { dedupedFetch } = useNavChrome("FuelAction");
@@ -716,52 +795,28 @@ function FuelAction({ orgId }) {
716
795
  cancelled = true;
717
796
  };
718
797
  }, [dedupedFetch, orgId]);
719
- const low = !!fuel && (fuel.signal.lowFuel || fuel.broadcast.lowFuel);
720
- const lowFuelHintUp = useLowFuelHintVisible();
721
- return (
722
- // The nudge anchors to the trigger, so it sits OUTSIDE the popover panel in
723
- // its own relative wrapper (same shape the VRM widget used).
724
- /* @__PURE__ */ jsxs("div", { className: "relative inline-flex", children: [
725
- /* @__PURE__ */ jsx(
726
- TopBarPopover,
727
- {
728
- label: "Communications Fuel",
729
- description: "Remaining SMS and email sending balance for the active organization.",
730
- slot: "comms-fuel",
731
- fallbackIcon: BatteryGlyph,
732
- tone: low ? "warning" : "default",
733
- hideTooltip: lowFuelHintUp,
734
- children: /* @__PURE__ */ jsxs("div", { className: "px-4 py-3", children: [
735
- /* @__PURE__ */ jsx("p", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Communications Fuel" }),
736
- error || !loading && !fuel ? /* @__PURE__ */ jsx("p", { className: "text-sm text-[color:var(--muted)]", children: "Fuel balance is unavailable right now." }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
737
- /* @__PURE__ */ jsx(
738
- FuelRow,
739
- {
740
- label: "SMS Signal",
741
- channel: fuel?.signal ?? null,
742
- loading,
743
- addHref: withOrgParam(CORE_BILLING_URL, orgId),
744
- addLabel: "Add SMS Signal fuel"
745
- }
746
- ),
747
- /* @__PURE__ */ jsx(
748
- FuelRow,
749
- {
750
- label: "Email Broadcast",
751
- channel: fuel?.broadcast ?? null,
752
- loading,
753
- addHref: withOrgParam(CORE_BILLING_URL, orgId),
754
- addLabel: "Add Email Broadcast fuel"
755
- }
756
- )
757
- ] })
758
- ] })
759
- }
760
- ),
761
- /* @__PURE__ */ jsx(LowFuelHint, { lowFuel: low, orgId })
762
- ] })
798
+ const channels = channelsOf(fuel);
799
+ const iconState = creditsIconState(CREDIT_TYPE_ORDER.map((t) => channels[t]));
800
+ const Glyph = useMemo(() => creditsBatteryGlyph(iconState), [iconState]);
801
+ const addHref = withOrgParam(CORE_BILLING_URL, orgId);
802
+ return /* @__PURE__ */ jsx(
803
+ TopBarPopover,
804
+ {
805
+ label: "Credits",
806
+ ariaLabel: creditsAriaLabel(channels),
807
+ tooltip: /* @__PURE__ */ jsx(CreditsTooltip, { fuel }),
808
+ slot: "comms-fuel",
809
+ fallbackIcon: BatteryGlyph,
810
+ icon: Glyph,
811
+ tone: stateTone(iconState),
812
+ children: /* @__PURE__ */ jsxs("div", { className: "px-4 py-3", children: [
813
+ /* @__PURE__ */ jsx("p", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Credits" }),
814
+ error || !loading && !fuel ? /* @__PURE__ */ jsx("p", { className: "text-sm text-[color:var(--muted)]", children: "Credits are unavailable right now." }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: CREDIT_TYPE_ORDER.map((type) => /* @__PURE__ */ jsx(CreditRow, { type, channel: channels[type] ?? null, loading, addHref }, type)) })
815
+ ] })
816
+ }
763
817
  );
764
818
  }
819
+ var CreditsAction = FuelAction;
765
820
  function notifStateKey(orgId) {
766
821
  return `politogy_chrome_notif_state:${orgId ?? "none"}`;
767
822
  }
@@ -840,7 +895,6 @@ function NewReplyToast({
840
895
  const [visible, setVisible] = useState(false);
841
896
  const onCloseRef = useRef(onClose);
842
897
  onCloseRef.current = onClose;
843
- const lowFuelHintVisible2 = useLowFuelHintVisible();
844
898
  useEffect(() => {
845
899
  setVisible(true);
846
900
  const fadeAt = Math.max(0, ttlMs - 450);
@@ -870,7 +924,6 @@ function NewReplyToast({
870
924
  },
871
925
  className: "absolute right-[-6px] top-[calc(100%+12px)] z-50 min-w-[240px] max-w-[320px] cursor-pointer rounded-xl bg-[#161616] px-4 py-3 text-[#FFFFFF] shadow-[var(--shadow-soft)]",
872
926
  style: {
873
- top: lowFuelHintVisible2 ? `calc(100% + 12px + ${LOW_FUEL_HINT_CLEARANCE_PX}px)` : void 0,
874
927
  opacity: visible ? 1 : 0,
875
928
  transform: visible ? "translateY(0px)" : "translateY(-4px)",
876
929
  transition: "opacity 250ms ease, transform 250ms ease"
@@ -1998,6 +2051,6 @@ function SpeculationRulesScript({ items }) {
1998
2051
  return null;
1999
2052
  }
2000
2053
 
2001
- export { CORE_APP_ORIGIN, CORE_BILLING_PATH, FormColumn, FuelAction, NAV_DISPLAY_OPTIONS, NavChromeProvider, NavDisplayAction, NavThemeControl, NotificationsAction, SPECS_ORIGIN, SpecOverlayAction, SpeculationRulesScript, SupportAction, TOPBAR_GLYPH_SIZE, TopBarCountBadge, TopBarPanelArrow, TopBarPopover, TopBarTooltip, TopBarWidgetSlot, buildNavSpeculationRules, buildSpeculationRules, collectSiblingAppUrls, createCommsNotificationsAdapter, emptyNotificationsFeed, normalizeNotificationsFeed, resolveBrandGlyph, resolveOrgLogoUrl, topBarIconButtonClass, useChromeMe, useChromeOrgs, useNavChromeDedupedFetch, useNavChromeFetcher, useThemePreference, useTopBarIconSlotGlyph, warnDeprecated };
2002
- //# sourceMappingURL=chunk-L73FZKGE.js.map
2003
- //# sourceMappingURL=chunk-L73FZKGE.js.map
2054
+ export { CORE_APP_ORIGIN, CORE_BILLING_PATH, CREDIT_TYPE_LABELS, CREDIT_TYPE_ORDER, CreditsAction, FormColumn, FuelAction, NAV_DISPLAY_OPTIONS, NavChromeProvider, NavDisplayAction, NavThemeControl, NotificationsAction, SPECS_ORIGIN, SpecOverlayAction, SpeculationRulesScript, SupportAction, TOPBAR_GLYPH_SIZE, TopBarCountBadge, TopBarPanelArrow, TopBarPopover, TopBarTooltip, TopBarWidgetSlot, buildNavSpeculationRules, buildSpeculationRules, collectSiblingAppUrls, createCommsNotificationsAdapter, creditGaugeRatio, creditStateWord, creditTooltipLine, creditsAriaLabel, creditsIconState, emptyNotificationsFeed, normalizeNotificationsFeed, resolveBrandGlyph, resolveOrgLogoUrl, topBarIconButtonClass, useChromeMe, useChromeOrgs, useNavChromeDedupedFetch, useNavChromeFetcher, useThemePreference, useTopBarIconSlotGlyph, warnDeprecated };
2055
+ //# sourceMappingURL=chunk-PRZP2SB6.js.map
2056
+ //# sourceMappingURL=chunk-PRZP2SB6.js.map