@loafmarkets/ui 0.1.412 → 0.1.413

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.mjs CHANGED
@@ -15097,6 +15097,9 @@ var STATUS_COLOR2 = {
15097
15097
  CLOSED: "#848e9c",
15098
15098
  CANCELLED: "#f6465d"
15099
15099
  };
15100
+ var STUCK_SETTLING_MS = 7e3;
15101
+ var SETTLING_MESSAGE = "Trade settling on-chain";
15102
+ var STUCK_SETTLING_MESSAGE = "Attention: Trade taking longer than usual to settle on chain. Please wait up to 10 seconds before trading. This is reflected in your locked balance.";
15100
15103
  var settlingSpin = keyframes`
15101
15104
  to { transform: rotate(360deg); }
15102
15105
  `;
@@ -15110,41 +15113,55 @@ var SettlingSpinner = styled10.span`
15110
15113
  animation: ${settlingSpin} 0.7s linear infinite;
15111
15114
  flex-shrink: 0;
15112
15115
  `;
15116
+ var StuckSettlingGlyph = styled10.span`
15117
+ display: inline-block;
15118
+ font-size: 11px;
15119
+ line-height: 1;
15120
+ flex-shrink: 0;
15121
+ `;
15113
15122
  var SettlingTip = styled10.span`
15114
15123
  position: relative;
15115
15124
  display: inline-flex;
15116
15125
  align-items: center;
15117
15126
  cursor: help;
15118
- &::after {
15119
- content: 'Trade settling on-chain';
15120
- position: absolute;
15121
- left: 50%;
15122
- bottom: calc(100% + 8px);
15123
- transform: translateX(-50%) translateY(3px);
15124
- padding: 6px 8px;
15125
- background: rgba(20, 22, 28, 0.97);
15126
- border: 1px solid rgba(255, 255, 255, 0.1);
15127
- border-radius: 6px;
15128
- color: rgba(255, 255, 255, 0.78);
15129
- font-size: 0.66rem;
15130
- font-weight: 400;
15131
- line-height: 1.3;
15132
- letter-spacing: normal;
15133
- text-transform: none;
15134
- white-space: nowrap;
15135
- box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
15136
- opacity: 0;
15137
- visibility: hidden;
15138
- transition: opacity 0.15s ease, transform 0.15s ease;
15139
- pointer-events: none;
15140
- z-index: 2000;
15141
- }
15142
- &:hover::after {
15127
+ `;
15128
+ var SettlingTipBubble = styled10.span`
15129
+ position: absolute;
15130
+ bottom: calc(100% + 8px);
15131
+ /* Stuck copy is a paragraph, so it anchors to the icon's left edge instead of
15132
+ centring centred it would hang off the left of the row. */
15133
+ left: ${(p) => p.$stuck ? "0" : "50%"};
15134
+ transform: ${(p) => p.$stuck ? "translateX(0)" : "translateX(-50%)"} translateY(3px);
15135
+ width: ${(p) => p.$stuck ? "224px" : "auto"};
15136
+ white-space: ${(p) => p.$stuck ? "normal" : "nowrap"};
15137
+ padding: 6px 8px;
15138
+ background: rgba(20, 22, 28, 0.97);
15139
+ border: 1px solid rgba(255, 255, 255, 0.1);
15140
+ border-radius: 6px;
15141
+ color: rgba(255, 255, 255, 0.78);
15142
+ font-size: 0.66rem;
15143
+ font-weight: 400;
15144
+ line-height: 1.35;
15145
+ letter-spacing: normal;
15146
+ text-transform: none;
15147
+ text-align: left;
15148
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
15149
+ opacity: 0;
15150
+ visibility: hidden;
15151
+ transition: opacity 0.15s ease, transform 0.15s ease;
15152
+ pointer-events: none;
15153
+ z-index: 2000;
15154
+
15155
+ ${SettlingTip}:hover & {
15143
15156
  opacity: 1;
15144
15157
  visibility: visible;
15145
- transform: translateX(-50%) translateY(0);
15158
+ transform: ${(p) => p.$stuck ? "translateX(0)" : "translateX(-50%)"} translateY(0);
15146
15159
  }
15147
15160
  `;
15161
+ var SettlingIndicator = ({ stuck, style }) => /* @__PURE__ */ jsxs(SettlingTip, { role: "img", "aria-label": stuck ? "Settlement delayed" : SETTLING_MESSAGE, style, children: [
15162
+ stuck ? /* @__PURE__ */ jsx(StuckSettlingGlyph, { "aria-hidden": "true", children: "\u26A0\uFE0F" }) : /* @__PURE__ */ jsx(SettlingSpinner, { "aria-hidden": "true" }),
15163
+ /* @__PURE__ */ jsx(SettlingTipBubble, { $stuck: stuck, children: stuck ? STUCK_SETTLING_MESSAGE : SETTLING_MESSAGE })
15164
+ ] });
15148
15165
  var ACTIVITY_PAGE_SIZE_DEFAULT = 8;
15149
15166
  var formatCurrency4 = (value, opts) => {
15150
15167
  const min = opts?.minimumFractionDigits ?? 2;
@@ -15152,14 +15169,21 @@ var formatCurrency4 = (value, opts) => {
15152
15169
  return `$${value.toLocaleString("en-US", { minimumFractionDigits: min, maximumFractionDigits: max })}`;
15153
15170
  };
15154
15171
  var formatNumber2 = (value, maximumFractionDigits = 2) => value.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits });
15172
+ var toMillis = (unix) => {
15173
+ if (unix > 1e18) return Math.floor(unix / 1e6);
15174
+ if (unix > 1e14) return Math.floor(unix / 1e3);
15175
+ if (unix > 1e12) return unix;
15176
+ return unix * 1e3;
15177
+ };
15178
+ var SETTLED_TRADE_STATUSES = ["SETTLED", "CONFIRMED", "FILLED", "ALLOCATED"];
15179
+ var FAILED_TRADE_STATUSES = ["SETTLEMENT_FAILED", "REJECTED", "CANCELLED", "CANCELED"];
15180
+ var tradeIsSettling = (status) => {
15181
+ const s = status?.toUpperCase();
15182
+ return !!s && !SETTLED_TRADE_STATUSES.includes(s) && !FAILED_TRADE_STATUSES.includes(s);
15183
+ };
15155
15184
  var formatTimestamp = (unix) => {
15156
15185
  if (!unix) return "\u2014";
15157
- let sec;
15158
- if (unix > 1e18) sec = Math.floor(unix / 1e9);
15159
- else if (unix > 1e14) sec = Math.floor(unix / 1e6);
15160
- else if (unix > 1e12) sec = Math.floor(unix / 1e3);
15161
- else sec = unix;
15162
- return new Date(sec * 1e3).toLocaleString("en-AU", {
15186
+ return new Date(toMillis(unix)).toLocaleString("en-AU", {
15163
15187
  day: "2-digit",
15164
15188
  month: "short",
15165
15189
  hour: "2-digit",
@@ -15223,6 +15247,40 @@ function PortfolioActivityPanel({
15223
15247
  const [closeAllOpen, setCloseAllOpen] = useState(false);
15224
15248
  const [closeAllType, setCloseAllType] = useState("market");
15225
15249
  const [closingAll, setClosingAll] = useState(false);
15250
+ const settlingPositionIds = positions.filter((p) => p.settling).map((p) => p.propertyId);
15251
+ const settlingKey = settlingPositionIds.join(",");
15252
+ const settlingTradeStartByProperty = /* @__PURE__ */ new Map();
15253
+ const settlingTradeDeadlines = [];
15254
+ for (const trade of tradeHistory) {
15255
+ if (!tradeIsSettling(trade.status)) continue;
15256
+ const startedAt = toMillis(trade.executedAt);
15257
+ settlingTradeDeadlines.push(startedAt + STUCK_SETTLING_MS);
15258
+ const earliest = settlingTradeStartByProperty.get(trade.propertyId);
15259
+ if (earliest === void 0 || startedAt < earliest) settlingTradeStartByProperty.set(trade.propertyId, startedAt);
15260
+ }
15261
+ const tradeDeadlineKey = settlingTradeDeadlines.join(",");
15262
+ const settlingSinceRef = useRef(/* @__PURE__ */ new Map());
15263
+ const [now, setNow] = useState(() => Date.now());
15264
+ useEffect(() => {
15265
+ const since = settlingSinceRef.current;
15266
+ const seenAt = Date.now();
15267
+ for (const id of settlingPositionIds) if (!since.has(id)) since.set(id, seenAt);
15268
+ for (const id of [...since.keys()]) if (!settlingPositionIds.includes(id)) since.delete(id);
15269
+ setNow(seenAt);
15270
+ }, [settlingKey]);
15271
+ const positionSettlingStart = (propertyId) => settlingTradeStartByProperty.get(propertyId) ?? settlingSinceRef.current.get(propertyId) ?? now;
15272
+ useEffect(() => {
15273
+ const pending = [
15274
+ ...settlingPositionIds.map((id) => positionSettlingStart(id) + STUCK_SETTLING_MS),
15275
+ ...settlingTradeDeadlines
15276
+ ].filter((deadline) => deadline > now);
15277
+ if (pending.length === 0) return;
15278
+ const delay = Math.min(Math.min(...pending) - now + 50, 2147483647);
15279
+ const timer = setTimeout(() => setNow(Date.now()), delay);
15280
+ return () => clearTimeout(timer);
15281
+ }, [settlingKey, tradeDeadlineKey, now]);
15282
+ const isPositionStuck = (pos) => !!pos.settling && now - positionSettlingStart(pos.propertyId) >= STUCK_SETTLING_MS;
15283
+ const isTradeStuck = (trade) => now - toMillis(trade.executedAt) >= STUCK_SETTLING_MS;
15226
15284
  const hasPositionActions = !!(onClosePosition || onSharePosition || onCloseAllPositions);
15227
15285
  const [posSortKey, setPosSortKey] = useState("asset");
15228
15286
  const [posSortAsc, setPosSortAsc] = useState(true);
@@ -15477,7 +15535,7 @@ function PortfolioActivityPanel({
15477
15535
  /* @__PURE__ */ jsx(CCellLabel, { children: "Asset" }),
15478
15536
  /* @__PURE__ */ jsxs(CAssetName, { children: [
15479
15537
  pos.tokenName,
15480
- pos.settling && /* @__PURE__ */ jsx(SettlingTip, { style: { marginLeft: 6 }, children: /* @__PURE__ */ jsx(SettlingSpinner, {}) })
15538
+ pos.settling && /* @__PURE__ */ jsx(SettlingIndicator, { stuck: isPositionStuck(pos), style: { marginLeft: 6 } })
15481
15539
  ] })
15482
15540
  ] }),
15483
15541
  /* @__PURE__ */ jsxs(CUnitsCell, { children: [
@@ -15569,7 +15627,7 @@ function PortfolioActivityPanel({
15569
15627
  return /* @__PURE__ */ jsxs(PositionsRow, { $hasClose: hasPositionActions, children: [
15570
15628
  /* @__PURE__ */ jsx(PositionsCell, { children: /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
15571
15629
  /* @__PURE__ */ jsx(HoldingsPropertyLink, { onClick: () => onPositionClick?.(pos.tokenName, pos.isIpoAllocation), children: pos.tokenName }),
15572
- pos.settling && /* @__PURE__ */ jsx(SettlingTip, { children: /* @__PURE__ */ jsx(SettlingSpinner, {}) })
15630
+ pos.settling && /* @__PURE__ */ jsx(SettlingIndicator, { stuck: isPositionStuck(pos) })
15573
15631
  ] }) }),
15574
15632
  /* @__PURE__ */ jsx(PositionsCell, { children: /* @__PURE__ */ jsxs("span", { style: { color: "#fff", fontSize: "0.8rem" }, children: [
15575
15633
  formatNumber2(pos.totalQuantity),
@@ -15950,7 +16008,8 @@ function PortfolioActivityPanel({
15950
16008
  const s = trade.status?.toUpperCase();
15951
16009
  const isSettled = s === "SETTLED" || s === "CONFIRMED" || s === "FILLED" || s === "ALLOCATED";
15952
16010
  const isFailed = s === "SETTLEMENT_FAILED" || s === "REJECTED" || s === "CANCELLED" || s === "CANCELED";
15953
- const isSettling = !isSettled && !isFailed && !!trade.status;
16011
+ const isSettling = tradeIsSettling(trade.status);
16012
+ const stuckSettling = isSettling && isTradeStuck(trade);
15954
16013
  const settlementLabel = isSettled ? "Settled" : isFailed ? "Settlement Failed" : isSettling ? prettyLabel(trade.status) : "\u2014";
15955
16014
  const settlementMeta = isSettled ? { color: "#0ecb81", bg: "rgba(14,203,129,0.1)" } : isFailed ? { color: "#f6465d", bg: "rgba(246,70,93,0.12)" } : isSettling ? { color: "#E6C87E", bg: "rgba(240,185,11,0.15)" } : null;
15956
16015
  const sideColor = sideLabel(trade.side) === "Buy" ? "#0ecb81" : "#f6465d";
@@ -15993,7 +16052,10 @@ function PortfolioActivityPanel({
15993
16052
  ] }),
15994
16053
  /* @__PURE__ */ jsxs(CDetailItem, { children: [
15995
16054
  /* @__PURE__ */ jsx(CDLabel, { children: "Settlement" }),
15996
- /* @__PURE__ */ jsx(CDValue, { children: settlementMeta ? /* @__PURE__ */ jsx(ActivityTag, { $color: settlementMeta.color, $bg: settlementMeta.bg, children: settlementLabel }) : "\u2014" })
16055
+ /* @__PURE__ */ jsxs(CDValue, { children: [
16056
+ settlementMeta ? /* @__PURE__ */ jsx(ActivityTag, { $color: settlementMeta.color, $bg: settlementMeta.bg, children: settlementLabel }) : "\u2014",
16057
+ stuckSettling && /* @__PURE__ */ jsx(SettlingIndicator, { stuck: true, style: { marginLeft: 6 } })
16058
+ ] })
15997
16059
  ] }),
15998
16060
  /* @__PURE__ */ jsxs(CDetailItem, { children: [
15999
16061
  /* @__PURE__ */ jsx(CDLabel, { children: "Date" }),
@@ -16018,7 +16080,8 @@ function PortfolioActivityPanel({
16018
16080
  const s = trade.status?.toUpperCase();
16019
16081
  const isSettled = s === "SETTLED" || s === "CONFIRMED" || s === "FILLED" || s === "ALLOCATED";
16020
16082
  const isFailed = s === "SETTLEMENT_FAILED" || s === "REJECTED" || s === "CANCELLED" || s === "CANCELED";
16021
- const isSettling = !isSettled && !isFailed && !!trade.status;
16083
+ const isSettling = tradeIsSettling(trade.status);
16084
+ const stuckSettling = isSettling && isTradeStuck(trade);
16022
16085
  const settlementLabel = isSettled ? "Settled" : isFailed ? "Settlement Failed" : isSettling ? prettyLabel(trade.status) : "\u2014";
16023
16086
  const settlementMeta = isSettled ? { color: "#0ecb81", bg: "rgba(14,203,129,0.1)" } : isFailed ? { color: "#f6465d", bg: "rgba(246,70,93,0.12)" } : isSettling ? { color: "#E6C87E", bg: "rgba(240,185,11,0.15)" } : null;
16024
16087
  const sideColor = sideLabel(trade.side) === "Buy" ? "#0ecb81" : "#f6465d";
@@ -16035,7 +16098,10 @@ function PortfolioActivityPanel({
16035
16098
  ] }) }),
16036
16099
  /* @__PURE__ */ jsx(GridCell, { children: /* @__PURE__ */ jsx(CellText, { children: formatCurrency4(trade.price) }) }),
16037
16100
  /* @__PURE__ */ jsx(GridCell, { children: /* @__PURE__ */ jsx(CellText, { children: formatCurrency4(trade.price * trade.quantity) }) }),
16038
- /* @__PURE__ */ jsx(GridCell, { children: settlementMeta ? /* @__PURE__ */ jsx(ActivityTag, { $color: settlementMeta.color, $bg: settlementMeta.bg, children: settlementLabel }) : /* @__PURE__ */ jsx(CellText, { $muted: true, children: "\u2014" }) }),
16101
+ /* @__PURE__ */ jsx(GridCell, { children: /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: "0.35rem" }, children: [
16102
+ settlementMeta ? /* @__PURE__ */ jsx(ActivityTag, { $color: settlementMeta.color, $bg: settlementMeta.bg, children: settlementLabel }) : /* @__PURE__ */ jsx(CellText, { $muted: true, children: "\u2014" }),
16103
+ stuckSettling && /* @__PURE__ */ jsx(SettlingIndicator, { stuck: true })
16104
+ ] }) }),
16039
16105
  /* @__PURE__ */ jsx(GridCell, { children: /* @__PURE__ */ jsx(CellText, { $muted: true, children: formatTimestamp(trade.executedAt) }) }),
16040
16106
  /* @__PURE__ */ jsx(GridCell, { children: trade.txHash && /* @__PURE__ */ jsx(TxLink, { href: `${_blockExplorerBaseUrl}/${trade.txHash}`, target: "_blank", rel: "noopener noreferrer", title: "View on Arbiscan", children: /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
16041
16107
  /* @__PURE__ */ jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),