From 3501dbc50bd57b9abef5af24841075a000a9cc5a Mon Sep 17 00:00:00 2001 From: Ernesto Soltero Date: Thu, 28 Aug 2025 10:18:09 -0700 Subject: [PATCH 01/30] [UmaProber] Add Tether as a currency unit (#20124) ## Reason UmaProber does a "probing" against vasps to try to figure out fx exchange rates. Currently when we probe Coins.ph in the Philippines they only have a preferred currency as Tether but we keep mangling the response because we don't have a representation of tether as a currency unit. GitOrigin-RevId: 591ec8a37b23e852e3ec69c32dddc5750a19da3a --- apps/examples/nodejs-scripts/CHANGELOG.md | 7 ---- apps/examples/nodejs-scripts/package.json | 4 +- apps/examples/oauth-app/CHANGELOG.md | 9 ----- apps/examples/oauth-app/package.json | 6 +-- .../remote-signing-server/CHANGELOG.md | 7 ---- .../remote-signing-server/package.json | 4 +- apps/examples/ui-test-app/CHANGELOG.md | 7 ---- apps/examples/ui-test-app/package.json | 4 +- apps/examples/uma-vasp-cli/CHANGELOG.md | 7 ---- apps/examples/uma-vasp-cli/package.json | 4 +- apps/examples/uma-vasp/CHANGELOG.md | 7 ---- apps/examples/uma-vasp/package.json | 4 +- packages/core/src/utils/currency.ts | 37 ++++++++++++++++++- packages/lightspark-cli/CHANGELOG.md | 7 ---- packages/lightspark-cli/package.json | 4 +- packages/lightspark-sdk/CHANGELOG.md | 6 --- packages/lightspark-sdk/package.json | 2 +- packages/ui/CHANGELOG.md | 6 --- packages/ui/package.json | 2 +- 19 files changed, 52 insertions(+), 82 deletions(-) diff --git a/apps/examples/nodejs-scripts/CHANGELOG.md b/apps/examples/nodejs-scripts/CHANGELOG.md index 7ccda080f..0a3933166 100644 --- a/apps/examples/nodejs-scripts/CHANGELOG.md +++ b/apps/examples/nodejs-scripts/CHANGELOG.md @@ -1,12 +1,5 @@ # @lightsparkdev/nodejs-scripts -## 0.0.32 - -### Patch Changes - -- Updated dependencies [1ee5899] - - @lightsparkdev/lightspark-sdk@1.9.10 - ## 0.0.31 ### Patch Changes diff --git a/apps/examples/nodejs-scripts/package.json b/apps/examples/nodejs-scripts/package.json index ae79205f2..7f8de1aeb 100644 --- a/apps/examples/nodejs-scripts/package.json +++ b/apps/examples/nodejs-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/nodejs-scripts", - "version": "0.0.32", + "version": "0.0.31", "private": true, "description": "Example NodeJS scripts for Lightspark JS SDKs", "main": "index.js", @@ -34,7 +34,7 @@ }, "dependencies": { "@lightsparkdev/core": "1.4.4", - "@lightsparkdev/lightspark-sdk": "1.9.10", + "@lightsparkdev/lightspark-sdk": "1.9.9", "commander": "^11.0.0", "dayjs": "^1.11.7", "lodash-es": "^4.17.21" diff --git a/apps/examples/oauth-app/CHANGELOG.md b/apps/examples/oauth-app/CHANGELOG.md index e256b68db..af6e4f44d 100644 --- a/apps/examples/oauth-app/CHANGELOG.md +++ b/apps/examples/oauth-app/CHANGELOG.md @@ -1,14 +1,5 @@ # @lightsparkdev/oauth-app -## 0.0.60 - -### Patch Changes - -- Updated dependencies [1ee5899] -- Updated dependencies [1ee5899] - - @lightsparkdev/ui@1.1.11 - - @lightsparkdev/lightspark-sdk@1.9.10 - ## 0.0.59 ### Patch Changes diff --git a/apps/examples/oauth-app/package.json b/apps/examples/oauth-app/package.json index 3b63c2b8a..a972996a6 100644 --- a/apps/examples/oauth-app/package.json +++ b/apps/examples/oauth-app/package.json @@ -1,14 +1,14 @@ { "name": "@lightsparkdev/oauth-app", - "version": "0.0.60", + "version": "0.0.59", "private": true, "dependencies": { "@emotion/css": "^11.11.0", "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", - "@lightsparkdev/lightspark-sdk": "1.9.10", + "@lightsparkdev/lightspark-sdk": "1.9.9", "@lightsparkdev/oauth": "*", - "@lightsparkdev/ui": "1.1.11", + "@lightsparkdev/ui": "1.1.10", "react": "^18.2.0", "react-dom": "^18.1.0", "react-router-dom": "6.11.2", diff --git a/apps/examples/remote-signing-server/CHANGELOG.md b/apps/examples/remote-signing-server/CHANGELOG.md index a9e127185..65c004406 100644 --- a/apps/examples/remote-signing-server/CHANGELOG.md +++ b/apps/examples/remote-signing-server/CHANGELOG.md @@ -1,12 +1,5 @@ # @lightsparkdev/remote-signing-server -## 0.0.56 - -### Patch Changes - -- Updated dependencies [1ee5899] - - @lightsparkdev/lightspark-sdk@1.9.10 - ## 0.0.55 ### Patch Changes diff --git a/apps/examples/remote-signing-server/package.json b/apps/examples/remote-signing-server/package.json index 92fc26ae8..e7e29778a 100644 --- a/apps/examples/remote-signing-server/package.json +++ b/apps/examples/remote-signing-server/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/remote-signing-server", - "version": "0.0.56", + "version": "0.0.55", "private": true, "type": "module", "scripts": { @@ -18,7 +18,7 @@ }, "dependencies": { "@lightsparkdev/core": "1.4.4", - "@lightsparkdev/lightspark-sdk": "1.9.10", + "@lightsparkdev/lightspark-sdk": "1.9.9", "express": "^4.18.2" }, "devDependencies": { diff --git a/apps/examples/ui-test-app/CHANGELOG.md b/apps/examples/ui-test-app/CHANGELOG.md index 9e73f7e16..482d53616 100644 --- a/apps/examples/ui-test-app/CHANGELOG.md +++ b/apps/examples/ui-test-app/CHANGELOG.md @@ -1,12 +1,5 @@ # @lightsparkdev/ui-test-app -## 0.0.30 - -### Patch Changes - -- Updated dependencies [1ee5899] - - @lightsparkdev/ui@1.1.11 - ## 0.0.29 ### Patch Changes diff --git a/apps/examples/ui-test-app/package.json b/apps/examples/ui-test-app/package.json index f5a6115ae..8fd9840a7 100644 --- a/apps/examples/ui-test-app/package.json +++ b/apps/examples/ui-test-app/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/ui-test-app", - "version": "0.0.30", + "version": "0.0.29", "description": "Lightspark UI components", "author": "Lightspark Inc.", "main": "./dist/index.js", @@ -30,7 +30,7 @@ "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", "@lightsparkdev/core": "1.4.4", - "@lightsparkdev/ui": "1.1.11", + "@lightsparkdev/ui": "1.1.10", "react": "^18.2.0", "react-dom": "^18.1.0", "react-router-dom": "6.11.2" diff --git a/apps/examples/uma-vasp-cli/CHANGELOG.md b/apps/examples/uma-vasp-cli/CHANGELOG.md index c014487bb..868ecef44 100644 --- a/apps/examples/uma-vasp-cli/CHANGELOG.md +++ b/apps/examples/uma-vasp-cli/CHANGELOG.md @@ -1,12 +1,5 @@ # @lightsparkdev/uma-vasp-cli -## 0.0.37 - -### Patch Changes - -- Updated dependencies [1ee5899] - - @lightsparkdev/lightspark-sdk@1.9.10 - ## 0.0.36 ### Patch Changes diff --git a/apps/examples/uma-vasp-cli/package.json b/apps/examples/uma-vasp-cli/package.json index 4739dbec4..60464c8f3 100644 --- a/apps/examples/uma-vasp-cli/package.json +++ b/apps/examples/uma-vasp-cli/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/uma-vasp-cli", - "version": "0.0.37", + "version": "0.0.36", "private": true, "description": "CLI for the Demo UMA VASP in ../apps/examples/uma-vasp", "main": "./dist/index.js", @@ -44,7 +44,7 @@ "dependencies": { "@inquirer/prompts": "^1.1.3", "@lightsparkdev/core": "1.4.4", - "@lightsparkdev/lightspark-sdk": "1.9.10", + "@lightsparkdev/lightspark-sdk": "1.9.9", "@uma-sdk/core": "^1.3.0", "chalk": "^5.3.0", "commander": "^11.0.0" diff --git a/apps/examples/uma-vasp/CHANGELOG.md b/apps/examples/uma-vasp/CHANGELOG.md index 16e7e3358..64277a2e1 100644 --- a/apps/examples/uma-vasp/CHANGELOG.md +++ b/apps/examples/uma-vasp/CHANGELOG.md @@ -1,12 +1,5 @@ # @lightsparkdev/uma-vasp -## 0.0.57 - -### Patch Changes - -- Updated dependencies [1ee5899] - - @lightsparkdev/lightspark-sdk@1.9.10 - ## 0.0.56 ### Patch Changes diff --git a/apps/examples/uma-vasp/package.json b/apps/examples/uma-vasp/package.json index fc3fb5dcb..fea7dac01 100644 --- a/apps/examples/uma-vasp/package.json +++ b/apps/examples/uma-vasp/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/uma-vasp", - "version": "0.0.57", + "version": "0.0.56", "private": true, "type": "module", "scripts": { @@ -16,7 +16,7 @@ "main": "dist/index.js", "dependencies": { "@lightsparkdev/core": "1.4.4", - "@lightsparkdev/lightspark-sdk": "1.9.10", + "@lightsparkdev/lightspark-sdk": "1.9.9", "@uma-sdk/core": "^1.3.0", "express": "^4.18.2", "express-async-handler": "^1.2.0", diff --git a/packages/core/src/utils/currency.ts b/packages/core/src/utils/currency.ts index f40aae308..3bb7c33bb 100644 --- a/packages/core/src/utils/currency.ts +++ b/packages/core/src/utils/currency.ts @@ -24,6 +24,7 @@ export const CurrencyUnit = { EUR: "EUR", GBP: "GBP", INR: "INR", + USDT: "USDT", Bitcoin: "BITCOIN", Microbitcoin: "MICROBITCOIN", @@ -36,6 +37,7 @@ export const CurrencyUnit = { Php: "PHP", Gbp: "GBP", Inr: "INR", + Usdt: "USDT", } as const; export type CurrencyUnitType = (typeof CurrencyUnit)[keyof typeof CurrencyUnit]; @@ -67,6 +69,7 @@ const standardUnitConversionObj = { [CurrencyUnit.EUR]: (v: number) => v, [CurrencyUnit.GBP]: (v: number) => v, [CurrencyUnit.INR]: (v: number) => v, + [CurrencyUnit.USDT]: (v: number) => v, }; /* Round without decimals since we're returning cents: */ @@ -97,6 +100,7 @@ const CONVERSION_MAP = { [CurrencyUnit.EUR]: toBitcoinConversion, [CurrencyUnit.GBP]: toBitcoinConversion, [CurrencyUnit.INR]: toBitcoinConversion, + [CurrencyUnit.USDT]: toBitcoinConversion, }, [CurrencyUnit.MICROBITCOIN]: { [CurrencyUnit.BITCOIN]: (v: number) => v / 1_000_000, @@ -111,6 +115,7 @@ const CONVERSION_MAP = { [CurrencyUnit.EUR]: toMicrobitcoinConversion, [CurrencyUnit.GBP]: toMicrobitcoinConversion, [CurrencyUnit.INR]: toMicrobitcoinConversion, + [CurrencyUnit.USDT]: toMicrobitcoinConversion, }, [CurrencyUnit.MILLIBITCOIN]: { [CurrencyUnit.BITCOIN]: (v: number) => v / 1_000, @@ -125,6 +130,7 @@ const CONVERSION_MAP = { [CurrencyUnit.EUR]: toMillibitcoinConversion, [CurrencyUnit.GBP]: toMillibitcoinConversion, [CurrencyUnit.INR]: toMillibitcoinConversion, + [CurrencyUnit.USDT]: toMillibitcoinConversion, }, [CurrencyUnit.MILLISATOSHI]: { [CurrencyUnit.BITCOIN]: (v: number) => v / 100_000_000_000, @@ -139,6 +145,7 @@ const CONVERSION_MAP = { [CurrencyUnit.EUR]: toMillisatoshiConversion, [CurrencyUnit.GBP]: toMillisatoshiConversion, [CurrencyUnit.INR]: toMillisatoshiConversion, + [CurrencyUnit.USDT]: toMillisatoshiConversion, }, [CurrencyUnit.NANOBITCOIN]: { [CurrencyUnit.BITCOIN]: (v: number) => v / 1_000_000_000, @@ -153,6 +160,7 @@ const CONVERSION_MAP = { [CurrencyUnit.EUR]: toNanobitcoinConversion, [CurrencyUnit.GBP]: toNanobitcoinConversion, [CurrencyUnit.INR]: toNanobitcoinConversion, + [CurrencyUnit.USDT]: toNanobitcoinConversion, }, [CurrencyUnit.SATOSHI]: { [CurrencyUnit.BITCOIN]: (v: number) => v / 100_000_000, @@ -167,6 +175,7 @@ const CONVERSION_MAP = { [CurrencyUnit.EUR]: toSatoshiConversion, [CurrencyUnit.GBP]: toSatoshiConversion, [CurrencyUnit.INR]: toSatoshiConversion, + [CurrencyUnit.USDT]: toSatoshiConversion, }, [CurrencyUnit.USD]: standardUnitConversionObj, [CurrencyUnit.MXN]: standardUnitConversionObj, @@ -174,6 +183,7 @@ const CONVERSION_MAP = { [CurrencyUnit.EUR]: standardUnitConversionObj, [CurrencyUnit.GBP]: standardUnitConversionObj, [CurrencyUnit.INR]: standardUnitConversionObj, + [CurrencyUnit.USDT]: standardUnitConversionObj, }; export function convertCurrencyAmountValue( @@ -241,6 +251,7 @@ export type CurrencyMap = { [CurrencyUnit.EUR]: number; [CurrencyUnit.GBP]: number; [CurrencyUnit.INR]: number; + [CurrencyUnit.USDT]: number; [CurrencyUnit.FUTURE_VALUE]: number; formatted: { sats: string; @@ -258,6 +269,7 @@ export type CurrencyMap = { [CurrencyUnit.EUR]: string; [CurrencyUnit.GBP]: string; [CurrencyUnit.INR]: string; + [CurrencyUnit.USDT]: string; [CurrencyUnit.FUTURE_VALUE]: string; }; isZero: boolean; @@ -459,6 +471,7 @@ function convertCurrencyAmountValues( mibtc: CurrencyUnit.MICROBITCOIN, mlbtc: CurrencyUnit.MILLIBITCOIN, nbtc: CurrencyUnit.NANOBITCOIN, + usdt: CurrencyUnit.USDT, }; return Object.entries(namesToUnits).reduce( (acc, [name, unit]) => { @@ -505,8 +518,21 @@ export function mapCurrencyAmount( * preferred_currency_unit on CurrencyAmount types: */ const conversionOverride = getPreferredConversionOverride(currencyAmountArg); - const { sats, msats, btc, usd, mxn, php, mibtc, mlbtc, nbtc, eur, gbp, inr } = - convertCurrencyAmountValues(unit, value, unitsPerBtc, conversionOverride); + const { + sats, + msats, + btc, + usd, + mxn, + php, + mibtc, + mlbtc, + nbtc, + eur, + gbp, + inr, + usdt, + } = convertCurrencyAmountValues(unit, value, unitsPerBtc, conversionOverride); const mapWithCurrencyUnits = { [CurrencyUnit.BITCOIN]: btc, @@ -521,6 +547,7 @@ export function mapCurrencyAmount( [CurrencyUnit.MICROBITCOIN]: mibtc, [CurrencyUnit.MILLIBITCOIN]: mlbtc, [CurrencyUnit.NANOBITCOIN]: nbtc, + [CurrencyUnit.USDT]: usdt, [CurrencyUnit.FUTURE_VALUE]: NaN, formatted: { [CurrencyUnit.BITCOIN]: formatCurrencyStr({ @@ -571,6 +598,10 @@ export function mapCurrencyAmount( value: inr, unit: CurrencyUnit.INR, }), + [CurrencyUnit.USDT]: formatCurrencyStr({ + value: usdt, + unit: CurrencyUnit.USDT, + }), [CurrencyUnit.FUTURE_VALUE]: "-", }, }; @@ -651,6 +682,8 @@ export const abbrCurrencyUnit = (unit: CurrencyUnitType) => { return "GBP"; case CurrencyUnit.INR: return "INR"; + case CurrencyUnit.USDT: + return "USDT"; } return "Unsupported CurrencyUnit"; }; diff --git a/packages/lightspark-cli/CHANGELOG.md b/packages/lightspark-cli/CHANGELOG.md index 9b5b119aa..8c7a62596 100644 --- a/packages/lightspark-cli/CHANGELOG.md +++ b/packages/lightspark-cli/CHANGELOG.md @@ -1,12 +1,5 @@ # @lightsparkdev/lightspark-cli -## 0.1.10 - -### Patch Changes - -- Updated dependencies [1ee5899] - - @lightsparkdev/lightspark-sdk@1.9.10 - ## 0.1.9 ### Patch Changes diff --git a/packages/lightspark-cli/package.json b/packages/lightspark-cli/package.json index 35d130f3c..5ae1944b3 100644 --- a/packages/lightspark-cli/package.json +++ b/packages/lightspark-cli/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/lightspark-cli", - "version": "0.1.10", + "version": "0.1.9", "description": "CLI for the Lightspark JS sdk", "main": "./dist/index.js", "bin": { @@ -46,7 +46,7 @@ "@inquirer/prompts": "^1.1.3", "@lightsparkdev/core": "1.4.4", "@lightsparkdev/crypto-wasm": "0.1.18", - "@lightsparkdev/lightspark-sdk": "1.9.10", + "@lightsparkdev/lightspark-sdk": "1.9.9", "commander": "^11.0.0", "dayjs": "^1.11.7", "dotenv": "^16.3.1", diff --git a/packages/lightspark-sdk/CHANGELOG.md b/packages/lightspark-sdk/CHANGELOG.md index 1c4dbbe3c..1fde846d4 100644 --- a/packages/lightspark-sdk/CHANGELOG.md +++ b/packages/lightspark-sdk/CHANGELOG.md @@ -1,11 +1,5 @@ # @lightsparkdev/lightspark-sdk -## 1.9.10 - -### Patch Changes - -- 1ee5899: feat: exposing idempotency field on payInvoice method - ## 1.9.9 ### Patch Changes diff --git a/packages/lightspark-sdk/package.json b/packages/lightspark-sdk/package.json index 9021d304d..8121ff207 100644 --- a/packages/lightspark-sdk/package.json +++ b/packages/lightspark-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/lightspark-sdk", - "version": "1.9.10", + "version": "1.9.9", "description": "Lightspark JS SDK", "author": "Lightspark Inc.", "keywords": [ diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index 03dbb21be..1cd680048 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,11 +1,5 @@ # @lightsparkdev/ui -## 1.1.11 - -### Patch Changes - -- 1ee5899: feat: adding hint for birthday input - ## 1.1.10 ### Patch Changes diff --git a/packages/ui/package.json b/packages/ui/package.json index 24720591e..e0c7a6a39 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/ui", - "version": "1.1.11", + "version": "1.1.10", "main": "./dist/index.cjs", "module": "./dist/index.js", "types": "./dist/index.d.ts", From fc09146530cc5cb45b1e01cbecf2437f31ff3606 Mon Sep 17 00:00:00 2001 From: Brian Siao Tick Chong Date: Fri, 29 Aug 2025 12:22:42 -0700 Subject: [PATCH 02/30] [nage] add feature to create api tokens (#20167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - adds mutation to create new umaaas api tokens - shows uma permissions when creating tokens - shows error toasts and state when token info not provided - adds NageModal to be used for token created - updates Modal to use overlayBackground prop instead of voidOverlayBackground - adds CopyInput component for nage - fixes CentralLoadingCircle color - fixes Checkbox background theme color (theme.info) ![Screenshot 2025-08-28 at 5.53.33 PM.png](https://app.graphite.dev/user-attachments/assets/ca762d4b-2034-416b-8a75-24743441560d.png) ![Screenshot 2025-08-28 at 5.40.12 PM.png](https://app.graphite.dev/user-attachments/assets/4e9942a5-4b80-47a8-be34-2014c9ce000d.png) GitOrigin-RevId: 3ff84388c5cb04bb85265067868e7a025763f857 --- packages/ui/src/components/Button.tsx | 15 ++++++++++----- packages/ui/src/components/Modal.tsx | 17 +++++++++++------ packages/ui/src/icons/central/LoadingCircle.tsx | 2 +- packages/ui/src/styles/themes.tsx | 1 + 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/components/Button.tsx b/packages/ui/src/components/Button.tsx index c933a58c1..402bf696f 100644 --- a/packages/ui/src/components/Button.tsx +++ b/packages/ui/src/components/Button.tsx @@ -170,6 +170,10 @@ function resolveProps(props: ButtonProps, theme: Theme) { const size = resolveProp(sizeProp, kind, "defaultSize", theme); const defaultPaddingsY = resolveProp(null, kind, "defaultPaddingsY", theme); const defaultPaddingY = defaultPaddingsY[size]; + const numericDefaultPaddingY = + typeof defaultPaddingY === "number" + ? defaultPaddingY + : defaultPaddingY[paddingYType]; const defaultPaddingsX = resolveProp(null, kind, "defaultPaddingsX", theme); const defaultPaddingX = defaultPaddingsX[size]; @@ -219,11 +223,12 @@ function resolveProps(props: ButtonProps, theme: Theme) { kind, size, typography, - paddingY: - typeof defaultPaddingY === "number" - ? defaultPaddingY - : defaultPaddingY[paddingYType], - paddingX: defaultPaddingX, + ...(props.text + ? { + paddingY: numericDefaultPaddingY, + paddingX: defaultPaddingX, + } + : { paddingY: numericDefaultPaddingY, paddingX: numericDefaultPaddingY }), borderRadius: kind === "roundSingleChar" || kind === "roundIcon" ? 999 diff --git a/packages/ui/src/components/Modal.tsx b/packages/ui/src/components/Modal.tsx index 415a5c37e..2c986812c 100644 --- a/packages/ui/src/components/Modal.tsx +++ b/packages/ui/src/components/Modal.tsx @@ -19,6 +19,7 @@ import { standardContentInset, standardFocusOutline, } from "../styles/common.js"; +import { getColor, type ThemeOrColorKey } from "../styles/themes.js"; import { Spacing } from "../styles/tokens/spacing.js"; import { overflowAutoWithoutScrollbars } from "../styles/utils.js"; import { z } from "../styles/z-index.js"; @@ -121,7 +122,7 @@ type ModalProps = { drawerCloseButton?: boolean; drawerAlignBottom?: boolean | undefined; drawerDisableTouchMove?: boolean | undefined; - voidOverlayBackground?: boolean | undefined; + overlayBackground?: ThemeOrColorKey | undefined; }; export function Modal({ @@ -162,7 +163,7 @@ export function Modal({ drawerCloseButton = true, drawerAlignBottom, drawerDisableTouchMove, - voidOverlayBackground, + overlayBackground, }: ModalProps) { const visibleChangedRef = useRef(false); const [visibleChanged, setVisibleChanged] = useState(false); @@ -432,7 +433,7 @@ export function Modal({ {!(smKind === "fullscreen" && bp.isSm()) ? ( ) : null} ` +const ModalOverlay = styled.div<{ + overlayBackground?: ThemeOrColorKey | undefined; +}>` position: fixed; bottom: 0; left: 0; z-index: ${z.modalOverlay}; width: 100vw; height: 100vh; - background: ${({ voidOverlayBackground }) => - voidOverlayBackground ? "transparent" : "rgba(0, 0, 0, 0.5)"}; + background: ${({ overlayBackground, theme }) => + overlayBackground + ? getColor(theme, overlayBackground) + : "rgba(0, 0, 0, 0.5)"}; `; const ModalContainer = styled.div<{ top: number }>` diff --git a/packages/ui/src/icons/central/LoadingCircle.tsx b/packages/ui/src/icons/central/LoadingCircle.tsx index 2e0dc4075..a206ed163 100644 --- a/packages/ui/src/icons/central/LoadingCircle.tsx +++ b/packages/ui/src/icons/central/LoadingCircle.tsx @@ -14,7 +14,7 @@ export function LoadingCircle({ > diff --git a/packages/ui/src/styles/themes.tsx b/packages/ui/src/styles/themes.tsx index 5778eb328..f5ef837e7 100644 --- a/packages/ui/src/styles/themes.tsx +++ b/packages/ui/src/styles/themes.tsx @@ -840,6 +840,7 @@ const nageLightTheme = extend(lightTheme, { bg: colors["gray-100"], smBg: colors["gray-100"], text: colors["gray-950"], + info: colors["gray-950"], secondary: colors["gray-500"], tertiary: colors.gray6, inputBackground: colors["gray-050"], From d13e00cb21381fcd714be74ff9307816637ee08c Mon Sep 17 00:00:00 2001 From: Brian Siao Tick Chong Date: Fri, 29 Aug 2025 14:54:51 -0700 Subject: [PATCH 03/30] [nage] add delete api token feature (#20186) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - adds feature to delete api tokens - updates Table to handle triple dot button columns - updates dropdown component to handle toReactNodesArgs - adds cancel button to NageModal - adds theme for danger button (delete button) - adds card for token display ![Screenshot 2025-08-28 at 6.55.17 PM.png](https://app.graphite.dev/user-attachments/assets/3288bd01-c4a9-4d9c-8610-95f3f3c93db6.png) ![Screenshot 2025-08-28 at 7.15.39 PM.png](https://app.graphite.dev/user-attachments/assets/5744aefc-2fdc-414a-8afb-220346a869ff.png) GitOrigin-RevId: fc011969714490d3541b0b217abe081dca62cee5 --- packages/ui/src/components/Dropdown.tsx | 14 ++++++---- packages/ui/src/components/Table/Table.tsx | 31 ++++++++++++++++++++++ packages/ui/src/styles/colors.tsx | 2 ++ packages/ui/src/styles/themes.tsx | 9 +++++++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/Dropdown.tsx b/packages/ui/src/components/Dropdown.tsx index cc272e3c9..494517d28 100644 --- a/packages/ui/src/components/Dropdown.tsx +++ b/packages/ui/src/components/Dropdown.tsx @@ -35,6 +35,10 @@ import { smHeaderLogoMarginLeft } from "../styles/constants.js"; import { themeOr, type ThemeProp, type WithTheme } from "../styles/themes.js"; import { z } from "../styles/z-index.js"; import { type NewRoutesType } from "../types/index.js"; +import { + toReactNodes, + type ToReactNodesArgs, +} from "../utils/toReactNodes/toReactNodes.js"; import { Button } from "./Button.js"; import { Icon } from "./Icon/Icon.js"; import { type IconName } from "./Icon/types.js"; @@ -47,7 +51,7 @@ type DropdownItemGetProps = WithTheme<{ }>; type DropdownItemType = { - label: string; + label: ToReactNodesArgs; getIcon?: | (({ theme, dropdownItem }: DropdownItemGetProps) => | { @@ -394,7 +398,7 @@ export function Dropdown({ > {dropdownItems?.map((dropdownItem, i) => { return ( -
  • +
  • {dropdownItemIconNode} - {dropdownItem.label} + {toReactNodes(dropdownItem.label)} ); } @@ -495,7 +499,7 @@ function DropdownItem({ dropdownItem, onClick }: DropdownItemProps) { return ( {dropdownItemIconNode} - {dropdownItem.label} + {toReactNodes(dropdownItem.label)}
    {"options" in dropdownItem.toggle ? ( @@ -514,7 +518,7 @@ function DropdownItem({ dropdownItem, onClick }: DropdownItemProps) { onClick={onClickDropdownItem} > {dropdownItemIconNode} - {dropdownItem.label} + {toReactNodes(dropdownItem.label)} ); } diff --git a/packages/ui/src/components/Table/Table.tsx b/packages/ui/src/components/Table/Table.tsx index c1d033c50..ba4acd165 100644 --- a/packages/ui/src/components/Table/Table.tsx +++ b/packages/ui/src/components/Table/Table.tsx @@ -30,7 +30,9 @@ import { } from "../../styles/utils.js"; import { type NewRoutesType } from "../../types/index.js"; import { type ElideObjArgs } from "../../utils/strings.js"; +import { type ToReactNodesArgs } from "../../utils/toReactNodes/toReactNodes.js"; import { ClipboardTextField } from "../ClipboardTextField.js"; +import { Dropdown } from "../Dropdown.js"; import { Icon } from "../Icon/Icon.js"; import { type IconName } from "../Icon/types.js"; import { InfoIconTooltip } from "../InfoIconTooltip.js"; @@ -117,6 +119,10 @@ export type TableProps> = { rowHoverEffect?: "border" | "background" | "none" | undefined; customComponents?: CustomTableComponents; minHeight?: number; + tripleDotsMenuItems?: { + text: ToReactNodesArgs; + onClick: (row: T) => void; + }[]; }; export function Table>({ @@ -127,6 +133,7 @@ export function Table>({ emptyState, clipboardCallbacks, customComponents, + tripleDotsMenuItems, rowHoverEffect = "border", minHeight = 300, }: TableProps) { @@ -312,6 +319,30 @@ export function Table>({ ], ); + if (tripleDotsMenuItems) { + mappedColumns.push({ + header: (context: HeaderContext) => "", + accessorKey: "tripleDots", + cell: (context) => ( + ({ + label: item.text, + onClick: () => item.onClick(context.row.original), + })) || [] + } + /> + ), + }); + } + const tableInstance = useReactTable({ columns: mappedColumns, data, diff --git a/packages/ui/src/styles/colors.tsx b/packages/ui/src/styles/colors.tsx index a204dc39d..3086d6d07 100644 --- a/packages/ui/src/styles/colors.tsx +++ b/packages/ui/src/styles/colors.tsx @@ -150,6 +150,8 @@ const baseColors = { red42a20: "#D800272D", red42a30: "#D800273F", errorText: "#E41C1B", + errorText2: "#D80027", + errorText3: "#FF4444", errorBackground: "#FEE2E1", // yellow primary, diff --git a/packages/ui/src/styles/themes.tsx b/packages/ui/src/styles/themes.tsx index f5ef837e7..341465f1c 100644 --- a/packages/ui/src/styles/themes.tsx +++ b/packages/ui/src/styles/themes.tsx @@ -819,6 +819,15 @@ const nageBaseSettings = { defaultActiveBackgroundColor: "black-10", defaultActiveBorderColor: "transparent", }, + danger: { + defaultColor: "gray-050", + defaultBackgroundColor: "errorText", + defaultBorderColor: "transparent", + defaultHoverBackgroundColor: "errorText2", + defaultHoverBorderColor: "transparent", + defaultActiveBackgroundColor: "errorText3", + defaultActiveBorderColor: "transparent", + }, }, }), loading: merge(loadingThemeBase, { From f6838ad3d19f1384cf46117134a6c63b3ae43892 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Fri, 29 Aug 2025 16:40:08 -0700 Subject: [PATCH 04/30] [Nage] Fix command center typography and add auto scroll (#19868) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ![Screenshot 2025-08-18 at 3.16.41 PM.png](https://app.graphite.dev/user-attachments/assets/a558cbf4-8361-4b46-a7fd-3275f7a9503e.png) GitOrigin-RevId: fec8ca6cdaae37018643f176f834d464492cc424 --- packages/ui/src/styles/tokens/typography.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/styles/tokens/typography.ts b/packages/ui/src/styles/tokens/typography.ts index b48f91450..22e75bde7 100644 --- a/packages/ui/src/styles/tokens/typography.ts +++ b/packages/ui/src/styles/tokens/typography.ts @@ -4726,7 +4726,7 @@ function getTypographyTypes(fontFamilies: FontFamilies): TypographyTypes { [TypographyType.Label]: { [TokenSize.Large]: { fontFamily: `${fontFamilies.main}`, - fontWeight: `${FONT_WEIGHTS.main.Medium}`, + fontWeight: `${FONT_WEIGHTS.main.Book}`, lineHeight: `${LINE_HEIGHTS[TypographyGroup.Nage]["xl"]}`, fontSize: `${FONT_SIZE[TypographyGroup.Nage]["lg"]}`, letterSpacing: `${LETTER_SPACING[TypographyGroup.Nage].normal}`, @@ -4738,7 +4738,7 @@ function getTypographyTypes(fontFamilies: FontFamilies): TypographyTypes { }, [TokenSize.Medium]: { fontFamily: `${fontFamilies.main}`, - fontWeight: `${FONT_WEIGHTS.main.Medium}`, + fontWeight: `${FONT_WEIGHTS.main.Book}`, lineHeight: `${LINE_HEIGHTS[TypographyGroup.Nage]["md"]}`, fontSize: `${FONT_SIZE[TypographyGroup.Nage]["md"]}`, letterSpacing: `${LETTER_SPACING[TypographyGroup.Nage].normal}`, @@ -4750,7 +4750,7 @@ function getTypographyTypes(fontFamilies: FontFamilies): TypographyTypes { }, [TokenSize.Small]: { fontFamily: `${fontFamilies.main}`, - fontWeight: `${FONT_WEIGHTS.main.Medium}`, + fontWeight: `${FONT_WEIGHTS.main.Book}`, lineHeight: `${LINE_HEIGHTS[TypographyGroup.Nage]["xs"]}`, fontSize: `${FONT_SIZE[TypographyGroup.Nage]["xs"]}`, letterSpacing: `${LETTER_SPACING[TypographyGroup.Nage].normal}`, @@ -4762,7 +4762,7 @@ function getTypographyTypes(fontFamilies: FontFamilies): TypographyTypes { }, [TokenSize.ExtraSmall]: { fontFamily: `${fontFamilies.main}`, - fontWeight: `${FONT_WEIGHTS.main.Medium}`, + fontWeight: `${FONT_WEIGHTS.main.Book}`, lineHeight: `${LINE_HEIGHTS[TypographyGroup.Nage]["3xs"]}`, fontSize: `${FONT_SIZE[TypographyGroup.Nage]["2xs"]}`, letterSpacing: `${LETTER_SPACING[TypographyGroup.Nage].normal}`, @@ -5194,7 +5194,7 @@ function getTypographyTypes(fontFamilies: FontFamilies): TypographyTypes { [TypographyType.Label]: { [TokenSize.Large]: { fontFamily: `${fontFamilies.main}`, - fontWeight: `${FONT_WEIGHTS.main.Medium}`, + fontWeight: `${FONT_WEIGHTS.main.Book}`, lineHeight: `${LINE_HEIGHTS[TypographyGroup.Nage]["xl"]}`, fontSize: `${FONT_SIZE[TypographyGroup.Nage]["lg"]}`, letterSpacing: `${LETTER_SPACING[TypographyGroup.Nage].normal}`, @@ -5206,7 +5206,7 @@ function getTypographyTypes(fontFamilies: FontFamilies): TypographyTypes { }, [TokenSize.Medium]: { fontFamily: `${fontFamilies.main}`, - fontWeight: `${FONT_WEIGHTS.main.Medium}`, + fontWeight: `${FONT_WEIGHTS.main.Book}`, lineHeight: `${LINE_HEIGHTS[TypographyGroup.Nage]["md"]}`, fontSize: `${FONT_SIZE[TypographyGroup.Nage]["md"]}`, letterSpacing: `${LETTER_SPACING[TypographyGroup.Nage].normal}`, @@ -5218,7 +5218,7 @@ function getTypographyTypes(fontFamilies: FontFamilies): TypographyTypes { }, [TokenSize.Small]: { fontFamily: `${fontFamilies.main}`, - fontWeight: `${FONT_WEIGHTS.main.Medium}`, + fontWeight: `${FONT_WEIGHTS.main.Book}`, lineHeight: `${LINE_HEIGHTS[TypographyGroup.Nage]["xs"]}`, fontSize: `${FONT_SIZE[TypographyGroup.Nage]["xs"]}`, letterSpacing: `${LETTER_SPACING[TypographyGroup.Nage].normal}`, From 3d739e4d369867fc43cdea9f3d0643218adbeec1 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Wed, 3 Sep 2025 14:36:08 -0700 Subject: [PATCH 05/30] [Nage] Add update name / start of invitations into nage settings (#20194) ## Overview - Implement update name path - Add current user query - Rename update name to update profile per designs GitOrigin-RevId: 6a08f2bc9e63972bd317cda7298fe32b0490e6b2 --- packages/ui/src/components/Table/Table.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/ui/src/components/Table/Table.tsx b/packages/ui/src/components/Table/Table.tsx index ba4acd165..eadf8da00 100644 --- a/packages/ui/src/components/Table/Table.tsx +++ b/packages/ui/src/components/Table/Table.tsx @@ -97,6 +97,7 @@ const isMultilineCell = (value: unknown): value is MultilineCell => { interface Column> { header: TableColumnHeaderInfo; accessorKey: keyof T; + function?: (context: CellContext) => ReactNode; } export type CustomTableComponents = { @@ -169,6 +170,10 @@ export function Table>({ ), accessorKey: column.accessorKey.toString(), cell: (context: CellContext) => { + if (column.function && typeof column.function === "function") { + return column.function(context); + } + const value = context.getValue(); let content: ReactNode = null; From d123ce5dbc424c81db79b58e43f949013be8040f Mon Sep 17 00:00:00 2001 From: Lightspark Eng Date: Wed, 3 Sep 2025 21:46:27 +0000 Subject: [PATCH 06/30] CI update lock file for PR --- yarn.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/yarn.lock b/yarn.lock index 67537c641..e3b2387de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2203,7 +2203,7 @@ __metadata: "@lightsparkdev/core": "npm:1.4.4" "@lightsparkdev/crypto-wasm": "npm:0.1.18" "@lightsparkdev/eslint-config": "npm:*" - "@lightsparkdev/lightspark-sdk": "npm:1.9.10" + "@lightsparkdev/lightspark-sdk": "npm:1.9.9" "@lightsparkdev/tsconfig": "npm:0.0.1" "@types/jsonwebtoken": "npm:^9.0.2" "@types/node": "npm:^20.2.5" @@ -2229,7 +2229,7 @@ __metadata: languageName: unknown linkType: soft -"@lightsparkdev/lightspark-sdk@npm:1.9.10, @lightsparkdev/lightspark-sdk@workspace:packages/lightspark-sdk": +"@lightsparkdev/lightspark-sdk@npm:1.9.9, @lightsparkdev/lightspark-sdk@workspace:packages/lightspark-sdk": version: 0.0.0-use.local resolution: "@lightsparkdev/lightspark-sdk@workspace:packages/lightspark-sdk" dependencies: @@ -2270,7 +2270,7 @@ __metadata: dependencies: "@lightsparkdev/core": "npm:1.4.4" "@lightsparkdev/eslint-config": "npm:*" - "@lightsparkdev/lightspark-sdk": "npm:1.9.10" + "@lightsparkdev/lightspark-sdk": "npm:1.9.9" "@types/jest": "npm:^29.5.3" "@types/node": "npm:^20.2.5" commander: "npm:^11.0.0" @@ -2296,10 +2296,10 @@ __metadata: "@emotion/react": "npm:^11.11.0" "@emotion/styled": "npm:^11.11.0" "@lightsparkdev/eslint-config": "npm:*" - "@lightsparkdev/lightspark-sdk": "npm:1.9.10" + "@lightsparkdev/lightspark-sdk": "npm:1.9.9" "@lightsparkdev/oauth": "npm:*" "@lightsparkdev/tsconfig": "npm:*" - "@lightsparkdev/ui": "npm:1.1.11" + "@lightsparkdev/ui": "npm:1.1.10" "@types/jest": "npm:^29.5.3" "@types/node": "npm:^20.2.5" "@types/react": "npm:^18.2.12" @@ -2348,7 +2348,7 @@ __metadata: resolution: "@lightsparkdev/remote-signing-server@workspace:apps/examples/remote-signing-server" dependencies: "@lightsparkdev/core": "npm:1.4.4" - "@lightsparkdev/lightspark-sdk": "npm:1.9.10" + "@lightsparkdev/lightspark-sdk": "npm:1.9.9" "@lightsparkdev/tsconfig": "npm:*" "@types/jest": "npm:^29.5.3" "@types/node": "npm:^20.2.5" @@ -2395,7 +2395,7 @@ __metadata: "@lightsparkdev/core": "npm:1.4.4" "@lightsparkdev/eslint-config": "npm:*" "@lightsparkdev/tsconfig": "npm:*" - "@lightsparkdev/ui": "npm:1.1.11" + "@lightsparkdev/ui": "npm:1.1.10" "@lightsparkdev/vite": "npm:*" "@testing-library/jest-dom": "npm:^6.1.2" "@types/jest": "npm:^29.5.3" @@ -2420,7 +2420,7 @@ __metadata: languageName: unknown linkType: soft -"@lightsparkdev/ui@npm:1.1.11, @lightsparkdev/ui@workspace:packages/ui": +"@lightsparkdev/ui@npm:1.1.10, @lightsparkdev/ui@workspace:packages/ui": version: 0.0.0-use.local resolution: "@lightsparkdev/ui@workspace:packages/ui" dependencies: @@ -2491,7 +2491,7 @@ __metadata: "@inquirer/prompts": "npm:^1.1.3" "@lightsparkdev/core": "npm:1.4.4" "@lightsparkdev/eslint-config": "npm:*" - "@lightsparkdev/lightspark-sdk": "npm:1.9.10" + "@lightsparkdev/lightspark-sdk": "npm:1.9.9" "@lightsparkdev/tsconfig": "npm:0.0.1" "@types/chalk": "npm:^2.2.0" "@types/node": "npm:^20.2.5" @@ -2516,7 +2516,7 @@ __metadata: resolution: "@lightsparkdev/uma-vasp@workspace:apps/examples/uma-vasp" dependencies: "@lightsparkdev/core": "npm:1.4.4" - "@lightsparkdev/lightspark-sdk": "npm:1.9.10" + "@lightsparkdev/lightspark-sdk": "npm:1.9.9" "@lightsparkdev/tsconfig": "npm:*" "@types/body-parser": "npm:^1.19.5" "@types/express": "npm:^4.17.21" From 09990566df3e84e0941eadd3f3f7b8a1232656ff Mon Sep 17 00:00:00 2001 From: Brian Siao Tick Chong Date: Thu, 4 Sep 2025 10:00:31 -0700 Subject: [PATCH 07/30] [nage] add filter query params for DataManagerTable (#20270) - adds option to DataManagerTable to save/load filters from URL query params - this allows refreshing the page and persisting filter state - also will be used for the CommandCenter in the future - must be only enabled via boolean because ops pages can have multiple DataManagerTables and we don't want the same filters applying to every table; will need to figure out a solution for that later - fixes date range filter for transactions table GitOrigin-RevId: 0b7220cefe53c79b1eceab0282b95c4d6db965b2 --- .../DataManagerTable/DataManagerTable.tsx | 284 +++++++++++++++++- 1 file changed, 283 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx b/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx index a2bcc464a..776bbcaa9 100644 --- a/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx +++ b/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx @@ -1,6 +1,8 @@ import styled from "@emotion/styled"; import { Fragment, useEffect, useState, type ComponentProps } from "react"; +import { CurrencyUnit } from "@lightsparkdev/core"; +import { useSearchParams } from "react-router-dom"; import { type useClipboard } from "../../hooks/useClipboard.js"; import { bp, useBreakpoints } from "../../styles/breakpoints.js"; import { colors } from "../../styles/colors.js"; @@ -131,6 +133,7 @@ export type DataManagerTableProps< } | undefined; minHeight?: number | undefined; + enableURLFilters?: boolean | undefined; }; export type DataManagerTableState> = Record< @@ -183,11 +186,224 @@ type PageCursorState = { }; }; +// Utility functions for URL state management +function saveFiltersToURL>( + searchParams: URLSearchParams, + filters: Filter[], + filterStates: DataManagerTableState, + currentPage: number, +): URLSearchParams { + const newSearchParams = new URLSearchParams(searchParams); + + // Clear all existing filter-related params first + filters.forEach((filter) => { + newSearchParams.delete(String(filter.accessorKey)); + }); + + // Set new filter values based on filter accessorKeys + filters.forEach((filter) => { + const filterState = filterStates[filter.accessorKey]; + if (filterState && filterState.isApplied) { + if ( + filter.type === FilterType.STRING || + filter.type === FilterType.ENUM || + filter.type === FilterType.ID + ) { + const appliedValues = ( + filterState as StringFilterState | EnumFilterState | IdFilterState + ).appliedValues; + if (appliedValues && appliedValues.length > 0) { + newSearchParams.set( + String(filter.accessorKey), + appliedValues.join(","), + ); + } + } else if (filter.type === FilterType.BOOLEAN) { + const appliedValue = (filterState as BooleanFilterState).value; + if (appliedValue !== undefined) { + newSearchParams.set(String(filter.accessorKey), String(appliedValue)); + } + } else if (filter.type === FilterType.DATE) { + const dateFilter = filterState as DateFilterState; + if (dateFilter.start || dateFilter.end) { + // For date filters, save both start and end dates + const startStr = dateFilter.start + ? dateFilter.start.toISOString() + : ""; + const endStr = dateFilter.end ? dateFilter.end.toISOString() : ""; + newSearchParams.set( + String(filter.accessorKey), + `${startStr},${endStr}`, + ); + } + } else if (filter.type === FilterType.CURRENCY) { + const currencyFilter = filterState as CurrencyFilterState; + if (currencyFilter.min_amount || currencyFilter.max_amount) { + // For currency filters, we'll save a simple representation + const minValue = currencyFilter.min_amount?.value; + const maxValue = currencyFilter.max_amount?.value; + if (minValue !== undefined || maxValue !== undefined) { + const currencyValue = + minValue !== undefined && maxValue !== undefined + ? `${minValue}-${maxValue}` + : minValue !== undefined + ? `>${minValue}` + : `<${maxValue}`; + newSearchParams.set(String(filter.accessorKey), currencyValue); + } + } + } + } + }); + + return newSearchParams; +} + +function loadFiltersFromURL>( + searchParams: URLSearchParams, + filters: Filter[], + currentFilterStates: DataManagerTableState, +): DataManagerTableState { + const newFilterStates = { ...currentFilterStates }; + + // Update each filter based on URL params + filters.forEach((filter) => { + const paramValue = searchParams.get(String(filter.accessorKey)); + + if (paramValue !== null && paramValue !== undefined && paramValue !== "") { + // URL has a value for this filter + if (filter.type === FilterType.STRING) { + const stringFilter = newFilterStates[ + filter.accessorKey + ] as StringFilterState; + // Handle comma-separated values for multi-value filters + const appliedValues = paramValue.includes(",") + ? paramValue.split(",").filter((v) => v.trim() !== "") + : [paramValue]; + newFilterStates[filter.accessorKey] = { + ...stringFilter, + appliedValues, + isApplied: true, + } as StringFilterState; + } else if (filter.type === FilterType.ENUM) { + const enumFilter = newFilterStates[ + filter.accessorKey + ] as EnumFilterState; + // Handle comma-separated values for multi-value filters + const appliedValues = paramValue.includes(",") + ? paramValue.split(",").filter((v) => v.trim() !== "") + : [paramValue]; + newFilterStates[filter.accessorKey] = { + ...enumFilter, + appliedValues, + isApplied: true, + } as EnumFilterState; + } else if (filter.type === FilterType.ID) { + const idFilter = newFilterStates[filter.accessorKey] as IdFilterState; + // Handle comma-separated values for multi-value filters + const appliedValues = paramValue.includes(",") + ? paramValue.split(",").filter((v) => v.trim() !== "") + : [paramValue]; + newFilterStates[filter.accessorKey] = { + ...idFilter, + appliedValues, + isApplied: true, + } as IdFilterState; + } else if (filter.type === FilterType.BOOLEAN) { + const booleanFilter = newFilterStates[ + filter.accessorKey + ] as BooleanFilterState; + const boolValue = paramValue === "true"; + newFilterStates[filter.accessorKey] = { + ...booleanFilter, + value: boolValue, + isApplied: true, + } as BooleanFilterState; + } else if (filter.type === FilterType.DATE) { + const dateFilter = newFilterStates[ + filter.accessorKey + ] as DateFilterState; + // Parse start and end dates from comma-separated ISO strings + if (paramValue.includes(",")) { + const [startStr, endStr] = paramValue.split(","); + const startDate = startStr ? new Date(startStr) : null; + const endDate = endStr ? new Date(endStr) : null; + + if ( + (startDate && !isNaN(startDate.getTime())) || + (endDate && !isNaN(endDate.getTime())) + ) { + newFilterStates[filter.accessorKey] = { + ...dateFilter, + start: startDate, + end: endDate, + isApplied: true, + } as DateFilterState; + } + } else { + // Single date value (no comma) + const dateValue = new Date(paramValue); + if (!isNaN(dateValue.getTime())) { + newFilterStates[filter.accessorKey] = { + ...dateFilter, + start: dateValue, + end: null, + isApplied: true, + } as DateFilterState; + } + } + } else if (filter.type === FilterType.CURRENCY) { + const currencyFilter = newFilterStates[ + filter.accessorKey + ] as CurrencyFilterState; + // Parse currency range from string like "100-200", ">100", "<200" + if (paramValue.includes("-")) { + const [min, max] = paramValue.split("-").map((v) => parseInt(v)); + if (!isNaN(min) && !isNaN(max)) { + newFilterStates[filter.accessorKey] = { + ...currencyFilter, + min_amount: { value: min, unit: CurrencyUnit.SATOSHI }, + max_amount: { value: max, unit: CurrencyUnit.SATOSHI }, + isApplied: true, + } as CurrencyFilterState; + } + } else if (paramValue.startsWith(">")) { + const min = parseInt(paramValue.substring(1)); + if (!isNaN(min)) { + newFilterStates[filter.accessorKey] = { + ...currencyFilter, + min_amount: { value: min, unit: CurrencyUnit.SATOSHI }, + max_amount: null, + isApplied: true, + } as CurrencyFilterState; + } + } else if (paramValue.startsWith("<")) { + const max = parseInt(paramValue.substring(1)); + if (!isNaN(max)) { + newFilterStates[filter.accessorKey] = { + ...currencyFilter, + min_amount: null, + max_amount: { value: max, unit: CurrencyUnit.SATOSHI }, + isApplied: true, + } as CurrencyFilterState; + } + } + } + } else { + // URL doesn't have a value for this filter, reset to default + newFilterStates[filter.accessorKey] = getDefaultFilterState(filter); + } + }); + + return newFilterStates; +} + export function DataManagerTable< T extends Record, QueryVariablesType, QueryResultType, >(props: DataManagerTableProps) { + const [searchParams, setSearchParams] = useSearchParams(); const isCardPageFullWidth = typeof props.cardPageFullWidth === "boolean" ? props.cardPageFullWidth @@ -228,6 +444,62 @@ export function DataManagerTable< setIsLoading(Boolean(props.loading)); }, [props.loading]); + // Sync filter states with URL query parameters + useEffect(() => { + if (!props.filterOptions || !props.enableURLFilters) return; + + const { filters, getFilterQueryVariables } = props.filterOptions; + + // Load filter states from URL query parameters + const newFilterStates = loadFiltersFromURL( + searchParams, + filters, + filterStates, + ); + setFilterStates(newFilterStates); + + const newFetchVariables = getFilterQueryVariables( + filters, + newFilterStates, + pageSize, + ); + setFetchVariables(newFetchVariables); + + // Update the count of applied filters + const numFiltersApplied = Object.values(newFilterStates).reduce( + (acc, state) => { + return state.isApplied ? acc + 1 : acc; + }, + 0, + ); + setNumFiltersApplied(numFiltersApplied); + + // Refetch data if filters changed and we have filter options + if ( + props.filterOptions && + Object.values(newFilterStates).some((state) => state.isApplied) + ) { + setIsLoading(true); + + // Clear start result number when filters change + setPageCursorState((prevState) => ({ + ...prevState, + startResult: undefined, + })); + + // Refetch with new filter variables + props.filterOptions + .refetch(newFetchVariables) + .then(() => { + setIsLoading(false); + }) + .catch((e) => { + setIsLoading(false); + throw e; + }); + } + }, [searchParams, props.filterOptions, pageSize]); // eslint-disable-line react-hooks/exhaustive-deps + // When data is fetched, the nextPageCursor associated with the results changes. // We then need to update the current result number and the cursor cache. // The cursor cache remembers previously seen cursors for paginating to the previous page. @@ -411,9 +683,19 @@ export function DataManagerTable< appliedFilterStates, pageSize, ); - setFetchVariables(newFetchVariables); + if (props.enableURLFilters) { + // Update url query params with the applied filters using utility function + const newSearchParams = saveFiltersToURL( + searchParams, + filters, + appliedFilterStates, + currentPage, + ); + setSearchParams(newSearchParams); + } + // Clear start result number when filters are applied setPageCursorState((prevState) => ({ ...prevState, From 3cebc1a90c902aaf59f463daae193f617273229c Mon Sep 17 00:00:00 2001 From: Brian Siao Tick Chong Date: Mon, 8 Sep 2025 08:52:46 -0700 Subject: [PATCH 08/30] [site] route to umaaas routes depending on user role (#20317) - since umaaas/nage routes live in site, we need to reroute to /umaaas when the user tries accessing any normal cn2 routes - also route to cn2 if the user's roles are not nage related - small fix for zIndex in filter dropdowns GitOrigin-RevId: bfb40937ff165181049b1e877fb49a32608deb80 --- packages/ui/src/components/DataManagerTable/DateFilter.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/DataManagerTable/DateFilter.tsx b/packages/ui/src/components/DataManagerTable/DateFilter.tsx index 6df349883..209b73dda 100644 --- a/packages/ui/src/components/DataManagerTable/DateFilter.tsx +++ b/packages/ui/src/components/DataManagerTable/DateFilter.tsx @@ -148,7 +148,7 @@ export const DateFilter = ({ }); } }} - zIndex={z.modalContainer} + zIndex={z.dropdown} /> ), @@ -323,7 +323,7 @@ export const DateFilter = ({ ); } }} - zIndex={z.modalContainer} + zIndex={z.dropdown} /> {dateRangeOptions[customDateRangeData.type].components} From 3bc369411ceba6e4e0cb2a5f344cce2f311a6f28 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 8 Sep 2025 10:19:22 -0700 Subject: [PATCH 09/30] [Nage] Add sandbox platform creation flow (#20257) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ![Screenshot 2025-09-03 at 2.38.02 PM.png](https://app.graphite.dev/user-attachments/assets/0b992a5a-bfa3-49e8-81f2-2ecf9bbc1b46.png) ![Screenshot 2025-09-03 at 2.38.07 PM.png](https://app.graphite.dev/user-attachments/assets/c69bbaaa-9147-4e9e-8ebd-97da96fa03ed.png) Contributes to PX-834 GitOrigin-RevId: 450a09f8bb6befea49d93270fdeb5d4b953415f9 --- packages/ui/src/components/Dropdown.tsx | 22 +++++++++--- packages/ui/src/components/TextInput.tsx | 45 ++++++++++++++++++++++-- packages/ui/src/styles/colors.tsx | 1 + packages/ui/src/styles/fields.tsx | 34 ++++++++++++++++-- 4 files changed, 92 insertions(+), 10 deletions(-) diff --git a/packages/ui/src/components/Dropdown.tsx b/packages/ui/src/components/Dropdown.tsx index 494517d28..a56a908cc 100644 --- a/packages/ui/src/components/Dropdown.tsx +++ b/packages/ui/src/components/Dropdown.tsx @@ -103,6 +103,7 @@ type DropdownProps = { align?: "left" | "right" | "center"; verticalPlacement?: "top" | "bottom"; footer?: ReactNode | null; + getFooterCSS?: ({ isOpen, theme }: DropdownGetProps) => CSSObject; getCSS?: ({ isOpen, theme }: DropdownGetProps) => CSSObject; getDropdownItemsCSS?: ({ isOpen, theme }: DropdownGetProps) => CSSObject; onOpen?: () => void; @@ -129,6 +130,7 @@ export function Dropdown({ align = "center" as const, verticalPlacement = "bottom", footer = null, + getFooterCSS, dropdownContent = null, borderRadius = 8, }: DropdownProps) { @@ -376,6 +378,7 @@ export function Dropdown({ const containerCSS = getCSS && getCSS({ isOpen, theme }); const dropdownItemsCSS = getDropdownItemsCSS && getDropdownItemsCSS({ isOpen, theme }); + const footerCSS = getFooterCSS && getFooterCSS({ isOpen, theme }); return (
    {footer}} + {footer && ( + + {footer} + + )} , nodeRef.current, ) @@ -416,10 +423,15 @@ export function Dropdown({ ); } -const DropdownFooter = styled.div` - padding: 24px 16px 0; - border-top: 1px solid ${({ theme }) => theme.c2Neutral}; - margin-top: 16px; +const DropdownFooter = styled.div<{ css?: CSSObject }>` + ${({ css, theme }) => + css + ? css + : ` + padding: 24px 16px 0; + border-top: 1px solid ${theme.c2Neutral}; + margin-top: 16px; + `} `; type DropdownItemsProps = { diff --git a/packages/ui/src/components/TextInput.tsx b/packages/ui/src/components/TextInput.tsx index 1ffa04eb1..c38f83656 100644 --- a/packages/ui/src/components/TextInput.tsx +++ b/packages/ui/src/components/TextInput.tsx @@ -17,6 +17,7 @@ import { standardBorderRadius } from "../styles/common.js"; import { InputSubtext, defaultTextInputTypography, + getBorderRadiusCSS, inputSpacingPx, textInputPlaceholderColor, textInputStyle, @@ -47,6 +48,22 @@ import { const selectLeftOffset = 10; +const getPartialBorderRadiusCSS = ( + partialBorderRadius?: PartialBorderRadius, +): string => { + if (!partialBorderRadius) { + return ""; + } + + const { + topLeft = 0, + topRight = 0, + bottomLeft = 0, + bottomRight = 0, + } = partialBorderRadius; + return `border-radius: ${topLeft}px ${topRight}px ${bottomRight}px ${bottomLeft}px;`; +}; + export const iconSides = ["left", "right"] as const; export type IconSide = (typeof iconSides)[number]; export const iconOffsets = ["small", "medium", "large"] as const; @@ -66,6 +83,13 @@ export type TextLabelOptions = { marginBottom?: number; }; +export type PartialBorderRadius = { + topLeft?: number; + topRight?: number; + bottomLeft?: number; + bottomRight?: number; +}; + export type TextInputProps = { disabled?: boolean | undefined; error?: string | ToReactNodesArgs | undefined; @@ -117,6 +141,7 @@ export type TextInputProps = { label?: string; labelOptions?: TextLabelOptions; rightButtonText?: string | undefined; + rightText?: string | undefined; onRightButtonClick?: (e: React.MouseEvent) => void; typography?: PartialSimpleTypographyProps | undefined; select?: @@ -131,6 +156,7 @@ export type TextInputProps = { } | undefined; borderRadius?: TextInputBorderRadius | undefined; + partialBorderRadius?: PartialBorderRadius | undefined; borderWidth?: number | undefined; width?: "full" | "short" | undefined; paddingX?: number; @@ -282,6 +308,7 @@ export function TextInput(textInputProps: TextInputProps) { } }} borderRadius={props.borderRadius} + partialBorderRadius={props.partialBorderRadius} borderWidth={props.borderWidth} enterKeyHint={props.enterKeyHint} autoFocus={props.autoFocus} @@ -293,6 +320,11 @@ export function TextInput(textInputProps: TextInputProps) { )} + {props.rightText && ( + + {props.rightText} + + )} ); @@ -470,11 +502,13 @@ interface InputProps { activeOutlineColor?: ThemeOrColorKey | undefined; typography: RequiredSimpleTypographyProps; borderRadius?: TextInputBorderRadius | undefined; + partialBorderRadius?: PartialBorderRadius | undefined; borderWidth?: number | undefined; } const Input = styled.input` ${textInputStyle}; + ${({ partialBorderRadius }) => getPartialBorderRadiusCSS(partialBorderRadius)} // disable autofill styles in chrome https://stackoverflow.com/a/68240841/9808766 &:-webkit-autofill, &:-webkit-autofill:focus, @@ -508,14 +542,19 @@ const RightButton = styled(UnstyledButton)` padding: 8px 10px; `; +const RightTextBody = styled.label<{ focused: boolean }>` + font-size: 14px; + font-weight: ${({ focused }) => (focused ? 600 : 400)}; +`; + const TextInputLabel = styled.label<{ hasError: boolean; labelOptions?: TextLabelOptions | undefined; }>` - ${({ labelOptions }) => + border-radius: ${({ labelOptions }) => labelOptions?.borderRadius - ? `${labelOptions.borderRadius}px` - : standardBorderRadius(8)}; + ? getBorderRadiusCSS(labelOptions.borderRadius) + : "8px"}; font-size: 10px; position: ${({ labelOptions }) => labelOptions?.position || "absolute"}; z-index: ${z.textInput + 1}; diff --git a/packages/ui/src/styles/colors.tsx b/packages/ui/src/styles/colors.tsx index 3086d6d07..042073a00 100644 --- a/packages/ui/src/styles/colors.tsx +++ b/packages/ui/src/styles/colors.tsx @@ -62,6 +62,7 @@ const baseColors = { // gray scale "gray-040": "#fafaf9", "gray-050": "#f8f8f7", + "gray-075": "#F4F4F3", "gray-090": "#eeeeec", "gray-100": "#f0f0ee", "gray-200": "#deded9", diff --git a/packages/ui/src/styles/fields.tsx b/packages/ui/src/styles/fields.tsx index 3be742017..e438c76ee 100644 --- a/packages/ui/src/styles/fields.tsx +++ b/packages/ui/src/styles/fields.tsx @@ -109,7 +109,37 @@ export const defaultTextInputTypography = { color: "text", } as const; -export type TextInputBorderRadius = 8 | 16 | 999; +export type TextInputBorderRadius = + | 8 + | 16 + | 999 + | { + topLeft?: number; + topRight?: number; + bottomLeft?: number; + bottomRight?: number; + }; + +export const getBorderRadiusCSS = ( + borderRadius?: TextInputBorderRadius, +): string => { + if (!borderRadius) { + return "8px "; + } + + if (typeof borderRadius === "number") { + return `${borderRadius}px `; + } + + // Handle partial border radius object + const { + topLeft = 8, + topRight = 8, + bottomLeft = 8, + bottomRight = 8, + } = borderRadius; + return `${topLeft}px ${topRight}px ${bottomRight}px ${bottomLeft}px`; +}; export const textInputStyle = ({ theme, @@ -140,7 +170,7 @@ export const textInputStyle = ({ borderRadius?: TextInputBorderRadius | undefined; borderWidth?: number | undefined; }>) => css` - border-radius: ${borderRadius ?? 8}px; + border-radius: ${getBorderRadiusCSS(borderRadius)}; background-color: ${disabled ? theme.vlcNeutral : theme.inputBackground}; cursor: ${disabled ? "not-allowed" : "auto"}; box-sizing: border-box; From 12ff69b832356a3296059038fcc8d4ddd1efeb7c Mon Sep 17 00:00:00 2001 From: Brian Siao Tick Chong Date: Mon, 8 Sep 2025 11:57:23 -0700 Subject: [PATCH 10/30] [nage] show nav items depending on user role (#20318) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - shows nav items depending on umaaas role - fixes console error due to svg prop - removes notification bell and unused menu items - fixes docs link Admin and developer: ![Screenshot 2025-09-05 at 5.44.15 PM.png](https://app.graphite.dev/user-attachments/assets/701d9d7a-0cf8-4f88-9131-466d0f1a3480.png) Compliance: ![Screenshot 2025-09-05 at 5.44.25 PM.png](https://app.graphite.dev/user-attachments/assets/c87495e0-38a7-4209-8f3d-24f38a0126d5.png) Support: ![Screenshot 2025-09-05 at 5.44.03 PM.png](https://app.graphite.dev/user-attachments/assets/27b48b7f-14df-4efa-bc33-201101aa1340.png) GitOrigin-RevId: 6435f3e38ff44d0ae9f888bc9baa035a9bd9af08 --- packages/ui/src/icons/LogoMark.tsx | 2 +- packages/ui/src/icons/NubankLogo.tsx | 2 +- packages/ui/src/icons/Origin.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/icons/LogoMark.tsx b/packages/ui/src/icons/LogoMark.tsx index 05b5be0a7..0fec7a8f7 100644 --- a/packages/ui/src/icons/LogoMark.tsx +++ b/packages/ui/src/icons/LogoMark.tsx @@ -8,7 +8,7 @@ export function LogoMark() { fill="none" xmlns="http://www.w3.org/2000/svg" > - + - + - + Date: Mon, 8 Sep 2025 12:14:17 -0700 Subject: [PATCH 11/30] [nage] fix nav button and command center search button styling (#20319) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - use transparent button kind instead of links - add option to Button for justifyContent - add themeable prop for button gap - auto navigate to transactions page if landing on home / base route ![Screenshot 2025-09-05 at 6.10.04 PM.png](https://app.graphite.dev/user-attachments/assets/7439eb79-882b-40e7-9c1b-b15cb90948a0.png) GitOrigin-RevId: bb79b901ef7b06f4bbe538c64fce099771913751 --- packages/ui/src/components/Button.tsx | 13 +++++++++---- packages/ui/src/styles/themeDefaults/buttons.tsx | 1 + packages/ui/src/styles/themes.tsx | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/Button.tsx b/packages/ui/src/components/Button.tsx index 402bf696f..94d436704 100644 --- a/packages/ui/src/components/Button.tsx +++ b/packages/ui/src/components/Button.tsx @@ -99,7 +99,8 @@ export type ButtonProps = { ml?: number | "auto"; mb?: number | "auto"; fullWidth?: boolean | undefined; - spaceBetween?: boolean | undefined; + justifyContent?: "space-between" | "center" | "flex-start" | "flex-end"; + gap?: number | undefined; type?: "button" | "submit"; newTab?: boolean; tooltipText?: string | undefined; @@ -161,6 +162,7 @@ function resolveProps(props: ButtonProps, theme: Theme) { const { kind = defaultKind, size: sizeProp, + gap: gapProp, paddingY: paddingYType = "regular", borderRadius, typography: typographyProp, @@ -208,6 +210,7 @@ function resolveProps(props: ButtonProps, theme: Theme) { kind, "defaultActiveBorderColor", ); + const gap = resolveProp(props.gap, kind, "defaultGap", theme); const typography = { type: @@ -239,6 +242,7 @@ function resolveProps(props: ButtonProps, theme: Theme) { hoverBorderColor, activeBackgroundColor, activeBorderColor, + gap, }; } @@ -274,11 +278,12 @@ export const Button = forwardRef< hoverBorderColor, activeBackgroundColor, activeBorderColor, + justifyContent, + gap, iconSide = "left", loading = false, loadingKind = "primary", fullWidth = false, - spaceBetween = false, disabled = false, mt = 0, ml = 0, @@ -332,8 +337,8 @@ export const Button = forwardRef< css={{ display: "flex", alignItems: "center", - justifyContent: spaceBetween ? "space-between" : "center", - gap: "8px", + justifyContent, + gap: gap ? `${gap}px` : undefined, }} > {iconSide === "left" && currentIcon} diff --git a/packages/ui/src/styles/themeDefaults/buttons.tsx b/packages/ui/src/styles/themeDefaults/buttons.tsx index d81ea23d7..215f1cb2a 100644 --- a/packages/ui/src/styles/themeDefaults/buttons.tsx +++ b/packages/ui/src/styles/themeDefaults/buttons.tsx @@ -29,6 +29,7 @@ export const buttonsThemeBase = { defaultTypographyType: "Body Strong" as AllowedButtonTypographyTypes, defaultColor: "text" as ThemeOrColorKey, defaultBorderRadius: 32 as ButtonBorderRadius, + defaultGap: 8, defaultBackgroundColor: "bg" as BackgroundColorKeyArg, defaultBorderColor: "bg" as BackgroundColorKeyArg, defaultHoverBackgroundColor: "bg" as BackgroundColorKeyArg, diff --git a/packages/ui/src/styles/themes.tsx b/packages/ui/src/styles/themes.tsx index 341465f1c..85474c847 100644 --- a/packages/ui/src/styles/themes.tsx +++ b/packages/ui/src/styles/themes.tsx @@ -744,6 +744,7 @@ const nageBaseSettings = { defaultTypographyType: "Btn", defaultSize: "Medium", defaultBorderRadius: 8, + defaultGap: 16, defaultPaddingsY: { ExtraSmall: 6, Small: 6, From bdfd74a71beff3698d4d18feddf73216967dc13b Mon Sep 17 00:00:00 2001 From: Brian Siao Tick Chong Date: Mon, 8 Sep 2025 13:29:24 -0700 Subject: [PATCH 12/30] [ui] default button justifyContent to center (#20339) - this was a regression when the justifyContent prop was added - it should default to center when not provided GitOrigin-RevId: 3a40a61007ca75b224a4df7f58c1b613a9960215 --- packages/ui/src/components/Button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/Button.tsx b/packages/ui/src/components/Button.tsx index 94d436704..7ece00e68 100644 --- a/packages/ui/src/components/Button.tsx +++ b/packages/ui/src/components/Button.tsx @@ -278,7 +278,7 @@ export const Button = forwardRef< hoverBorderColor, activeBackgroundColor, activeBorderColor, - justifyContent, + justifyContent = "center", gap, iconSide = "left", loading = false, From 591cb031f3525bbf7716513ea0f99fbe606bf413 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Tue, 9 Sep 2025 13:21:23 -0700 Subject: [PATCH 13/30] [Nage] Update settings styling, update nage select, and implement user invites (#20341) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ![Screenshot 2025-09-09 at 11.32.57 AM.png](https://app.graphite.dev/user-attachments/assets/188b53c1-19c0-4b24-8525-72be634f71b3.png) GitOrigin-RevId: 5bb91f6ea9c876618ee399e3558e1a54a025949e --- packages/ui/src/components/Select.tsx | 137 ++++++++++++++++---------- 1 file changed, 87 insertions(+), 50 deletions(-) diff --git a/packages/ui/src/components/Select.tsx b/packages/ui/src/components/Select.tsx index 4af25d402..4a79e6f5d 100644 --- a/packages/ui/src/components/Select.tsx +++ b/packages/ui/src/components/Select.tsx @@ -28,8 +28,12 @@ export type SelectProps< > = ReactSelectProps & { zIndex?: number | undefined; label?: string | undefined; + getMenuStyles?: StylesConfig["menu"]; getOptionStyles?: StylesConfig["option"]; getControlStyles?: StylesConfig["control"]; + getPlaceholderStyles?: StylesConfig["placeholder"]; + getInputStyles?: StylesConfig["input"]; + getSingleValueStyles?: StylesConfig["singleValue"]; selectRef?: Ref> | undefined; }; @@ -43,8 +47,12 @@ export function Select< label, openMenuOnFocus = true, tabSelectsValue = false, + getMenuStyles, getOptionStyles, getControlStyles, + getPlaceholderStyles, + getInputStyles, + getSingleValueStyles, ...rest }: SelectProps) { const theme = useTheme(); @@ -60,19 +68,20 @@ export function Select< control: (styles, state) => { const controlStyles = getControlStyles ? getControlStyles(styles, state) - : {}; + : { + padding: + state.menuIsOpen || state.isFocused + ? `${textInputPaddingPx - 1}px` + : textInputPadding, + background: theme.bg, + minHeight: 0, + fontSize: 14, + borderWidth: state.menuIsOpen || state.isFocused ? "2px" : "1px", + }; return { ...styles, transition: "none", - padding: - state.menuIsOpen || state.isFocused - ? `${textInputPaddingPx - 1}px` - : textInputPadding, boxShadow: "none", - background: theme.bg, - minHeight: 0, - fontSize: 14, - borderWidth: state.menuIsOpen || state.isFocused ? "2px" : "1px", "&:hover": { borderColor: state.menuIsOpen ? textInputBorderColorFocused({ theme }) @@ -93,40 +102,47 @@ export function Select< display: "none", }), menu: (styles, state) => { + const menuStyles = getMenuStyles + ? getMenuStyles(styles, state) + : { + ...styles, + overflow: "hidden", + border: `2px solid ${theme.lcNeutral}`, + backgroundColor: theme.bg, + boxShadow: "0px 3px 6px 1px rgba(0,0,0,0.05)", + borderColor: theme.hcNeutral, + width: "max-content", + minWidth: "100%", + }; return { ...styles, - overflow: "hidden", - border: `2px solid ${theme.lcNeutral}`, - backgroundColor: theme.bg, - boxShadow: "0px 3px 6px 1px rgba(0,0,0,0.05)", - borderColor: theme.hcNeutral, - width: "max-content", - minWidth: "100%", + ...menuStyles, }; }, option: (styles, props) => { const optionStyles = getOptionStyles ? getOptionStyles(styles, props) - : {}; + : { + color: props.isFocused + ? getColor(theme, defaultTextInputTypography.color) + : props.isDisabled + ? textInputPlaceholderColor({ theme }) + : getColor(theme, defaultTextInputTypography.color), + fontWeight: 600, + cursor: "pointer", + backgroundColor: props.isFocused + ? themeOr(theme.c05Neutral, theme.c2Neutral)({ theme }) + : theme.bg, + WebkitTapHighlightColor: theme.c1Neutral, + "&:active": { + backgroundColor: themeOr( + theme.c05Neutral, + theme.c5Neutral, + )({ theme }), + }, + }; return { ...styles, - color: props.isFocused - ? getColor(theme, defaultTextInputTypography.color) - : props.isDisabled - ? textInputPlaceholderColor({ theme }) - : getColor(theme, defaultTextInputTypography.color), - fontWeight: 600, - cursor: "pointer", - backgroundColor: props.isFocused - ? themeOr(theme.c05Neutral, theme.c2Neutral)({ theme }) - : theme.bg, - WebkitTapHighlightColor: theme.c1Neutral, - "&:active": { - backgroundColor: themeOr( - theme.c05Neutral, - theme.c5Neutral, - )({ theme }), - }, ...optionStyles, }; }, @@ -135,26 +151,47 @@ export function Select< fontSize: 14, zIndex: zIndex || z.selectFocused, }), - singleValue: (styles) => ({ - ...styles, - color: theme.text, - fontWeight: 600, - fontSize: 14, - }), - placeholder: (styles) => ({ - ...styles, - color: theme.mcNeutral, - }), + singleValue: (styles, state) => { + const singleValueStyles = getSingleValueStyles + ? getSingleValueStyles(styles, state) + : { + color: theme.text, + fontWeight: 600, + fontSize: 14, + }; + return { + ...styles, + ...singleValueStyles, + }; + }, + placeholder: (styles, state) => { + const placeholderStyles = getPlaceholderStyles + ? getPlaceholderStyles(styles, state) + : { + color: theme.mcNeutral, + }; + return { + ...styles, + ...placeholderStyles, + }; + }, valueContainer: (styles) => ({ ...styles, padding: 0, }), - input: (styles) => ({ - ...styles, - color: theme.text, - fontWeight: 600, - fontSize: 14, - }), + input: (styles, state) => { + const inputStyles = getInputStyles + ? getInputStyles(styles, state) + : { + color: theme.text, + fontWeight: 600, + fontSize: 14, + }; + return { + ...styles, + ...inputStyles, + }; + }, }; return ( From 32682c0e1664c91d1c236c171381799ba9a6c328 Mon Sep 17 00:00:00 2001 From: Brian Siao Tick Chong Date: Tue, 9 Sep 2025 15:35:53 -0700 Subject: [PATCH 14/30] [nage] refresh table data on platform change (#20376) GitOrigin-RevId: 9a3ee6ce8d5f395dae9e3fc620774f8c05e8209b --- .../src/components/DataManagerTable/DataManagerTable.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx b/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx index 776bbcaa9..ccb9d92fc 100644 --- a/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx +++ b/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx @@ -134,6 +134,7 @@ export type DataManagerTableProps< | undefined; minHeight?: number | undefined; enableURLFilters?: boolean | undefined; + refetchOnPropsChange?: (string | number | boolean)[] | undefined; }; export type DataManagerTableState> = Record< @@ -500,6 +501,13 @@ export function DataManagerTable< } }, [searchParams, props.filterOptions, pageSize]); // eslint-disable-line react-hooks/exhaustive-deps + // Handle refetching data when props from the parent component change. + useEffect(() => { + if (props.refetchOnPropsChange && props.filterOptions) { + void handleApplyFilters(filterStates, props.filterOptions, pageSize); + } + }, [...(props.refetchOnPropsChange || [])]); // eslint-disable-line react-hooks/exhaustive-deps + // When data is fetched, the nextPageCursor associated with the results changes. // We then need to update the current result number and the cursor cache. // The cursor cache remembers previously seen cursors for paginating to the previous page. From 9f6470f1b0a712e0db001fcae1e25c6d2eeec21c Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Wed, 10 Sep 2025 14:32:32 -0700 Subject: [PATCH 15/30] [Nage] Add sandbox deletion functionality and separate prod/sandbox config (#20378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ![Screenshot 2025-09-09 at 4.12.30 PM.png](https://app.graphite.dev/user-attachments/assets/67f6d258-b1b7-4c8b-8052-4d0814e6c854.png) ![Screenshot 2025-09-09 at 4.12.26 PM.png](https://app.graphite.dev/user-attachments/assets/dd81b73b-551e-44c3-b9ca-b23243c30548.png) GitOrigin-RevId: 88c4bd93d79ae47d18450e4be416d846f4be4efb --- packages/ui/src/styles/colors.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/styles/colors.tsx b/packages/ui/src/styles/colors.tsx index 042073a00..97ca62453 100644 --- a/packages/ui/src/styles/colors.tsx +++ b/packages/ui/src/styles/colors.tsx @@ -146,14 +146,15 @@ const baseColors = { purple55: "#8B38DE", purple60: "#7366c5", // red - red50: "#E31A1A", - red42a10: "#D800271A", - red42a20: "#D800272D", - red42a30: "#D800273F", + errorBackground: "#FEE2E1", errorText: "#E41C1B", errorText2: "#D80027", errorText3: "#FF4444", - errorBackground: "#FEE2E1", + red09: "#cc0909", + red42a10: "#D800271A", + red42a20: "#D800272D", + red42a30: "#D800273F", + red50: "#E31A1A", // yellow primary, warning: primary, From 685fedcd2af1b941bdfd1f937adeab8bdf226929 Mon Sep 17 00:00:00 2001 From: Brian Siao Tick Chong Date: Wed, 10 Sep 2025 15:30:51 -0700 Subject: [PATCH 16/30] [nage] add progress bar loading style to data manager tables (#20398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - adds progress bar loading style to data manager table - adds option to show no loader or progress bar - adds ability to stretch table to match parent component (fullHeight) so that the pagination is at the bottom of the screen and the loader is properly centered when there are no results [Screen Recording 2025-09-10 at 2.40.06 PM.mov (uploaded via Graphite) ](https://app.graphite.dev/user-attachments/video/f2b95a15-0056-4c63-a0b7-47a297c22bf9.mov) GitOrigin-RevId: 734257f091069d6cd1d7b3ac3a456d95920ab23c --- .../DataManagerTable/DataManagerTable.tsx | 12 +++- packages/ui/src/components/Table/Table.tsx | 71 +++++++++++++++++-- 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx b/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx index ccb9d92fc..42ded08fb 100644 --- a/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx +++ b/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx @@ -1,6 +1,7 @@ import styled from "@emotion/styled"; import { Fragment, useEffect, useState, type ComponentProps } from "react"; +import { css } from "@emotion/react"; import { CurrencyUnit } from "@lightsparkdev/core"; import { useSearchParams } from "react-router-dom"; import { type useClipboard } from "../../hooks/useClipboard.js"; @@ -1261,7 +1262,7 @@ export function DataManagerTable< ); return ( - + {isCardPageFullWidth ? ( {content} @@ -1273,8 +1274,15 @@ export function DataManagerTable< ); } -const StyledDataManagerTable = styled.div` +const StyledDataManagerTable = styled.div<{ fullHeight: boolean | undefined }>` width: 100%; + display: flex; + flex-direction: column; + ${({ fullHeight }) => + fullHeight && + css` + flex-grow: 1; + `} `; const commonPadding = ` diff --git a/packages/ui/src/components/Table/Table.tsx b/packages/ui/src/components/Table/Table.tsx index eadf8da00..b4efeb562 100644 --- a/packages/ui/src/components/Table/Table.tsx +++ b/packages/ui/src/components/Table/Table.tsx @@ -124,6 +124,18 @@ export type TableProps> = { text: ToReactNodesArgs; onClick: (row: T) => void; }[]; + loadingStyle?: + | { + style: "spinner"; + } + | { + style: "progress-bar"; + timeMs: number; + } + | { + style: "none"; + }; + fullHeight?: boolean; }; export function Table>({ @@ -137,6 +149,8 @@ export function Table>({ tripleDotsMenuItems, rowHoverEffect = "border", minHeight = 300, + loadingStyle = { style: "spinner" }, + fullHeight = false, }: TableProps) { const navigate = useNavigate(); const [sorting, setSorting] = useState([]); @@ -406,7 +420,7 @@ export function Table>({ const tbody = ( - { + {(!loading || ["none", "spinner"].includes(loadingStyle.style)) && // Loop over the table rows tableInstance.getRowModel().rows.map((row) => { return ( @@ -427,15 +441,14 @@ export function Table>({ ))} ); - }) - } + })} ); const TableComponent = customComponents?.table || StyledTable; return ( - + >({ {thead} {tbody} - {emptyState} - {loading && } + {!loading && emptyState} + {loading && loadingStyle.style === "spinner" && } + {loading && loadingStyle.style === "progress-bar" && ( + + )} ); } +function ProgressBar({ timeMs }: { timeMs: number }) { + return ( + + + + ); +} + +const ProgressBarContainer = styled.div` + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 280px; + height: 3px; + background: ${({ theme }) => theme.bg}; +`; + +const ProgressBarInner = styled.div<{ timeMs: number }>` + height: 100%; + background: ${({ theme }) => theme.text}; + + @keyframes progressBar { + 0% { + width: 0%; + } + 50% { + width: 90%; + } + 100% { + width: 95%; + } + } + + animation: progressBar ${({ timeMs }) => timeMs}ms ease-out forwards; +`; + type TableWrapperProps = { minHeight: number; + fullHeight: boolean; }; const TableWrapper = styled.div` position: relative; min-height: ${({ minHeight }) => minHeight}px; ${overflowAutoWithoutScrollbars} + ${({ fullHeight }) => + fullHeight && + css` + height: 100%; + `} `; export type StyledTableProps = { From e89a84163d10eac923b18b1d7971ea3351db7401 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Wed, 10 Sep 2025 20:38:54 -0400 Subject: [PATCH 17/30] add blocked ip screen to add credentials component (#20034) ## Reason This change provides an "uma not allowed in your region" screen if the user's ip is banned when selecting europe during the striga onboarding flow ## Overview * adds a new query to check the current user's ip address against a sanctioned list * modifies the `AddCredentials.tsx` to show a 'uma not allowed' screen as a result of this query * modifies AddCredentials to introduce a more robust state management mechanism ## Test Plan During onboarding of Striga users, set "is ip valid" to false by default on backend, ensure we see the "uma not allowed" screen GitOrigin-RevId: e23f3e750360563e7e1433f4afcf101aec9de1e8 --- packages/ui/src/icons/central/TriangleExclamation.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/ui/src/icons/central/TriangleExclamation.tsx b/packages/ui/src/icons/central/TriangleExclamation.tsx index 0c138c4d0..f65c4d108 100644 --- a/packages/ui/src/icons/central/TriangleExclamation.tsx +++ b/packages/ui/src/icons/central/TriangleExclamation.tsx @@ -6,13 +6,7 @@ export function TriangleExclamation({ strokeLinejoin = "round", }: PathProps) { return ( - + Date: Mon, 15 Sep 2025 11:50:20 -0700 Subject: [PATCH 18/30] Make bobby tables textarea resizable (#20476) Make the SQL textarea resizable in bobby tables. GitOrigin-RevId: 6d4d64521e3a3a3ff171d243a55820653053d67c --- packages/ui/src/components/TextArea.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/TextArea.tsx b/packages/ui/src/components/TextArea.tsx index 8c96295d5..ca0c1ee6d 100644 --- a/packages/ui/src/components/TextArea.tsx +++ b/packages/ui/src/components/TextArea.tsx @@ -16,6 +16,7 @@ type TextAreaProps = { event: React.ChangeEvent, ) => void; readOnly?: boolean; + resize?: "none" | "vertical" | "horizontal" | "both"; }; const defaultProps = { @@ -33,6 +34,7 @@ export function TextArea({ disabled = defaultProps.disabled, minHeight, readOnly = false, + resize = "none", }: TextAreaProps) { return ( @@ -41,6 +43,7 @@ export function TextArea({ id={id} value={value} minHeight={minHeight} + resize={resize} onChange={(event) => { onChange(event.target.value, event); }} @@ -57,10 +60,11 @@ export function TextArea({ const StyledTextArea = styled.textarea<{ disabled: boolean; hasError: boolean; + resize?: "none" | "vertical" | "horizontal" | "both"; minHeight?: number | undefined; }>` ${textInputStyle} - resize: none; + resize: ${({ resize }) => resize || "none"}; min-height: ${({ minHeight }) => typeof minHeight === "number" ? `${minHeight}px` : "initial"}; `; From d858f587a3e329339856a9252d8f8c2dc895fdb2 Mon Sep 17 00:00:00 2001 From: Corey Martin Date: Wed, 17 Sep 2025 15:14:44 -0700 Subject: [PATCH 19/30] [lightspark-sdk] Allow RemoteSigningWebhookHandler validator to be async (#20536) ## Reason Explain *why* this change is being made. ## Overview For large or complex changes, describe what is being changed. ## Test Plan Explain how you tested the change. GitOrigin-RevId: d4a54c0b0cfff6e1eaf7481829e70e41c05f84df --- .../remote-signing-server/src/index.ts | 6 +- .../src/tests/remote-signing-handler.test.ts | 151 ++++++++++++++++++ packages/lightspark-sdk/src/webhooks.ts | 14 +- 3 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 packages/lightspark-sdk/src/tests/remote-signing-handler.test.ts diff --git a/apps/examples/remote-signing-server/src/index.ts b/apps/examples/remote-signing-server/src/index.ts index cbb838b10..a61ef2933 100644 --- a/apps/examples/remote-signing-server/src/index.ts +++ b/apps/examples/remote-signing-server/src/index.ts @@ -104,7 +104,11 @@ async function handleRemoteSigningWebhook( signatureHeader: string, ) { const validator = { - should_sign: (webhook: WebhookEvent) => true, + should_sign: async (webhook: WebhookEvent) => { + // Simulate async policy, e.g., fetch account config or check DB + await new Promise((r) => setTimeout(r, 1)); + return webhook.event_type === WebhookEventType.REMOTE_SIGNING; + }, }; const remoteSigningHandler = new RemoteSigningWebhookHandler( diff --git a/packages/lightspark-sdk/src/tests/remote-signing-handler.test.ts b/packages/lightspark-sdk/src/tests/remote-signing-handler.test.ts new file mode 100644 index 000000000..4b71976a7 --- /dev/null +++ b/packages/lightspark-sdk/src/tests/remote-signing-handler.test.ts @@ -0,0 +1,151 @@ +import { hexToBytes } from "@lightsparkdev/core"; +import { createHmac } from "crypto"; +import type LightsparkClient from "../client.js"; +import { RemoteSigningWebhookHandler } from "../webhooks.js"; + +describe("RemoteSigningWebhookHandler (integration with wasm)", () => { + const seedHex = + "1a6deac8f74fb2e332677e3f4833b5e962f80d153fb368b8ee322a9caca4113d56cccd88f1c6a74e152669d8cd373fee2f27e3645d80de27640177a8c71395f8"; + const seedBytes = hexToBytes(seedHex); + + test("revoke/ack path returns undefined and does not call client", async () => { + const dataHex = + "7b226576656e745f74797065223a202252454d4f54455f5349474e494e47222c20226576656e745f6964223a20223031386665366130346663323036613830303030393538343032643337663465222c20226170695f76657273696f6e223a2022323032332d30392d3133222c202274696d657374616d70223a2022323032342d30362d30355430343a32303a31362e3936323134312b30303a3030222c2022656e746974795f6964223a20224368616e6e656c3a30313866653661302d346661372d363965332d303030302d653139633836386564366564222c202264617461223a207b227375625f6576656e745f74797065223a202252455645414c5f434f554e54455250415254595f5045525f434f4d4d49544d454e545f534543524554222c2022626974636f696e5f6e6574776f726b223a202252454754455354222c20227065725f636f6d6d69746d656e745f7365637265745f696478223a2032373336333139312c20227065725f636f6d6d69746d656e745f736563726574223a202236363837613837386538393733353535353131343039653363633762643934663535336336643265616230656231353831343761383337386266386335333261222c20226e6f64655f6964223a20226e6f64655f776974685f7365727665725f7369676e696e673a30313861393633352d333637332d383864662d303030302d383237663233303531623139227d7d"; + const dataBytes = hexToBytes(dataHex); + const sig = + "864f7f575ec2425eb6ecfe457cbf17b41da0e4cbb70cda126c00ea507fbf699c"; + const webhookSecret = "39kyJO140v7fYkwHnR7jz8Y3UphqVeNYQk44Xx049ws"; + + const calls: Array<[Parameters[0]]> = + []; + const executeRawQuery: LightsparkClient["executeRawQuery"] = async ( + query, + ) => { + calls.push([query]); + return null; + }; + const client = { executeRawQuery } as unknown as LightsparkClient; + + const handler = new RemoteSigningWebhookHandler(client, seedBytes, { + should_sign: async () => true, + }); + + const res = await handler.handleWebhookRequest( + dataBytes, + sig, + webhookSecret, + ); + expect(res).toBeUndefined(); + expect(calls.length).toBe(0); + }); + + test("async validator true → executes UpdateChannelPerCommitmentPoint", async () => { + const dataHex = + "7b226576656e745f74797065223a202252454d4f54455f5349474e494e47222c20226576656e745f6964223a20223031386665366138313066333036613830303030363835353239663539316563222c20226170695f76657273696f6e223a2022323032332d30392d3133222c202274696d657374616d70223a2022323032342d30362d30355430343a32383a34352e3137313839392b30303a3030222c2022656e746974795f6964223a20224368616e6e656c3a30313866653661382d313039362d363965332d303030302d623630373635343330663635222c202264617461223a207b227375625f6576656e745f74797065223a20224745545f5045525f434f4d4d49544d454e545f504f494e54222c2022626974636f696e5f6e6574776f726b223a202252454754455354222c202264657269766174696f6e5f70617468223a20226d2f332f3139333238222c20227065725f636f6d6d69746d656e745f706f696e745f696478223a2032373336333139312c20226e6f64655f6964223a20226e6f64655f776974685f7365727665725f7369676e696e673a30313861393633352d333637332d383864662d303030302d383237663233303531623139227d7d"; + const dataBytes = hexToBytes(dataHex); + const sig = + "5809279c1fd8088a6c62be82d3e858c11ce187ee97a0fadae1ae498e2f69442c"; + const webhookSecret = "39kyJO140v7fYkwHnR7jz8Y3UphqVeNYQk44Xx049ws"; + + const expectedVarsJson = + '{"channel_id":"Channel:018fe6a8-1096-69e3-0000-b60765430f65","per_commitment_point":"027ba8a666d57947ba8337d0e211cae9125dbaf7c9883cb34f49393bc1a4907dd8","per_commitment_point_index":27363191}'; + + const calls: Array<[Parameters[0]]> = + []; + const executeRawQuery: LightsparkClient["executeRawQuery"] = async ( + query, + ) => { + calls.push([query]); + return null; + }; + const client = { executeRawQuery } as unknown as LightsparkClient; + + const handler = new RemoteSigningWebhookHandler(client, seedBytes, { + should_sign: async () => true, + }); + + await handler.handleWebhookRequest(dataBytes, sig, webhookSecret); + expect(calls.length).toBe(1); + const [[firstArg]] = calls; + expect(firstArg.queryPayload).toMatch( + /^mutation UpdateChannelPerCommitmentPoint/, + ); + expect(firstArg.variables).toEqual(JSON.parse(expectedVarsJson)); + }); + + test("async validator false → does not execute query and throws", async () => { + const dataHex = + "7b226576656e745f74797065223a202252454d4f54455f5349474e494e47222c20226576656e745f6964223a20223031386665366138313066333036613830303030363835353239663539316563222c20226170695f76657273696f6e223a2022323032332d30392d3133222c202274696d657374616d70223a2022323032342d30362d30355430343a32383a34352e3137313839392b30303a3030222c2022656e746974795f6964223a20224368616e6e656c3a30313866653661382d313039362d363965332d303030302d623630373635343330663635222c202264617461223a207b227375625f6576656e745f74797065223a20224745545f5045525f434f4d4d49544d454e545f504f494e54222c2022626974636f696e5f6e6574776f726b223a202252454754455354222c202264657269766174696f6e5f70617468223a20226d2f332f3139333238222c20227065725f636f6d6d69746d656e745f706f696e745f696478223a2032373336333139312c20226e6f64655f6964223a20226e6f64655f776974685f7365727665725f7369676e696e673a30313861393633352d333637332d383864662d303030302d383237663233303531623139227d7d"; + const dataBytes = hexToBytes(dataHex); + const sig = + "5809279c1fd8088a6c62be82d3e858c11ce187ee97a0fadae1ae498e2f69442c"; + const webhookSecret = "39kyJO140v7fYkwHnR7jz8Y3UphqVeNYQk44Xx049ws"; + + const calls: Array<[Parameters[0]]> = + []; + const executeRawQuery: LightsparkClient["executeRawQuery"] = async ( + query, + ) => { + calls.push([query]); + return null; + }; + const client = { executeRawQuery } as unknown as LightsparkClient; + + const handler = new RemoteSigningWebhookHandler(client, seedBytes, { + should_sign: async () => false, + }); + + await expect( + handler.handleWebhookRequest(dataBytes, sig, webhookSecret), + ).rejects.toBeTruthy(); + expect(calls.length).toBe(0); + }); + + test("async validator false (DERIVE_KEY_AND_SIGN) → decline_to_sign_messages", async () => { + const event = { + event_type: "REMOTE_SIGNING", + event_id: "abc-derive", + timestamp: "2024-06-05T04:28:45.171899+00:00", + entity_id: "Node:018fe6a8-1096-69e3-0000-b60765430f65", + data: { + sub_event_type: "DERIVE_KEY_AND_SIGN", + bitcoin_network: "REGTEST", + signing_jobs: [ + { + id: "payload-1", + derivation_path: "m/3/2106220917/0", + message: + "476bdd1db5d91897d00d75300eef50c0da7e0b2dada06dde93cbb5903b7e16b2", + is_raw: true, + }, + ], + }, + }; + const json = JSON.stringify(event); + const dataBytes = new TextEncoder().encode(json); + const webhookSecret = "39kyJO140v7fYkwHnR7jz8Y3UphqVeNYQk44Xx049ws"; + const sig = createHmac("sha256", webhookSecret) + .update(dataBytes) + .digest("hex"); + + const calls: Array<[Parameters[0]]> = + []; + const executeRawQuery: LightsparkClient["executeRawQuery"] = async ( + query, + ) => { + calls.push([query]); + return null; + }; + const client = { executeRawQuery } as unknown as LightsparkClient; + + const handler = new RemoteSigningWebhookHandler(client, seedBytes, { + should_sign: async () => false, + }); + + await handler.handleWebhookRequest(dataBytes, sig, webhookSecret); + expect(calls.length).toBe(1); + const [[firstArg]] = calls; + expect(firstArg.queryPayload).toMatch(/decline_to_sign_messages\s*\(/); + expect(firstArg.variables).toEqual({ payload_ids: ["payload-1"] }); + }); +}); diff --git a/packages/lightspark-sdk/src/webhooks.ts b/packages/lightspark-sdk/src/webhooks.ts index 9bbb6b317..cb8e6a1e0 100644 --- a/packages/lightspark-sdk/src/webhooks.ts +++ b/packages/lightspark-sdk/src/webhooks.ts @@ -42,7 +42,7 @@ const parseWebhook = (data: Uint8Array): WebhookEvent => { }; type Validator = { - should_sign: (event: WebhookEvent) => boolean; + should_sign: (event: WebhookEvent) => boolean | Promise; }; export class RemoteSigningWebhookHandler { @@ -71,15 +71,25 @@ export class RemoteSigningWebhookHandler { ); } + // Pre-parse to expose a typed event to the validator and allow async decisions. + const event = await verifyAndParseWebhook( + data, + webhookSignature, + webhookSecret, + ); + + const decision = await this.validator.should_sign(event); + const { wasm_handle_remote_signing_webhook_event } = await import( "@lightsparkdev/crypto-wasm" ); + const response = wasm_handle_remote_signing_webhook_event( data, webhookSignature, webhookSecret, this.#masterSeed, - this.validator, + { should_sign: () => decision }, ); if (!response) { return; From 747cd8ac57480ac1776c2d694a7c5023ee8b8c6b Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 18 Sep 2025 10:33:39 -0700 Subject: [PATCH 20/30] [Nage] Add basic uuid frontend to command center (#20475) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ![Screenshot 2025-09-17 at 3.15.56 PM.png](https://app.graphite.dev/user-attachments/assets/e70032ae-57b5-4a03-bc3c-bdf254fac9e9.png) ![Screenshot 2025-09-17 at 3.16.00 PM.png](https://app.graphite.dev/user-attachments/assets/ac35d88f-e4cc-4336-af69-728b3da1093b.png) GitOrigin-RevId: 13658f8a6e0e93fea745d4343ab5ba8dad1bb765 --- packages/ui/src/icons/central/MagnifyingGlass.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/ui/src/icons/central/MagnifyingGlass.tsx b/packages/ui/src/icons/central/MagnifyingGlass.tsx index 4be74af60..6bbe330bc 100644 --- a/packages/ui/src/icons/central/MagnifyingGlass.tsx +++ b/packages/ui/src/icons/central/MagnifyingGlass.tsx @@ -6,13 +6,7 @@ export function MagnifyingGlass({ strokeLinejoin = "round", }: PathProps) { return ( - + Date: Wed, 17 Sep 2025 13:09:05 -0700 Subject: [PATCH 21/30] [nage] add filter pill redesign to DataManagerTable GitOrigin-RevId: d3a034f534cfe94103d7c79dadab1d747a7280f2 --- packages/ui/package.json | 1 + .../DataManagerTable/DataManagerTable.tsx | 345 +++++++++++++- .../DataManagerTable/DateWidget.tsx | 4 +- .../DataManagerTable/PillFilter.tsx | 448 ++++++++++++++++++ .../src/components/DataManagerTable/utils.ts | 125 +++++ packages/ui/src/components/Dropdown.tsx | 104 +++- packages/ui/src/icons/central/ChevronLeft.tsx | 8 +- .../ui/src/icons/central/ChevronLeftSm.tsx | 8 +- .../ui/src/icons/central/ChevronLeftSmall.tsx | 8 +- .../ui/src/icons/central/ChevronLeftXs.tsx | 8 +- .../ui/src/icons/central/ChevronRightSm.tsx | 8 +- .../src/icons/central/ChevronRightSmall.tsx | 8 +- .../ui/src/icons/central/ChevronRightXs.tsx | 8 +- packages/ui/src/icons/central/ChevronTop.tsx | 8 +- .../ui/src/icons/central/ChevronTopSm.tsx | 8 +- .../ui/src/icons/central/ChevronTopSmall.tsx | 8 +- .../ui/src/icons/central/ChevronTopXs.tsx | 8 +- packages/ui/src/styles/colors.tsx | 1 + .../ui/src/styles/themeDefaults/buttons.tsx | 2 +- packages/ui/src/styles/themes.tsx | 8 +- 20 files changed, 1011 insertions(+), 115 deletions(-) create mode 100644 packages/ui/src/components/DataManagerTable/PillFilter.tsx create mode 100644 packages/ui/src/components/DataManagerTable/utils.ts diff --git a/packages/ui/package.json b/packages/ui/package.json index e0c7a6a39..6715e41c2 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -96,6 +96,7 @@ "@wojtekmaj/react-datetimerange-picker": "^5.5.0", "@zxing/browser": "^0.1.1", "@zxing/library": "^0.19.2", + "dayjs": "^1.11.7", "deep-object-diff": "^1.1.9", "deepmerge": "^4.3.1", "libphonenumber-js": "^1.11.1", diff --git a/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx b/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx index 42ded08fb..303f8e81c 100644 --- a/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx +++ b/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx @@ -1,8 +1,16 @@ import styled from "@emotion/styled"; -import { Fragment, useEffect, useState, type ComponentProps } from "react"; - +import { + Fragment, + useEffect, + useState, + type ComponentProps, + type Dispatch, + type SetStateAction, +} from "react"; + +import { type CSSInterpolation } from "@emotion/css"; import { css } from "@emotion/react"; -import { CurrencyUnit } from "@lightsparkdev/core"; +import { CurrencyUnit, ensureArray } from "@lightsparkdev/core"; import { useSearchParams } from "react-router-dom"; import { type useClipboard } from "../../hooks/useClipboard.js"; import { bp, useBreakpoints } from "../../styles/breakpoints.js"; @@ -17,7 +25,8 @@ import { z } from "../../styles/z-index.js"; import { Button, StyledButton } from "../Button.js"; import { StyledButtonRow } from "../ButtonRow.js"; import { CardPageFullWidth } from "../CardPage.js"; -import { Dropdown } from "../Dropdown.js"; +import { Dropdown, type DropdownItemType } from "../Dropdown.js"; +import { Flex } from "../Flex.js"; import { Icon } from "../Icon/Icon.js"; import { Modal } from "../Modal.js"; import { @@ -26,6 +35,7 @@ import { type TableProps, } from "../Table/Table.js"; import { TextIconAligner } from "../TextIconAligner.js"; +import { type TextInput } from "../TextInput.js"; import { Body } from "../typography/Body.js"; import { Label } from "../typography/Label.js"; import { LabelModerate } from "../typography/LabelModerate.js"; @@ -41,6 +51,7 @@ import { } from "./CurrencyFilter.js"; import { DateFilter, + DatePreset, getDefaultDateFilterState, type DateFilterState, } from "./DateFilter.js"; @@ -62,6 +73,7 @@ import { isIdFilterState, type IdFilterState, } from "./IdFilter.js"; +import { PillFilter } from "./PillFilter.js"; import { StringFilter, getDefaultStringFilterState, @@ -98,6 +110,13 @@ interface CustomDataManagerTableComponents extends CustomTableComponents { dataManagerTableHeaderComponent?: React.ComponentType< React.ComponentProps >; + dropdownComponent?: React.ComponentType< + React.ComponentProps + >; + textInputComponent?: React.ComponentType< + React.ComponentProps + >; + customCalendarCss?: CSSInterpolation | undefined; } export type DataManagerTableProps< @@ -125,6 +144,7 @@ export type DataManagerTableProps< cardPageMt?: number; filterDropdownAlign?: "left" | "right" | "center"; filterButtonProps?: ComponentProps; + filterEditorStyle?: "default" | "pills"; customComponents?: CustomDataManagerTableComponents; paginationDisplayOptions?: | { @@ -440,6 +460,9 @@ export function DataManagerTable< props.filterOptions?.initialQueryVariables || ({} as QueryVariablesType), ); + const DropdownComponent = + props.customComponents?.dropdownComponent || Dropdown; + const isSm = breakPoint.isSm(); useEffect(() => { @@ -597,14 +620,11 @@ export function DataManagerTable< // Update UI filter state as a result of applying if needed if (isIdFilterState(filterState)) { const appliedValues = (validResult as IdFilterState).appliedValues; - const isApplied = !!appliedValues?.length; updateFilterState(filter)({ ...filterStates[filter.accessorKey], value: "", appliedValues: appliedValues ? [...appliedValues] : [], - isApplied, }); - filterState.isApplied = isApplied; } } else { // Set error messages on each state @@ -644,17 +664,12 @@ export function DataManagerTable< ...filterStates[filter.accessorKey], value: "", appliedValues: updatedAppliedValues, - isApplied: !!updatedAppliedValues.length, } as StringFilterState; // Apply the result of the validation for refetching data appliedFilterStates[filter.accessorKey] = newFilterState; // Update UI filter state as a result of applying if needed updateFilterState(filter)(newFilterState); - } else if (filterState.appliedValues?.length === 0) { - // If there are no more applied values, remove the filter - updateFilterState(filter)(getDefaultStringFilterState()); - filterState.isApplied = false; } } else if (isEnumFilterState(filterState)) { if (filterState.value) { @@ -662,17 +677,12 @@ export function DataManagerTable< ...filterStates[filter.accessorKey], value: "", appliedValues: filterState.appliedValues, - isApplied: !!filterState.appliedValues?.length, } as EnumFilterState; // Apply the result of the validation for refetching data appliedFilterStates[filter.accessorKey] = newFilterState; // Update UI filter state as a result of applying if needed updateFilterState(filter)(newFilterState); - } else if (filterState.appliedValues?.length === 0) { - // If there are no more applied values, remove the filter - updateFilterState(filter)(getDefaultEnumFilterState()); - filterState.isApplied = false; } } } @@ -995,7 +1005,11 @@ export function DataManagerTable< name: "Sort", }, text: !isFilterButtonSmall - ? `Filter${numFiltersApplied > 0 ? ` | ${numFiltersApplied}` : ""}` + ? `Filter${ + numFiltersApplied > 0 && props.filterEditorStyle !== "pills" + ? ` | ${numFiltersApplied}` + : "" + }` : undefined, } as const; @@ -1003,8 +1017,9 @@ export function DataManagerTable< const FilterContainerComponent = props.customComponents?.filterContainerComponent || DataManagerTableFilterContainer; - filters = ( - + + const defaultFilterContent = ( + <> {isSm ? ( ) : ( - )} + + ); + + const pillFilters = props.filterOptions + ? props.filterOptions.filters + .filter((filter: Filter) => { + // If the filter is not applied, don't show it in the pills. + return filterStates[filter.accessorKey]?.isApplied; + }) + .map((filter: Filter) => { + return ( + { + setFilterStates((prevStates) => { + const newStates = { ...prevStates }; + newStates[filter.accessorKey] = + getDefaultFilterState(filter); + void handleApplyFilters( + newStates, + props.filterOptions!, + pageSize, + ); + return newStates; + }); + }} + customComponents={{ + customDropdown: props.customComponents?.dropdownComponent, + customTextInput: props.customComponents?.textInputComponent, + customCalendarCss: props.customComponents?.customCalendarCss, + }} + onUpdateFilter={(newFilterState) => { + setFilterStates((prevStates) => { + const newStates = { ...prevStates }; + newStates[filter.accessorKey] = newFilterState; + void handleApplyFilters( + newStates, + props.filterOptions!, + pageSize, + ); + setShowFilterEditor(false); + return newStates; + }); + }} + /> + ); + }) + : []; + + const pillsFilterContent = ( + + + {pillFilters} + { + setShowFilterEditor(true); + }} + onClose={() => { + setShowFilterEditor(false); + }} + button={{ + ...commonButtonProps, + ...props.filterButtonProps, + iconSide: "left", + }} + align={props.filterDropdownAlign || "right"} + /> + + + {numFiltersApplied > 0 && ( + + )} + + + ); + + filters = ( + + {(!props.filterEditorStyle || props.filterEditorStyle === "default") && + defaultFilterContent} + {props.filterEditorStyle === "pills" && pillsFilterContent} ); } @@ -1274,6 +1402,172 @@ export function DataManagerTable< ); } +function getPillDropdownItems< + T extends Record, + QueryVariablesType, + QueryResultType, +>({ + filterOptions, + filterStates, + setFilterStates, + handleApplyFilters, + pageSize, + setShowFilterEditor, + customDropdownComponent, +}: { + filterOptions: FilterOptions; + filterStates: DataManagerTableState; + setFilterStates: Dispatch>>; + handleApplyFilters: ( + filterStates: DataManagerTableState, + filterOptions: FilterOptions, + pageSize: number, + ) => Promise; + pageSize: number; + setShowFilterEditor: Dispatch>; + customDropdownComponent?: + | React.ComponentType> + | undefined; +}) { + const getDropdownItemForFilter = (filter: Filter): DropdownItemType => { + let filterSubDropdownOptions: DropdownItemType[] = []; + if (filter.type === FilterType.ENUM) { + filterSubDropdownOptions = filter.enumValues.map((option) => ({ + label: option.label, + onClick: () => { + const optionValues = ensureArray(option.value); + const state = filterStates[filter.accessorKey] as EnumFilterState; + let updatedAppliedValues: string[] = []; + if (filter.isMulti) { + updatedAppliedValues = state.appliedValues + ? [ + ...state.appliedValues.filter( + (appliedValue) => appliedValue !== option.value, + ), + ...optionValues, + ] + : [...optionValues]; + } else { + updatedAppliedValues = [...optionValues]; + } + + setFilterStates((prevStates: DataManagerTableState) => { + const newStates = { ...prevStates }; + newStates[filter.accessorKey] = { + ...getDefaultFilterState(filter), + value: updatedAppliedValues.join(", "), + isApplied: true, + appliedValues: updatedAppliedValues, + } as unknown as FilterState; + void handleApplyFilters(newStates, filterOptions, pageSize); + setShowFilterEditor(false); + return newStates; + }); + }, + })); + } + + return { + label: filter.label, + getIcon: ({ dropdownItem, theme }) => ({ + name: "CentralChevronRightSm", + width: 18, + color: theme.secondary, + }), + iconSide: "right", + onClick: () => { + if (filter.type === FilterType.STRING) { + const state = filterStates[filter.accessorKey] as StringFilterState; + let updatedAppliedValues: string[] = []; + updatedAppliedValues = + filter.isMulti && state.appliedValues + ? [ + ...state.appliedValues.filter( + (appliedValue) => appliedValue !== (filter.value as string), + ), + ] + : []; + + setFilterStates((prevStates: DataManagerTableState) => { + const newStates = { ...prevStates }; + newStates[filter.accessorKey] = { + ...getDefaultFilterState(filter), + value: updatedAppliedValues.join(", "), + isApplied: true, + appliedValues: updatedAppliedValues, + } as unknown as FilterState; + void handleApplyFilters(newStates, filterOptions, pageSize); + setShowFilterEditor(false); + return newStates; + }); + } else if (filter.type === FilterType.BOOLEAN) { + setFilterStates((prevStates: DataManagerTableState) => { + const newStates = { ...prevStates }; + newStates[filter.accessorKey] = { + ...getDefaultFilterState(filter), + value: filter.value as boolean, + isApplied: true, + } as unknown as FilterState; + void handleApplyFilters(newStates, filterOptions, pageSize); + setShowFilterEditor(false); + return newStates; + }); + } else if (filter.type === FilterType.ID) { + const state = filterStates[filter.accessorKey] as IdFilterState; + let updatedAppliedValues: string[] = []; + updatedAppliedValues = + filter.isMulti && state.appliedValues + ? [ + ...state.appliedValues.filter( + (appliedValue) => appliedValue !== (filter.value as string), + ), + ] + : []; + setFilterStates((prevStates: DataManagerTableState) => { + const newStates = { ...prevStates }; + newStates[filter.accessorKey] = { + ...getDefaultFilterState(filter), + value: updatedAppliedValues.join(", "), + isApplied: true, + appliedValues: updatedAppliedValues, + } as unknown as FilterState; + void handleApplyFilters(newStates, filterOptions, pageSize); + setShowFilterEditor(false); + return newStates; + }); + } else if (filter.type === FilterType.CURRENCY) { + // TODO: Currency filter state, design tbd + } else if (filter.type === FilterType.DATE) { + setFilterStates((prevStates: DataManagerTableState) => { + const newStates = { ...prevStates }; + newStates[filter.accessorKey] = { + ...getDefaultFilterState(filter), + preset: DatePreset.Custom, + start: null, + end: null, + isApplied: true, + } as unknown as FilterState; + void handleApplyFilters(newStates, filterOptions, pageSize); + setShowFilterEditor(false); + return newStates; + }); + } + }, + subDropdown: + filterSubDropdownOptions.length > 0 + ? { + subItems: filterSubDropdownOptions, + customDropdown: customDropdownComponent, + } + : undefined, + }; + }; + + return filterOptions.filters.map((filter) => + getDropdownItemForFilter(filter), + ); +} + const StyledDataManagerTable = styled.div<{ fullHeight: boolean | undefined }>` width: 100%; display: flex; @@ -1415,3 +1709,12 @@ const FilterContentFooterRight = styled.div` display: flex; gap: ${Spacing.px.xs}; `; + +const PillFiltersContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + width: 100%; + gap: ${Spacing.px["2xs"]}; +`; diff --git a/packages/ui/src/components/DataManagerTable/DateWidget.tsx b/packages/ui/src/components/DataManagerTable/DateWidget.tsx index 13ab58295..e1c84af94 100644 --- a/packages/ui/src/components/DataManagerTable/DateWidget.tsx +++ b/packages/ui/src/components/DataManagerTable/DateWidget.tsx @@ -16,9 +16,9 @@ import { type CustomDateRangeData, } from "./date_utils.js"; -type DateOrNull = Date | null; +export type DateOrNull = Date | null; -type Dates = DateOrNull | [DateOrNull, DateOrNull]; +export type Dates = DateOrNull | [DateOrNull, DateOrNull]; function getInitialDateState(dateRangeOperation: DateRangeOperation) { switch (dateRangeOperation) { diff --git a/packages/ui/src/components/DataManagerTable/PillFilter.tsx b/packages/ui/src/components/DataManagerTable/PillFilter.tsx new file mode 100644 index 000000000..069ef9896 --- /dev/null +++ b/packages/ui/src/components/DataManagerTable/PillFilter.tsx @@ -0,0 +1,448 @@ +import { type CSSInterpolation } from "@emotion/css"; +import styled from "@emotion/styled"; +import { ensureArray } from "@lightsparkdev/core"; +import DateTimeRangePicker from "@wojtekmaj/react-datetimerange-picker"; +import dayjs from "dayjs"; +import utc from "dayjs/plugin/utc.js"; +import { capitalize } from "lodash-es"; +import { useState } from "react"; +import { colors } from "../../styles/colors.js"; +import { getColor, type LightsparkTheme } from "../../styles/themes.js"; +import { Spacing } from "../../styles/tokens/spacing.js"; +import { Button } from "../Button.js"; +import { Dropdown } from "../Dropdown.js"; +import { Flex } from "../Flex.js"; +import { Icon } from "../Icon/Icon.js"; +import { TextInput } from "../TextInput.js"; +import { Body } from "../typography/Body.js"; +import { Label } from "../typography/Label.js"; +import { isBooleanFilterState } from "./BooleanFilter.js"; +import { DateRangeOperation } from "./date_utils.js"; +import { DatePreset, isDateFilterState } from "./DateFilter.js"; +import { type Dates } from "./DateWidget.js"; +import { isEnumFilterState } from "./EnumFilter.js"; +import { type FilterState } from "./Filter.js"; +import { FilterType, type Filter } from "./filters.js"; +import { isIdFilterState } from "./IdFilter.js"; +import { isStringFilterState } from "./StringFilter.js"; +import { + isBooleanFilterAndState, + isDateFilterAndState, + isEnumFilterAndState, + isIdFilterAndState, + isStringFilterAndState, +} from "./utils.js"; + +dayjs.extend(utc); + +export interface PillFilterCustomComponents { + customDropdown?: + | React.ComponentType> + | undefined; + customTextInput?: + | React.ComponentType> + | undefined; + customCalendarCss?: CSSInterpolation | undefined; +} + +export function PillFilter>({ + filter, + state, + onUpdateFilter, + onDelete, + customComponents, +}: { + filter: Filter; + state: FilterState; + onUpdateFilter: (state: FilterState) => void; + onDelete: () => void; + customComponents?: PillFilterCustomComponents; +}) { + return ( + + + + + + + + + + + + + ); +} + +function getOperatorLabel>( + filter: Filter, +) { + switch (filter.type) { + case FilterType.ENUM: + return "is"; + case FilterType.DATE: + return "is"; + case FilterType.STRING: + // TODO: Add string "contains" operator + return "is"; + case FilterType.ID: + return "is"; + case FilterType.BOOLEAN: + return "is"; + // TODO: Currency filter state, design tbd + default: + return "is"; + } +} + +function formatDateValue(date: Date) { + return dayjs.utc(date).format("MMM DD, HH:mm"); +} + +function getFilterValue(state: FilterState) { + if (isEnumFilterState(state)) { + return state.appliedValues?.join(", ") || ""; + } else if (isStringFilterState(state)) { + return state.appliedValues?.join(", ") || ""; + } else if (isIdFilterState(state)) { + return state.appliedValues?.join(", ") || ""; + } else if (isDateFilterState(state)) { + return state.start && state.end + ? `${formatDateValue(state.start)} - ${formatDateValue(state.end)}` + : "Empty"; + } else if (isBooleanFilterState(state)) { + return state.value ? "True" : "False"; + } + // TODO: Currency filter state, design tbd + + throw new Error("Invalid filter state"); +} + +const commonDropdownGetCSS = ({ + isOpen, + theme, +}: { + isOpen: boolean; + theme: LightsparkTheme; +}) => { + return { + borderRight: `0.5px solid ${getColor(theme, ["controls", "border"])}`, + display: "flex", + justifyContent: "center", + alignItems: "center", + }; +}; + +const commonDropdownProps = { + getCSS: commonDropdownGetCSS, + align: "left", +} as const; + +function FilterDropdown>({ + filterAndState, + onUpdateFilter, + customComponents, +}: { + filterAndState: { filter: Filter; state: FilterState }; + onUpdateFilter: (state: FilterState) => void; + customComponents?: PillFilterCustomComponents | undefined; +}) { + const [stringFilterValue, setStringFilterValue] = useState(""); + const [dates, setDates] = useState(null); + const [isOpen, setIsOpen] = useState(false); + const DropdownComponent = customComponents?.customDropdown || Dropdown; + + if (isEnumFilterAndState(filterAndState)) { + const { filter, state } = filterAndState; + return ( + { + return ( + + + ); + }, + }} + dropdownItems={filter.enumValues.map((option) => ({ + label: option.label, + onClick: () => { + const optionValues = ensureArray(option.value); + let updatedAppliedValues: string[] = []; + if (filter.isMulti) { + updatedAppliedValues = state.appliedValues + ? [ + ...state.appliedValues.filter( + (appliedValue) => appliedValue !== option.value, + ), + ...optionValues, + ] + : [...optionValues]; + } else { + updatedAppliedValues = [...optionValues]; + } + + onUpdateFilter({ + ...state, + value: option.label, + isApplied: true, + appliedValues: updatedAppliedValues, + } as FilterState); + }, + }))} + /> + ); + } else if ( + isStringFilterAndState(filterAndState) || + isIdFilterAndState(filterAndState) + ) { + const TextInputComponent = customComponents?.customTextInput || TextInput; + + const { state } = filterAndState; + const handleApplyFilter = () => { + onUpdateFilter({ + ...state, + appliedValues: state.appliedValues, + isApplied: true, + } as unknown as FilterState); + setIsOpen(false); + }; + + return ( + setIsOpen(true)} + onClose={() => setIsOpen(false)} + button={{ + getContent: ({ isOpen, theme }) => { + const filterValue = getFilterValue(state); + return ( + + + ); + }, + }} + dropdownContent={ + + { + setStringFilterValue(value); + }} + onKeyDown={(keyValue, event) => { + if (keyValue === "Enter") { + handleApplyFilter(); + } + }} + /> +
    } /> From 797dcdb8af2cd0cf03ecc6188a1c73c736ca6dd1 Mon Sep 17 00:00:00 2001 From: "lightspark-ci-js-sdk[bot]" <134011073+lightspark-ci-js-sdk[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 16:36:06 -0700 Subject: [PATCH 26/30] Update from public js-sdk main branch (#20119) Update public `js` sources with the latest code from the [public repository](https://github.com/lightsparkdev/js-sdk) main branch. This typically happens when new versions of the SDK are released and version updates need to be synced. The PR should be merged as soon as possible to avoid updates to webdev overwriting the changes in the js-sdk develop branch. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Matt Davis Co-authored-by: Peng Ying Co-authored-by: lightspark-ci-js-sdk[bot] <134011073+lightspark-ci-js-sdk[bot]@users.noreply.github.com> Co-authored-by: Lightspark Eng Co-authored-by: Corey Martin GitOrigin-RevId: 6578b59e40e43022573e22ffa9d57bb1358262cf --- apps/examples/nodejs-scripts/CHANGELOG.md | 7 +++++++ apps/examples/nodejs-scripts/package.json | 4 ++-- apps/examples/oauth-app/CHANGELOG.md | 9 +++++++++ apps/examples/oauth-app/package.json | 6 +++--- apps/examples/remote-signing-server/CHANGELOG.md | 7 +++++++ apps/examples/remote-signing-server/package.json | 4 ++-- apps/examples/ui-test-app/CHANGELOG.md | 7 +++++++ apps/examples/ui-test-app/package.json | 4 ++-- apps/examples/uma-vasp-cli/CHANGELOG.md | 7 +++++++ apps/examples/uma-vasp-cli/package.json | 4 ++-- apps/examples/uma-vasp/CHANGELOG.md | 7 +++++++ apps/examples/uma-vasp/package.json | 4 ++-- packages/lightspark-cli/CHANGELOG.md | 7 +++++++ packages/lightspark-cli/package.json | 4 ++-- packages/lightspark-sdk/CHANGELOG.md | 6 ++++++ packages/lightspark-sdk/package.json | 2 +- packages/ui/CHANGELOG.md | 6 ++++++ packages/ui/package.json | 2 +- 18 files changed, 80 insertions(+), 17 deletions(-) diff --git a/apps/examples/nodejs-scripts/CHANGELOG.md b/apps/examples/nodejs-scripts/CHANGELOG.md index 0a3933166..7ccda080f 100644 --- a/apps/examples/nodejs-scripts/CHANGELOG.md +++ b/apps/examples/nodejs-scripts/CHANGELOG.md @@ -1,5 +1,12 @@ # @lightsparkdev/nodejs-scripts +## 0.0.32 + +### Patch Changes + +- Updated dependencies [1ee5899] + - @lightsparkdev/lightspark-sdk@1.9.10 + ## 0.0.31 ### Patch Changes diff --git a/apps/examples/nodejs-scripts/package.json b/apps/examples/nodejs-scripts/package.json index 7f8de1aeb..ae79205f2 100644 --- a/apps/examples/nodejs-scripts/package.json +++ b/apps/examples/nodejs-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/nodejs-scripts", - "version": "0.0.31", + "version": "0.0.32", "private": true, "description": "Example NodeJS scripts for Lightspark JS SDKs", "main": "index.js", @@ -34,7 +34,7 @@ }, "dependencies": { "@lightsparkdev/core": "1.4.4", - "@lightsparkdev/lightspark-sdk": "1.9.9", + "@lightsparkdev/lightspark-sdk": "1.9.10", "commander": "^11.0.0", "dayjs": "^1.11.7", "lodash-es": "^4.17.21" diff --git a/apps/examples/oauth-app/CHANGELOG.md b/apps/examples/oauth-app/CHANGELOG.md index af6e4f44d..e256b68db 100644 --- a/apps/examples/oauth-app/CHANGELOG.md +++ b/apps/examples/oauth-app/CHANGELOG.md @@ -1,5 +1,14 @@ # @lightsparkdev/oauth-app +## 0.0.60 + +### Patch Changes + +- Updated dependencies [1ee5899] +- Updated dependencies [1ee5899] + - @lightsparkdev/ui@1.1.11 + - @lightsparkdev/lightspark-sdk@1.9.10 + ## 0.0.59 ### Patch Changes diff --git a/apps/examples/oauth-app/package.json b/apps/examples/oauth-app/package.json index a972996a6..3b63c2b8a 100644 --- a/apps/examples/oauth-app/package.json +++ b/apps/examples/oauth-app/package.json @@ -1,14 +1,14 @@ { "name": "@lightsparkdev/oauth-app", - "version": "0.0.59", + "version": "0.0.60", "private": true, "dependencies": { "@emotion/css": "^11.11.0", "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", - "@lightsparkdev/lightspark-sdk": "1.9.9", + "@lightsparkdev/lightspark-sdk": "1.9.10", "@lightsparkdev/oauth": "*", - "@lightsparkdev/ui": "1.1.10", + "@lightsparkdev/ui": "1.1.11", "react": "^18.2.0", "react-dom": "^18.1.0", "react-router-dom": "6.11.2", diff --git a/apps/examples/remote-signing-server/CHANGELOG.md b/apps/examples/remote-signing-server/CHANGELOG.md index 65c004406..a9e127185 100644 --- a/apps/examples/remote-signing-server/CHANGELOG.md +++ b/apps/examples/remote-signing-server/CHANGELOG.md @@ -1,5 +1,12 @@ # @lightsparkdev/remote-signing-server +## 0.0.56 + +### Patch Changes + +- Updated dependencies [1ee5899] + - @lightsparkdev/lightspark-sdk@1.9.10 + ## 0.0.55 ### Patch Changes diff --git a/apps/examples/remote-signing-server/package.json b/apps/examples/remote-signing-server/package.json index e7e29778a..92fc26ae8 100644 --- a/apps/examples/remote-signing-server/package.json +++ b/apps/examples/remote-signing-server/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/remote-signing-server", - "version": "0.0.55", + "version": "0.0.56", "private": true, "type": "module", "scripts": { @@ -18,7 +18,7 @@ }, "dependencies": { "@lightsparkdev/core": "1.4.4", - "@lightsparkdev/lightspark-sdk": "1.9.9", + "@lightsparkdev/lightspark-sdk": "1.9.10", "express": "^4.18.2" }, "devDependencies": { diff --git a/apps/examples/ui-test-app/CHANGELOG.md b/apps/examples/ui-test-app/CHANGELOG.md index 482d53616..9e73f7e16 100644 --- a/apps/examples/ui-test-app/CHANGELOG.md +++ b/apps/examples/ui-test-app/CHANGELOG.md @@ -1,5 +1,12 @@ # @lightsparkdev/ui-test-app +## 0.0.30 + +### Patch Changes + +- Updated dependencies [1ee5899] + - @lightsparkdev/ui@1.1.11 + ## 0.0.29 ### Patch Changes diff --git a/apps/examples/ui-test-app/package.json b/apps/examples/ui-test-app/package.json index 8fd9840a7..f5a6115ae 100644 --- a/apps/examples/ui-test-app/package.json +++ b/apps/examples/ui-test-app/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/ui-test-app", - "version": "0.0.29", + "version": "0.0.30", "description": "Lightspark UI components", "author": "Lightspark Inc.", "main": "./dist/index.js", @@ -30,7 +30,7 @@ "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", "@lightsparkdev/core": "1.4.4", - "@lightsparkdev/ui": "1.1.10", + "@lightsparkdev/ui": "1.1.11", "react": "^18.2.0", "react-dom": "^18.1.0", "react-router-dom": "6.11.2" diff --git a/apps/examples/uma-vasp-cli/CHANGELOG.md b/apps/examples/uma-vasp-cli/CHANGELOG.md index 868ecef44..c014487bb 100644 --- a/apps/examples/uma-vasp-cli/CHANGELOG.md +++ b/apps/examples/uma-vasp-cli/CHANGELOG.md @@ -1,5 +1,12 @@ # @lightsparkdev/uma-vasp-cli +## 0.0.37 + +### Patch Changes + +- Updated dependencies [1ee5899] + - @lightsparkdev/lightspark-sdk@1.9.10 + ## 0.0.36 ### Patch Changes diff --git a/apps/examples/uma-vasp-cli/package.json b/apps/examples/uma-vasp-cli/package.json index 60464c8f3..4739dbec4 100644 --- a/apps/examples/uma-vasp-cli/package.json +++ b/apps/examples/uma-vasp-cli/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/uma-vasp-cli", - "version": "0.0.36", + "version": "0.0.37", "private": true, "description": "CLI for the Demo UMA VASP in ../apps/examples/uma-vasp", "main": "./dist/index.js", @@ -44,7 +44,7 @@ "dependencies": { "@inquirer/prompts": "^1.1.3", "@lightsparkdev/core": "1.4.4", - "@lightsparkdev/lightspark-sdk": "1.9.9", + "@lightsparkdev/lightspark-sdk": "1.9.10", "@uma-sdk/core": "^1.3.0", "chalk": "^5.3.0", "commander": "^11.0.0" diff --git a/apps/examples/uma-vasp/CHANGELOG.md b/apps/examples/uma-vasp/CHANGELOG.md index 64277a2e1..16e7e3358 100644 --- a/apps/examples/uma-vasp/CHANGELOG.md +++ b/apps/examples/uma-vasp/CHANGELOG.md @@ -1,5 +1,12 @@ # @lightsparkdev/uma-vasp +## 0.0.57 + +### Patch Changes + +- Updated dependencies [1ee5899] + - @lightsparkdev/lightspark-sdk@1.9.10 + ## 0.0.56 ### Patch Changes diff --git a/apps/examples/uma-vasp/package.json b/apps/examples/uma-vasp/package.json index fea7dac01..fc3fb5dcb 100644 --- a/apps/examples/uma-vasp/package.json +++ b/apps/examples/uma-vasp/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/uma-vasp", - "version": "0.0.56", + "version": "0.0.57", "private": true, "type": "module", "scripts": { @@ -16,7 +16,7 @@ "main": "dist/index.js", "dependencies": { "@lightsparkdev/core": "1.4.4", - "@lightsparkdev/lightspark-sdk": "1.9.9", + "@lightsparkdev/lightspark-sdk": "1.9.10", "@uma-sdk/core": "^1.3.0", "express": "^4.18.2", "express-async-handler": "^1.2.0", diff --git a/packages/lightspark-cli/CHANGELOG.md b/packages/lightspark-cli/CHANGELOG.md index 8c7a62596..9b5b119aa 100644 --- a/packages/lightspark-cli/CHANGELOG.md +++ b/packages/lightspark-cli/CHANGELOG.md @@ -1,5 +1,12 @@ # @lightsparkdev/lightspark-cli +## 0.1.10 + +### Patch Changes + +- Updated dependencies [1ee5899] + - @lightsparkdev/lightspark-sdk@1.9.10 + ## 0.1.9 ### Patch Changes diff --git a/packages/lightspark-cli/package.json b/packages/lightspark-cli/package.json index 5ae1944b3..35d130f3c 100644 --- a/packages/lightspark-cli/package.json +++ b/packages/lightspark-cli/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/lightspark-cli", - "version": "0.1.9", + "version": "0.1.10", "description": "CLI for the Lightspark JS sdk", "main": "./dist/index.js", "bin": { @@ -46,7 +46,7 @@ "@inquirer/prompts": "^1.1.3", "@lightsparkdev/core": "1.4.4", "@lightsparkdev/crypto-wasm": "0.1.18", - "@lightsparkdev/lightspark-sdk": "1.9.9", + "@lightsparkdev/lightspark-sdk": "1.9.10", "commander": "^11.0.0", "dayjs": "^1.11.7", "dotenv": "^16.3.1", diff --git a/packages/lightspark-sdk/CHANGELOG.md b/packages/lightspark-sdk/CHANGELOG.md index 1fde846d4..1c4dbbe3c 100644 --- a/packages/lightspark-sdk/CHANGELOG.md +++ b/packages/lightspark-sdk/CHANGELOG.md @@ -1,5 +1,11 @@ # @lightsparkdev/lightspark-sdk +## 1.9.10 + +### Patch Changes + +- 1ee5899: feat: exposing idempotency field on payInvoice method + ## 1.9.9 ### Patch Changes diff --git a/packages/lightspark-sdk/package.json b/packages/lightspark-sdk/package.json index 8121ff207..9021d304d 100644 --- a/packages/lightspark-sdk/package.json +++ b/packages/lightspark-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/lightspark-sdk", - "version": "1.9.9", + "version": "1.9.10", "description": "Lightspark JS SDK", "author": "Lightspark Inc.", "keywords": [ diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index 1cd680048..03dbb21be 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,5 +1,11 @@ # @lightsparkdev/ui +## 1.1.11 + +### Patch Changes + +- 1ee5899: feat: adding hint for birthday input + ## 1.1.10 ### Patch Changes diff --git a/packages/ui/package.json b/packages/ui/package.json index 6715e41c2..8a72920e8 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@lightsparkdev/ui", - "version": "1.1.10", + "version": "1.1.11", "main": "./dist/index.cjs", "module": "./dist/index.js", "types": "./dist/index.d.ts", From 3c4ce6994d050a990363c22082905da331668b0b Mon Sep 17 00:00:00 2001 From: Lightspark Eng Date: Fri, 19 Sep 2025 23:44:12 +0000 Subject: [PATCH 27/30] CI update lock file for PR --- yarn.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/yarn.lock b/yarn.lock index 88494d95c..eea4dd2fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2203,7 +2203,7 @@ __metadata: "@lightsparkdev/core": "npm:1.4.4" "@lightsparkdev/crypto-wasm": "npm:0.1.18" "@lightsparkdev/eslint-config": "npm:*" - "@lightsparkdev/lightspark-sdk": "npm:1.9.9" + "@lightsparkdev/lightspark-sdk": "npm:1.9.10" "@lightsparkdev/tsconfig": "npm:0.0.1" "@types/jsonwebtoken": "npm:^9.0.2" "@types/node": "npm:^20.2.5" @@ -2229,7 +2229,7 @@ __metadata: languageName: unknown linkType: soft -"@lightsparkdev/lightspark-sdk@npm:1.9.9, @lightsparkdev/lightspark-sdk@workspace:packages/lightspark-sdk": +"@lightsparkdev/lightspark-sdk@npm:1.9.10, @lightsparkdev/lightspark-sdk@workspace:packages/lightspark-sdk": version: 0.0.0-use.local resolution: "@lightsparkdev/lightspark-sdk@workspace:packages/lightspark-sdk" dependencies: @@ -2270,7 +2270,7 @@ __metadata: dependencies: "@lightsparkdev/core": "npm:1.4.4" "@lightsparkdev/eslint-config": "npm:*" - "@lightsparkdev/lightspark-sdk": "npm:1.9.9" + "@lightsparkdev/lightspark-sdk": "npm:1.9.10" "@types/jest": "npm:^29.5.3" "@types/node": "npm:^20.2.5" commander: "npm:^11.0.0" @@ -2296,10 +2296,10 @@ __metadata: "@emotion/react": "npm:^11.11.0" "@emotion/styled": "npm:^11.11.0" "@lightsparkdev/eslint-config": "npm:*" - "@lightsparkdev/lightspark-sdk": "npm:1.9.9" + "@lightsparkdev/lightspark-sdk": "npm:1.9.10" "@lightsparkdev/oauth": "npm:*" "@lightsparkdev/tsconfig": "npm:*" - "@lightsparkdev/ui": "npm:1.1.10" + "@lightsparkdev/ui": "npm:1.1.11" "@types/jest": "npm:^29.5.3" "@types/node": "npm:^20.2.5" "@types/react": "npm:^18.2.12" @@ -2348,7 +2348,7 @@ __metadata: resolution: "@lightsparkdev/remote-signing-server@workspace:apps/examples/remote-signing-server" dependencies: "@lightsparkdev/core": "npm:1.4.4" - "@lightsparkdev/lightspark-sdk": "npm:1.9.9" + "@lightsparkdev/lightspark-sdk": "npm:1.9.10" "@lightsparkdev/tsconfig": "npm:*" "@types/jest": "npm:^29.5.3" "@types/node": "npm:^20.2.5" @@ -2395,7 +2395,7 @@ __metadata: "@lightsparkdev/core": "npm:1.4.4" "@lightsparkdev/eslint-config": "npm:*" "@lightsparkdev/tsconfig": "npm:*" - "@lightsparkdev/ui": "npm:1.1.10" + "@lightsparkdev/ui": "npm:1.1.11" "@lightsparkdev/vite": "npm:*" "@testing-library/jest-dom": "npm:^6.1.2" "@types/jest": "npm:^29.5.3" @@ -2420,7 +2420,7 @@ __metadata: languageName: unknown linkType: soft -"@lightsparkdev/ui@npm:1.1.10, @lightsparkdev/ui@workspace:packages/ui": +"@lightsparkdev/ui@npm:1.1.11, @lightsparkdev/ui@workspace:packages/ui": version: 0.0.0-use.local resolution: "@lightsparkdev/ui@workspace:packages/ui" dependencies: @@ -2492,7 +2492,7 @@ __metadata: "@inquirer/prompts": "npm:^1.1.3" "@lightsparkdev/core": "npm:1.4.4" "@lightsparkdev/eslint-config": "npm:*" - "@lightsparkdev/lightspark-sdk": "npm:1.9.9" + "@lightsparkdev/lightspark-sdk": "npm:1.9.10" "@lightsparkdev/tsconfig": "npm:0.0.1" "@types/chalk": "npm:^2.2.0" "@types/node": "npm:^20.2.5" @@ -2517,7 +2517,7 @@ __metadata: resolution: "@lightsparkdev/uma-vasp@workspace:apps/examples/uma-vasp" dependencies: "@lightsparkdev/core": "npm:1.4.4" - "@lightsparkdev/lightspark-sdk": "npm:1.9.9" + "@lightsparkdev/lightspark-sdk": "npm:1.9.10" "@lightsparkdev/tsconfig": "npm:*" "@types/body-parser": "npm:^1.19.5" "@types/express": "npm:^4.17.21" From f429ce016852bff999e5060f328c3b5e2631e43d Mon Sep 17 00:00:00 2001 From: Peng Ying Date: Fri, 19 Sep 2025 16:52:08 -0700 Subject: [PATCH 28/30] chore: adding changesets --- .changeset/petite-pans-post.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/petite-pans-post.md diff --git a/.changeset/petite-pans-post.md b/.changeset/petite-pans-post.md new file mode 100644 index 000000000..6c038fde9 --- /dev/null +++ b/.changeset/petite-pans-post.md @@ -0,0 +1,7 @@ +--- +"@lightsparkdev/lightspark-sdk": patch +"@lightsparkdev/core": patch +"@lightsparkdev/ui": patch +--- + +Adding async validator for remote signing From a1b33fc9a8979a37988a0255fa035ce72399a4d0 Mon Sep 17 00:00:00 2001 From: Brian Siao Tick Chong Date: Fri, 19 Sep 2025 17:24:18 -0700 Subject: [PATCH 29/30] [nage] fix string and id filters, wrap pill filters in header (#20604) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fixes enum rendering in menu and pill filters - wraps filter pills in header - fixes string and id filters getting reset upon adding the filter again ![Screenshot 2025-09-19 at 3.33.12 PM.png](https://app.graphite.dev/user-attachments/assets/856eca6b-78a5-480d-be8c-e0acbd1dde62.png) ![Screenshot 2025-09-19 at 3.16.31 PM.png](https://app.graphite.dev/user-attachments/assets/5c02fef5-3c4f-4e42-83c9-69d7ce469985.png) ![Screenshot 2025-09-19 at 3.16.35 PM.png](https://app.graphite.dev/user-attachments/assets/70b44ed7-afda-469b-af8f-b49ea3c81ef3.png) GitOrigin-RevId: 2228ef78b37309d968b5d3b931b909f65a4cd77a --- .../DataManagerTable/DataManagerTable.tsx | 6 ++++- .../DataManagerTable/PillFilter.tsx | 23 ++++++++++++++++--- packages/ui/src/components/Flex.tsx | 5 ++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx b/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx index 303f8e81c..c5222da40 100644 --- a/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx +++ b/packages/ui/src/components/DataManagerTable/DataManagerTable.tsx @@ -1111,7 +1111,7 @@ export function DataManagerTable< const pillsFilterContent = ( - + {pillFilters} appliedValue !== (filter.value as string), ), ] + : state.appliedValues + ? [...state.appliedValues] : []; setFilterStates((prevStates: DataManagerTableState) => { @@ -1522,6 +1524,8 @@ function getPillDropdownItems< (appliedValue) => appliedValue !== (filter.value as string), ), ] + : state.appliedValues + ? [...state.appliedValues] : []; setFilterStates((prevStates: DataManagerTableState) => { const newStates = { ...prevStates }; diff --git a/packages/ui/src/components/DataManagerTable/PillFilter.tsx b/packages/ui/src/components/DataManagerTable/PillFilter.tsx index 08875f0ca..77c77b590 100644 --- a/packages/ui/src/components/DataManagerTable/PillFilter.tsx +++ b/packages/ui/src/components/DataManagerTable/PillFilter.tsx @@ -116,7 +116,7 @@ function formatDateValue(date: Date) { function getFilterValue(state: FilterState) { if (isEnumFilterState(state)) { - return state.appliedValues?.join(", ") || ""; + return state.appliedValues?.join(", ").replaceAll("_", " ") || ""; } else if (isStringFilterState(state)) { return state.appliedValues?.join(", ") || ""; } else if (isIdFilterState(state)) { @@ -218,11 +218,26 @@ function FilterDropdown>({ ) { const TextInputComponent = customComponents?.customTextInput || TextInput; - const { state } = filterAndState; + const { filter, state } = filterAndState; const handleApplyFilter = () => { + let updatedAppliedValues: string[] = []; + if (filter.isMulti) { + updatedAppliedValues = state.appliedValues + ? [ + ...state.appliedValues.filter( + (appliedValue) => appliedValue !== stringFilterValue, + ), + stringFilterValue, + ] + : [stringFilterValue]; + } else { + updatedAppliedValues = [stringFilterValue]; + } + onUpdateFilter({ ...state, - appliedValues: state.appliedValues, + appliedValues: updatedAppliedValues, + value: stringFilterValue, isApplied: true, } as unknown as FilterState); setIsOpen(false); @@ -439,6 +454,7 @@ const Property = styled.div` justify-content: center; align-items: center; padding: 0px ${Spacing.px.xs}; + white-space: nowrap; `; const Operator = styled.div` @@ -455,6 +471,7 @@ const Value = styled.div` justify-content: center; align-items: center; padding: 0px ${Spacing.px.xs}; + white-space: nowrap; `; const DeleteButton = styled.div` diff --git a/packages/ui/src/components/Flex.tsx b/packages/ui/src/components/Flex.tsx index c570b0bbb..d4722fc8a 100644 --- a/packages/ui/src/components/Flex.tsx +++ b/packages/ui/src/components/Flex.tsx @@ -49,6 +49,7 @@ type FlexProps = { gap?: number | undefined; position?: "absolute" | "relative" | undefined; grow?: string | number | undefined; + flexWrap?: "wrap" | "nowrap" | undefined; }; export function Flex({ @@ -76,6 +77,7 @@ export function Flex({ width, position, grow, + flexWrap, }: FlexProps) { const justify = justifyProp ? justifyProp : center ? "center" : "stretch"; const align = alignProp ? alignProp : center ? "center" : "stretch"; @@ -105,6 +107,7 @@ export function Flex({ width={width} position={position} grow={grow} + flexWrap={flexWrap} {...asButtonProps} > {children} @@ -145,6 +148,7 @@ type StyledFlexProps = { width?: FlexProps["width"]; position?: FlexProps["position"]; grow?: FlexProps["grow"]; + flexWrap?: FlexProps["flexWrap"]; }; export const StyledFlex = styled.div` @@ -191,4 +195,5 @@ export const StyledFlex = styled.div` width && (isNumber(width) ? `width: ${width}px;` : `width: ${width};`)} ${({ position }) => position && `position: ${position};`} ${({ grow }) => grow && `flex-grow: ${grow};`} + ${({ flexWrap }) => flexWrap && `flex-wrap: ${flexWrap};`} `; From 3ef2208e37a860195875614e10692de89a509c45 Mon Sep 17 00:00:00 2001 From: Corey Martin Date: Fri, 19 Sep 2025 18:18:16 -0700 Subject: [PATCH 30/30] fix changesets --- .changeset/better-turkeys-feel.md | 5 +++++ .changeset/petite-pans-post.md | 4 +--- .changeset/poor-rules-invent.md | 5 +++++ 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 .changeset/better-turkeys-feel.md create mode 100644 .changeset/poor-rules-invent.md diff --git a/.changeset/better-turkeys-feel.md b/.changeset/better-turkeys-feel.md new file mode 100644 index 000000000..1b9fc38bd --- /dev/null +++ b/.changeset/better-turkeys-feel.md @@ -0,0 +1,5 @@ +--- +"@lightsparkdev/core": patch +--- + +- Add USDT to supported currencies diff --git a/.changeset/petite-pans-post.md b/.changeset/petite-pans-post.md index 6c038fde9..c0c39d221 100644 --- a/.changeset/petite-pans-post.md +++ b/.changeset/petite-pans-post.md @@ -1,7 +1,5 @@ --- "@lightsparkdev/lightspark-sdk": patch -"@lightsparkdev/core": patch -"@lightsparkdev/ui": patch --- -Adding async validator for remote signing +- Adding async validator for remote signing diff --git a/.changeset/poor-rules-invent.md b/.changeset/poor-rules-invent.md new file mode 100644 index 000000000..b8255fb45 --- /dev/null +++ b/.changeset/poor-rules-invent.md @@ -0,0 +1,5 @@ +--- +"@lightsparkdev/ui": patch +--- + +- Component and theme updates