@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.js CHANGED
@@ -15122,6 +15122,9 @@ var STATUS_COLOR2 = {
15122
15122
  CLOSED: "#848e9c",
15123
15123
  CANCELLED: "#f6465d"
15124
15124
  };
15125
+ var STUCK_SETTLING_MS = 7e3;
15126
+ var SETTLING_MESSAGE = "Trade settling on-chain";
15127
+ 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.";
15125
15128
  var settlingSpin = styled10.keyframes`
15126
15129
  to { transform: rotate(360deg); }
15127
15130
  `;
@@ -15135,41 +15138,55 @@ var SettlingSpinner = styled10__default.default.span`
15135
15138
  animation: ${settlingSpin} 0.7s linear infinite;
15136
15139
  flex-shrink: 0;
15137
15140
  `;
15141
+ var StuckSettlingGlyph = styled10__default.default.span`
15142
+ display: inline-block;
15143
+ font-size: 11px;
15144
+ line-height: 1;
15145
+ flex-shrink: 0;
15146
+ `;
15138
15147
  var SettlingTip = styled10__default.default.span`
15139
15148
  position: relative;
15140
15149
  display: inline-flex;
15141
15150
  align-items: center;
15142
15151
  cursor: help;
15143
- &::after {
15144
- content: 'Trade settling on-chain';
15145
- position: absolute;
15146
- left: 50%;
15147
- bottom: calc(100% + 8px);
15148
- transform: translateX(-50%) translateY(3px);
15149
- padding: 6px 8px;
15150
- background: rgba(20, 22, 28, 0.97);
15151
- border: 1px solid rgba(255, 255, 255, 0.1);
15152
- border-radius: 6px;
15153
- color: rgba(255, 255, 255, 0.78);
15154
- font-size: 0.66rem;
15155
- font-weight: 400;
15156
- line-height: 1.3;
15157
- letter-spacing: normal;
15158
- text-transform: none;
15159
- white-space: nowrap;
15160
- box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
15161
- opacity: 0;
15162
- visibility: hidden;
15163
- transition: opacity 0.15s ease, transform 0.15s ease;
15164
- pointer-events: none;
15165
- z-index: 2000;
15166
- }
15167
- &:hover::after {
15152
+ `;
15153
+ var SettlingTipBubble = styled10__default.default.span`
15154
+ position: absolute;
15155
+ bottom: calc(100% + 8px);
15156
+ /* Stuck copy is a paragraph, so it anchors to the icon's left edge instead of
15157
+ centring centred it would hang off the left of the row. */
15158
+ left: ${(p) => p.$stuck ? "0" : "50%"};
15159
+ transform: ${(p) => p.$stuck ? "translateX(0)" : "translateX(-50%)"} translateY(3px);
15160
+ width: ${(p) => p.$stuck ? "224px" : "auto"};
15161
+ white-space: ${(p) => p.$stuck ? "normal" : "nowrap"};
15162
+ padding: 6px 8px;
15163
+ background: rgba(20, 22, 28, 0.97);
15164
+ border: 1px solid rgba(255, 255, 255, 0.1);
15165
+ border-radius: 6px;
15166
+ color: rgba(255, 255, 255, 0.78);
15167
+ font-size: 0.66rem;
15168
+ font-weight: 400;
15169
+ line-height: 1.35;
15170
+ letter-spacing: normal;
15171
+ text-transform: none;
15172
+ text-align: left;
15173
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
15174
+ opacity: 0;
15175
+ visibility: hidden;
15176
+ transition: opacity 0.15s ease, transform 0.15s ease;
15177
+ pointer-events: none;
15178
+ z-index: 2000;
15179
+
15180
+ ${SettlingTip}:hover & {
15168
15181
  opacity: 1;
15169
15182
  visibility: visible;
15170
- transform: translateX(-50%) translateY(0);
15183
+ transform: ${(p) => p.$stuck ? "translateX(0)" : "translateX(-50%)"} translateY(0);
15171
15184
  }
15172
15185
  `;
15186
+ var SettlingIndicator = ({ stuck, style }) => /* @__PURE__ */ jsxRuntime.jsxs(SettlingTip, { role: "img", "aria-label": stuck ? "Settlement delayed" : SETTLING_MESSAGE, style, children: [
15187
+ stuck ? /* @__PURE__ */ jsxRuntime.jsx(StuckSettlingGlyph, { "aria-hidden": "true", children: "\u26A0\uFE0F" }) : /* @__PURE__ */ jsxRuntime.jsx(SettlingSpinner, { "aria-hidden": "true" }),
15188
+ /* @__PURE__ */ jsxRuntime.jsx(SettlingTipBubble, { $stuck: stuck, children: stuck ? STUCK_SETTLING_MESSAGE : SETTLING_MESSAGE })
15189
+ ] });
15173
15190
  var ACTIVITY_PAGE_SIZE_DEFAULT = 8;
15174
15191
  var formatCurrency4 = (value, opts) => {
15175
15192
  const min = opts?.minimumFractionDigits ?? 2;
@@ -15177,14 +15194,21 @@ var formatCurrency4 = (value, opts) => {
15177
15194
  return `$${value.toLocaleString("en-US", { minimumFractionDigits: min, maximumFractionDigits: max })}`;
15178
15195
  };
15179
15196
  var formatNumber2 = (value, maximumFractionDigits = 2) => value.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits });
15197
+ var toMillis = (unix) => {
15198
+ if (unix > 1e18) return Math.floor(unix / 1e6);
15199
+ if (unix > 1e14) return Math.floor(unix / 1e3);
15200
+ if (unix > 1e12) return unix;
15201
+ return unix * 1e3;
15202
+ };
15203
+ var SETTLED_TRADE_STATUSES = ["SETTLED", "CONFIRMED", "FILLED", "ALLOCATED"];
15204
+ var FAILED_TRADE_STATUSES = ["SETTLEMENT_FAILED", "REJECTED", "CANCELLED", "CANCELED"];
15205
+ var tradeIsSettling = (status) => {
15206
+ const s = status?.toUpperCase();
15207
+ return !!s && !SETTLED_TRADE_STATUSES.includes(s) && !FAILED_TRADE_STATUSES.includes(s);
15208
+ };
15180
15209
  var formatTimestamp = (unix) => {
15181
15210
  if (!unix) return "\u2014";
15182
- let sec;
15183
- if (unix > 1e18) sec = Math.floor(unix / 1e9);
15184
- else if (unix > 1e14) sec = Math.floor(unix / 1e6);
15185
- else if (unix > 1e12) sec = Math.floor(unix / 1e3);
15186
- else sec = unix;
15187
- return new Date(sec * 1e3).toLocaleString("en-AU", {
15211
+ return new Date(toMillis(unix)).toLocaleString("en-AU", {
15188
15212
  day: "2-digit",
15189
15213
  month: "short",
15190
15214
  hour: "2-digit",
@@ -15248,6 +15272,40 @@ function PortfolioActivityPanel({
15248
15272
  const [closeAllOpen, setCloseAllOpen] = React5.useState(false);
15249
15273
  const [closeAllType, setCloseAllType] = React5.useState("market");
15250
15274
  const [closingAll, setClosingAll] = React5.useState(false);
15275
+ const settlingPositionIds = positions.filter((p) => p.settling).map((p) => p.propertyId);
15276
+ const settlingKey = settlingPositionIds.join(",");
15277
+ const settlingTradeStartByProperty = /* @__PURE__ */ new Map();
15278
+ const settlingTradeDeadlines = [];
15279
+ for (const trade of tradeHistory) {
15280
+ if (!tradeIsSettling(trade.status)) continue;
15281
+ const startedAt = toMillis(trade.executedAt);
15282
+ settlingTradeDeadlines.push(startedAt + STUCK_SETTLING_MS);
15283
+ const earliest = settlingTradeStartByProperty.get(trade.propertyId);
15284
+ if (earliest === void 0 || startedAt < earliest) settlingTradeStartByProperty.set(trade.propertyId, startedAt);
15285
+ }
15286
+ const tradeDeadlineKey = settlingTradeDeadlines.join(",");
15287
+ const settlingSinceRef = React5.useRef(/* @__PURE__ */ new Map());
15288
+ const [now, setNow] = React5.useState(() => Date.now());
15289
+ React5.useEffect(() => {
15290
+ const since = settlingSinceRef.current;
15291
+ const seenAt = Date.now();
15292
+ for (const id of settlingPositionIds) if (!since.has(id)) since.set(id, seenAt);
15293
+ for (const id of [...since.keys()]) if (!settlingPositionIds.includes(id)) since.delete(id);
15294
+ setNow(seenAt);
15295
+ }, [settlingKey]);
15296
+ const positionSettlingStart = (propertyId) => settlingTradeStartByProperty.get(propertyId) ?? settlingSinceRef.current.get(propertyId) ?? now;
15297
+ React5.useEffect(() => {
15298
+ const pending = [
15299
+ ...settlingPositionIds.map((id) => positionSettlingStart(id) + STUCK_SETTLING_MS),
15300
+ ...settlingTradeDeadlines
15301
+ ].filter((deadline) => deadline > now);
15302
+ if (pending.length === 0) return;
15303
+ const delay = Math.min(Math.min(...pending) - now + 50, 2147483647);
15304
+ const timer = setTimeout(() => setNow(Date.now()), delay);
15305
+ return () => clearTimeout(timer);
15306
+ }, [settlingKey, tradeDeadlineKey, now]);
15307
+ const isPositionStuck = (pos) => !!pos.settling && now - positionSettlingStart(pos.propertyId) >= STUCK_SETTLING_MS;
15308
+ const isTradeStuck = (trade) => now - toMillis(trade.executedAt) >= STUCK_SETTLING_MS;
15251
15309
  const hasPositionActions = !!(onClosePosition || onSharePosition || onCloseAllPositions);
15252
15310
  const [posSortKey, setPosSortKey] = React5.useState("asset");
15253
15311
  const [posSortAsc, setPosSortAsc] = React5.useState(true);
@@ -15502,7 +15560,7 @@ function PortfolioActivityPanel({
15502
15560
  /* @__PURE__ */ jsxRuntime.jsx(CCellLabel, { children: "Asset" }),
15503
15561
  /* @__PURE__ */ jsxRuntime.jsxs(CAssetName, { children: [
15504
15562
  pos.tokenName,
15505
- pos.settling && /* @__PURE__ */ jsxRuntime.jsx(SettlingTip, { style: { marginLeft: 6 }, children: /* @__PURE__ */ jsxRuntime.jsx(SettlingSpinner, {}) })
15563
+ pos.settling && /* @__PURE__ */ jsxRuntime.jsx(SettlingIndicator, { stuck: isPositionStuck(pos), style: { marginLeft: 6 } })
15506
15564
  ] })
15507
15565
  ] }),
15508
15566
  /* @__PURE__ */ jsxRuntime.jsxs(CUnitsCell, { children: [
@@ -15594,7 +15652,7 @@ function PortfolioActivityPanel({
15594
15652
  return /* @__PURE__ */ jsxRuntime.jsxs(PositionsRow, { $hasClose: hasPositionActions, children: [
15595
15653
  /* @__PURE__ */ jsxRuntime.jsx(PositionsCell, { children: /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
15596
15654
  /* @__PURE__ */ jsxRuntime.jsx(HoldingsPropertyLink, { onClick: () => onPositionClick?.(pos.tokenName, pos.isIpoAllocation), children: pos.tokenName }),
15597
- pos.settling && /* @__PURE__ */ jsxRuntime.jsx(SettlingTip, { children: /* @__PURE__ */ jsxRuntime.jsx(SettlingSpinner, {}) })
15655
+ pos.settling && /* @__PURE__ */ jsxRuntime.jsx(SettlingIndicator, { stuck: isPositionStuck(pos) })
15598
15656
  ] }) }),
15599
15657
  /* @__PURE__ */ jsxRuntime.jsx(PositionsCell, { children: /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: "#fff", fontSize: "0.8rem" }, children: [
15600
15658
  formatNumber2(pos.totalQuantity),
@@ -15975,7 +16033,8 @@ function PortfolioActivityPanel({
15975
16033
  const s = trade.status?.toUpperCase();
15976
16034
  const isSettled = s === "SETTLED" || s === "CONFIRMED" || s === "FILLED" || s === "ALLOCATED";
15977
16035
  const isFailed = s === "SETTLEMENT_FAILED" || s === "REJECTED" || s === "CANCELLED" || s === "CANCELED";
15978
- const isSettling = !isSettled && !isFailed && !!trade.status;
16036
+ const isSettling = tradeIsSettling(trade.status);
16037
+ const stuckSettling = isSettling && isTradeStuck(trade);
15979
16038
  const settlementLabel = isSettled ? "Settled" : isFailed ? "Settlement Failed" : isSettling ? prettyLabel(trade.status) : "\u2014";
15980
16039
  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;
15981
16040
  const sideColor = sideLabel(trade.side) === "Buy" ? "#0ecb81" : "#f6465d";
@@ -16018,7 +16077,10 @@ function PortfolioActivityPanel({
16018
16077
  ] }),
16019
16078
  /* @__PURE__ */ jsxRuntime.jsxs(CDetailItem, { children: [
16020
16079
  /* @__PURE__ */ jsxRuntime.jsx(CDLabel, { children: "Settlement" }),
16021
- /* @__PURE__ */ jsxRuntime.jsx(CDValue, { children: settlementMeta ? /* @__PURE__ */ jsxRuntime.jsx(ActivityTag, { $color: settlementMeta.color, $bg: settlementMeta.bg, children: settlementLabel }) : "\u2014" })
16080
+ /* @__PURE__ */ jsxRuntime.jsxs(CDValue, { children: [
16081
+ settlementMeta ? /* @__PURE__ */ jsxRuntime.jsx(ActivityTag, { $color: settlementMeta.color, $bg: settlementMeta.bg, children: settlementLabel }) : "\u2014",
16082
+ stuckSettling && /* @__PURE__ */ jsxRuntime.jsx(SettlingIndicator, { stuck: true, style: { marginLeft: 6 } })
16083
+ ] })
16022
16084
  ] }),
16023
16085
  /* @__PURE__ */ jsxRuntime.jsxs(CDetailItem, { children: [
16024
16086
  /* @__PURE__ */ jsxRuntime.jsx(CDLabel, { children: "Date" }),
@@ -16043,7 +16105,8 @@ function PortfolioActivityPanel({
16043
16105
  const s = trade.status?.toUpperCase();
16044
16106
  const isSettled = s === "SETTLED" || s === "CONFIRMED" || s === "FILLED" || s === "ALLOCATED";
16045
16107
  const isFailed = s === "SETTLEMENT_FAILED" || s === "REJECTED" || s === "CANCELLED" || s === "CANCELED";
16046
- const isSettling = !isSettled && !isFailed && !!trade.status;
16108
+ const isSettling = tradeIsSettling(trade.status);
16109
+ const stuckSettling = isSettling && isTradeStuck(trade);
16047
16110
  const settlementLabel = isSettled ? "Settled" : isFailed ? "Settlement Failed" : isSettling ? prettyLabel(trade.status) : "\u2014";
16048
16111
  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;
16049
16112
  const sideColor = sideLabel(trade.side) === "Buy" ? "#0ecb81" : "#f6465d";
@@ -16060,7 +16123,10 @@ function PortfolioActivityPanel({
16060
16123
  ] }) }),
16061
16124
  /* @__PURE__ */ jsxRuntime.jsx(GridCell, { children: /* @__PURE__ */ jsxRuntime.jsx(CellText, { children: formatCurrency4(trade.price) }) }),
16062
16125
  /* @__PURE__ */ jsxRuntime.jsx(GridCell, { children: /* @__PURE__ */ jsxRuntime.jsx(CellText, { children: formatCurrency4(trade.price * trade.quantity) }) }),
16063
- /* @__PURE__ */ jsxRuntime.jsx(GridCell, { children: settlementMeta ? /* @__PURE__ */ jsxRuntime.jsx(ActivityTag, { $color: settlementMeta.color, $bg: settlementMeta.bg, children: settlementLabel }) : /* @__PURE__ */ jsxRuntime.jsx(CellText, { $muted: true, children: "\u2014" }) }),
16126
+ /* @__PURE__ */ jsxRuntime.jsx(GridCell, { children: /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: "0.35rem" }, children: [
16127
+ settlementMeta ? /* @__PURE__ */ jsxRuntime.jsx(ActivityTag, { $color: settlementMeta.color, $bg: settlementMeta.bg, children: settlementLabel }) : /* @__PURE__ */ jsxRuntime.jsx(CellText, { $muted: true, children: "\u2014" }),
16128
+ stuckSettling && /* @__PURE__ */ jsxRuntime.jsx(SettlingIndicator, { stuck: true })
16129
+ ] }) }),
16064
16130
  /* @__PURE__ */ jsxRuntime.jsx(GridCell, { children: /* @__PURE__ */ jsxRuntime.jsx(CellText, { $muted: true, children: formatTimestamp(trade.executedAt) }) }),
16065
16131
  /* @__PURE__ */ jsxRuntime.jsx(GridCell, { children: trade.txHash && /* @__PURE__ */ jsxRuntime.jsx(TxLink, { href: `${_blockExplorerBaseUrl}/${trade.txHash}`, target: "_blank", rel: "noopener noreferrer", title: "View on Arbiscan", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
16066
16132
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),