From d72ff0393e7de04172ef070406ae7f9136232700 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 17 Mar 2025 10:28:42 -0700 Subject: [PATCH 01/26] Bridge homescreen UI refactor (#16088) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Got design sign off from Pat! ![Screenshot 2025-03-11 at 10.41.56 AM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/EJphitdgYFvuACNazm1q/8d4273c9-c53d-4089-89ba-e847780a7bb7.png) Contributes to PX-128 GitOrigin-RevId: 1c2acc507deae3420a05f231f5ca32f20b9f5480 --- packages/ui/src/icons/Lock2.tsx | 23 +++++++++++++++++++++++ packages/ui/src/icons/index.tsx | 1 + packages/ui/src/styles/colors.tsx | 1 + 3 files changed, 25 insertions(+) create mode 100644 packages/ui/src/icons/Lock2.tsx diff --git a/packages/ui/src/icons/Lock2.tsx b/packages/ui/src/icons/Lock2.tsx new file mode 100644 index 000000000..20bdba395 --- /dev/null +++ b/packages/ui/src/icons/Lock2.tsx @@ -0,0 +1,23 @@ +// Copyright ©, 2022, Lightspark Group, Inc. - All Rights Reserved + +import { type PathProps } from "./types.js"; + +export function Lock2({ strokeWidth = "1.4" }: PathProps) { + return ( + + + + ); +} diff --git a/packages/ui/src/icons/index.tsx b/packages/ui/src/icons/index.tsx index 9223e591c..6edd42a48 100644 --- a/packages/ui/src/icons/index.tsx +++ b/packages/ui/src/icons/index.tsx @@ -84,6 +84,7 @@ export { LinkIcon } from "./LinkIcon.js"; export { LoadingCircleLines } from "./LoadingCircleLines.js"; export { LoadingSpinner } from "./LoadingSpinner.js"; export { Lock } from "./Lock.js"; +export { Lock2 } from "./Lock2.js"; export { Logo } from "./Logo.js"; export { LogoBolt } from "./LogoBolt.js"; export { LogoIssuance } from "./LogoIssuance.js"; diff --git a/packages/ui/src/styles/colors.tsx b/packages/ui/src/styles/colors.tsx index 81b3c4178..d08451db2 100644 --- a/packages/ui/src/styles/colors.tsx +++ b/packages/ui/src/styles/colors.tsx @@ -84,6 +84,7 @@ const baseColors = { // neutral secondary: neutral.black, gray: "#242526", + gray2: "#6D7685", // transparent transparent: "transparent", transparenta02: "#00000005", From 9dea59ff4a6ea1beb5ab69a68ba88814c428c25f Mon Sep 17 00:00:00 2001 From: Corey Martin Date: Wed, 19 Mar 2025 11:23:41 -0700 Subject: [PATCH 02/26] [js] Upgrade turbo (#16355) GitOrigin-RevId: cf1bf5e6c62134ba3a87d8178382cc839bb90b81 --- package.json | 2 +- turbo.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e947806ce..0ba24fd01 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@octokit/auth-action": "^4.0.1", "octokit": "^4.0.2", "ts-prune": "^0.10.3", - "turbo": "^1.13.3" + "turbo": "^2.4.4" }, "engines": { "node": ">=18" diff --git a/turbo.json b/turbo.json index 2f4e7ff98..7730af4e6 100644 --- a/turbo.json +++ b/turbo.json @@ -1,6 +1,6 @@ { "$schema": "https://turbo.build/schema.json", - "pipeline": { + "tasks": { "build": { "dependsOn": ["^build"], "outputs": ["dist/**", "build/**"] From 57e5699862dca591a699f17a2799dc61ec89e13a Mon Sep 17 00:00:00 2001 From: Lightspark Eng Date: Wed, 19 Mar 2025 18:37:38 +0000 Subject: [PATCH 03/26] CI update lock file for PR --- yarn.lock | 60 +++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/yarn.lock b/yarn.lock index df275e3d4..1bce3768d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10592,7 +10592,7 @@ __metadata: "@octokit/auth-action": "npm:^4.0.1" octokit: "npm:^4.0.2" ts-prune: "npm:^0.10.3" - turbo: "npm:^1.13.3" + turbo: "npm:^2.4.4" languageName: unknown linkType: soft @@ -15106,58 +15106,58 @@ __metadata: languageName: node linkType: hard -"turbo-darwin-64@npm:1.13.3": - version: 1.13.3 - resolution: "turbo-darwin-64@npm:1.13.3" +"turbo-darwin-64@npm:2.4.4": + version: 2.4.4 + resolution: "turbo-darwin-64@npm:2.4.4" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"turbo-darwin-arm64@npm:1.13.3": - version: 1.13.3 - resolution: "turbo-darwin-arm64@npm:1.13.3" +"turbo-darwin-arm64@npm:2.4.4": + version: 2.4.4 + resolution: "turbo-darwin-arm64@npm:2.4.4" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"turbo-linux-64@npm:1.13.3": - version: 1.13.3 - resolution: "turbo-linux-64@npm:1.13.3" +"turbo-linux-64@npm:2.4.4": + version: 2.4.4 + resolution: "turbo-linux-64@npm:2.4.4" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"turbo-linux-arm64@npm:1.13.3": - version: 1.13.3 - resolution: "turbo-linux-arm64@npm:1.13.3" +"turbo-linux-arm64@npm:2.4.4": + version: 2.4.4 + resolution: "turbo-linux-arm64@npm:2.4.4" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"turbo-windows-64@npm:1.13.3": - version: 1.13.3 - resolution: "turbo-windows-64@npm:1.13.3" +"turbo-windows-64@npm:2.4.4": + version: 2.4.4 + resolution: "turbo-windows-64@npm:2.4.4" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"turbo-windows-arm64@npm:1.13.3": - version: 1.13.3 - resolution: "turbo-windows-arm64@npm:1.13.3" +"turbo-windows-arm64@npm:2.4.4": + version: 2.4.4 + resolution: "turbo-windows-arm64@npm:2.4.4" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"turbo@npm:^1.13.3": - version: 1.13.3 - resolution: "turbo@npm:1.13.3" - dependencies: - turbo-darwin-64: "npm:1.13.3" - turbo-darwin-arm64: "npm:1.13.3" - turbo-linux-64: "npm:1.13.3" - turbo-linux-arm64: "npm:1.13.3" - turbo-windows-64: "npm:1.13.3" - turbo-windows-arm64: "npm:1.13.3" +"turbo@npm:^2.4.4": + version: 2.4.4 + resolution: "turbo@npm:2.4.4" + dependencies: + turbo-darwin-64: "npm:2.4.4" + turbo-darwin-arm64: "npm:2.4.4" + turbo-linux-64: "npm:2.4.4" + turbo-linux-arm64: "npm:2.4.4" + turbo-windows-64: "npm:2.4.4" + turbo-windows-arm64: "npm:2.4.4" dependenciesMeta: turbo-darwin-64: optional: true @@ -15173,7 +15173,7 @@ __metadata: optional: true bin: turbo: bin/turbo - checksum: 10/de96309b3c4c28b51d9436cdec57e3df452dea4ec4512e6c3b7f6596265d4b22d2c16b9c8a674cc1bafa3cc2eea9feb89a807526d6d820f9bca0522d8d86df12 + checksum: 10/ea60911d280e85068ec31c58cf079e5eb423db9dfa3fc1f1fdb5456debb0c6395ef20040384b4d6400e22cfbc1590381a61c4be1bdc7a06bbdf6b9b92573c42a languageName: node linkType: hard From 4d99c58ce38477242ad2715914e2d33eda728cd1 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Thu, 20 Mar 2025 10:26:02 -0700 Subject: [PATCH 04/26] add link bank 3a component (#16161) # add a front end for linking bank accounts, during onboarding flow closes PX-80 GitOrigin-RevId: 534270f6023eeb3cbc27e27bf674aa6a5f191b3e --- packages/ui/src/icons/CheckmarkGreen.tsx | 27 ++++++++++++++++++++++++ packages/ui/src/icons/index.tsx | 1 + 2 files changed, 28 insertions(+) create mode 100644 packages/ui/src/icons/CheckmarkGreen.tsx diff --git a/packages/ui/src/icons/CheckmarkGreen.tsx b/packages/ui/src/icons/CheckmarkGreen.tsx new file mode 100644 index 000000000..89e6a651f --- /dev/null +++ b/packages/ui/src/icons/CheckmarkGreen.tsx @@ -0,0 +1,27 @@ +// Copyright ©, 2022, Lightspark Group, Inc. - All Rights Reserved + +import { type PathProps } from "./types.js"; + +export function CheckmarkGreen({ + strokeWidth = "4", + strokeLinecap = "round", + strokeLinejoin = "round", +}: PathProps) { + return ( + + + + ); +} diff --git a/packages/ui/src/icons/index.tsx b/packages/ui/src/icons/index.tsx index 6edd42a48..808b116fa 100644 --- a/packages/ui/src/icons/index.tsx +++ b/packages/ui/src/icons/index.tsx @@ -33,6 +33,7 @@ export { CheckmarkCircle } from "./CheckmarkCircle.js"; export { CheckmarkCircleTier1 } from "./CheckmarkCircleTier1.js"; export { CheckmarkCircleTier2 } from "./CheckmarkCircleTier2.js"; export { CheckmarkCircleTier3 } from "./CheckmarkCircleTier3.js"; +export { CheckmarkGreen } from "./CheckmarkGreen.js"; export { Chevron } from "./Chevron.js"; export { ChevronDown } from "./ChevronDown.js"; export { ChevronLeft } from "./ChevronLeft.js"; From 44b8fac1caefa804adc315b17370f094113f3e1d Mon Sep 17 00:00:00 2001 From: Corey Martin Date: Thu, 20 Mar 2025 11:45:45 -0700 Subject: [PATCH 05/26] [js] Add publint to package:checks (#16337) GitOrigin-RevId: 675017a49cc8ed7997396289d71cd4298f18da2f --- package.json | 4 ++-- packages/core/package.json | 6 ++++-- packages/core/turbo.json | 8 ++++++++ packages/lightspark-sdk/package.json | 6 ++++-- packages/lightspark-sdk/turbo.json | 8 ++++++++ packages/oauth/package.json | 5 +++-- packages/oauth/turbo.json | 8 ++++++++ packages/ui/package.json | 15 +++++++++++---- packages/ui/src/hooks/index.tsx | 11 +++++++++++ packages/ui/turbo.json | 8 ++++++++ turbo.json | 16 +++++++++++----- 11 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 packages/core/turbo.json create mode 100644 packages/lightspark-sdk/turbo.json create mode 100644 packages/oauth/turbo.json create mode 100644 packages/ui/src/hooks/index.tsx create mode 100644 packages/ui/turbo.json diff --git a/package.json b/package.json index 0ba24fd01..2145fcb88 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "build-sb": "turbo run build-sb", "build": "turbo run build", "build:watch": "turbo run build:watch", - "checks": "yarn deps:check && turbo gql-codegen && turbo lint format test circular-deps package-types", + "checks": "yarn deps:check && turbo gql-codegen && turbo run lint format test circular-deps package:checks", "circular-deps": "turbo run circular-deps", "clean": "turbo run clean", "clean-all": "./clean-all.sh", @@ -24,7 +24,7 @@ "gql-codegen": "turbo run gql-codegen", "lint": "turbo run lint", "lint:fix": "turbo run lint:fix", - "package-types": "turbo run package-types", + "package:checks": "turbo run package:checks", "release": "turbo build && changeset publish", "start:prod": "VITE_PROXY_TARGET=https://app.lightspark.com yarn start", "start": "./start.sh", diff --git a/packages/core/package.json b/packages/core/package.json index 68392097f..bf29c54ff 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,7 +13,7 @@ "homepage": "https://github.com/lightsparkdev/js-sdk", "repository": { "type": "git", - "url": "https://github.com/lightsparkdev/js-sdk.git" + "url": "git+https://github.com/lightsparkdev/js-sdk.git" }, "bugs": { "url": "https://github.com/lightsparkdev/js-sdk/issues" @@ -38,6 +38,7 @@ "files": [ "src/*", "dist/*", + "dist/utils/*", "CHANGELOG.md" ], "scripts": { @@ -50,7 +51,7 @@ "lint:fix": "eslint --fix .", "lint:watch": "esw ./src -w --ext .ts,.tsx,.js --color", "lint": "eslint .", - "package-types": "yarn attw --pack .", + "package:checks": "yarn publint && yarn attw --pack .", "postversion": "yarn build", "test": "node --experimental-vm-modules $(yarn bin jest) --no-cache --runInBand --bail src/**/tests/**/*.test.ts", "types:watch": "tsc-absolute --watch", @@ -82,6 +83,7 @@ "lodash-es": "^4.17.21", "prettier": "3.0.3", "prettier-plugin-organize-imports": "^3.2.4", + "publint": "^0.3.9", "ts-jest": "^29.1.1", "tsc-absolute": "^1.0.1", "tsup": "^8.2.4", diff --git a/packages/core/turbo.json b/packages/core/turbo.json new file mode 100644 index 000000000..0657018cb --- /dev/null +++ b/packages/core/turbo.json @@ -0,0 +1,8 @@ +{ + "extends": ["//"], + "tasks": { + "package:checks": { + "dependsOn": ["build"] + } + } +} diff --git a/packages/lightspark-sdk/package.json b/packages/lightspark-sdk/package.json index 978ab6770..6a51085bb 100644 --- a/packages/lightspark-sdk/package.json +++ b/packages/lightspark-sdk/package.json @@ -13,7 +13,7 @@ "homepage": "https://github.com/lightsparkdev/js-sdk", "repository": { "type": "git", - "url": "https://github.com/lightsparkdev/js-sdk.git" + "url": "git+https://github.com/lightsparkdev/js-sdk.git" }, "bugs": { "url": "https://github.com/lightsparkdev/js-sdk/issues" @@ -44,6 +44,7 @@ "files": [ "src/*", "dist/*", + "dist/objects/*", "CHANGELOG.md" ], "scripts": { @@ -58,7 +59,7 @@ "lint:fix:continue": "eslint --fix . || exit 0", "lint:watch": "esw ./src -w --ext .ts,.tsx,.js --color", "lint": "eslint .", - "package-types": "yarn attw --pack .", + "package:checks": "yarn publint && yarn attw --pack .", "postversion": "yarn build", "test-cmd": "node --experimental-vm-modules $(yarn bin jest) --no-cache --runInBand --bail", "test": "yarn test-cmd src/tests/*.test.ts", @@ -92,6 +93,7 @@ "jest": "^29.6.2", "prettier": "3.0.3", "prettier-plugin-organize-imports": "^3.2.4", + "publint": "^0.3.9", "ts-jest": "^29.1.1", "tsc-absolute": "^1.0.1", "tsup": "^8.2.4", diff --git a/packages/lightspark-sdk/turbo.json b/packages/lightspark-sdk/turbo.json new file mode 100644 index 000000000..0657018cb --- /dev/null +++ b/packages/lightspark-sdk/turbo.json @@ -0,0 +1,8 @@ +{ + "extends": ["//"], + "tasks": { + "package:checks": { + "dependsOn": ["build"] + } + } +} diff --git a/packages/oauth/package.json b/packages/oauth/package.json index d6cdabd1a..78ea439df 100644 --- a/packages/oauth/package.json +++ b/packages/oauth/package.json @@ -14,7 +14,7 @@ "homepage": "https://github.com/lightsparkdev/js-sdk", "repository": { "type": "git", - "url": "https://github.com/lightsparkdev/js-sdk.git" + "url": "git+https://github.com/lightsparkdev/js-sdk.git" }, "bugs": { "url": "https://github.com/lightsparkdev/js-sdk/issues" @@ -47,7 +47,7 @@ "lint:fix": "eslint --fix .", "lint:watch": "esw ./src -w --ext .ts,.tsx,.js --color", "lint": "eslint .", - "package-types": "yarn attw --pack .", + "package:checks": "yarn publint && yarn attw --pack .", "postversion": "yarn build", "test": "echo \"TODO\"", "types:watch": "tsc-absolute --watch", @@ -69,6 +69,7 @@ "jest": "^29.6.2", "prettier": "3.0.3", "prettier-plugin-organize-imports": "^3.2.4", + "publint": "^0.3.9", "ts-jest": "^29.1.1", "tsc-absolute": "^1.0.1", "tsup": "^8.2.4", diff --git a/packages/oauth/turbo.json b/packages/oauth/turbo.json new file mode 100644 index 000000000..0657018cb --- /dev/null +++ b/packages/oauth/turbo.json @@ -0,0 +1,8 @@ +{ + "extends": ["//"], + "tasks": { + "package:checks": { + "dependsOn": ["build"] + } + } +} diff --git a/packages/ui/package.json b/packages/ui/package.json index 8dc72e494..0b32a979d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -43,9 +43,9 @@ "import": "./dist/utils/index.js", "require": "./dist/utils/index.cjs" }, - "./static/": { - "import": "./dist/static/", - "require": "./dist/static/" + "./static/*": { + "import": "./dist/static/*", + "require": "./dist/static/*" }, "./router": { "import": "./dist/router.js", @@ -77,7 +77,7 @@ "lint:fix": "eslint --fix .", "lint:watch": "esw . -w --ext .ts,.tsx,.js --color", "lint": "eslint .", - "package-types": "yarn attw --pack .", + "package:checks": "yarn attw --pack .", "test": "echo \"ui package tests are located in apps/examples/ui-test-app\"", "types": "yarn tsc", "types:watch": "tsc-absolute --watch" @@ -157,6 +157,13 @@ }, "files": [ "dist/*", + "dist/hooks/*", + "dist/icons/*", + "dist/types/*", + "dist/styles/*", + "dist/utils/*", + "dist/router.js", + "dist/static/*", "CHANGELOG.md" ], "engines": { diff --git a/packages/ui/src/hooks/index.tsx b/packages/ui/src/hooks/index.tsx new file mode 100644 index 000000000..8df1054ae --- /dev/null +++ b/packages/ui/src/hooks/index.tsx @@ -0,0 +1,11 @@ +export { useClipboard } from "./useClipboard.js"; +export { useDebounce } from "./useDebounce.js"; +export { useDocumentTitle } from "./useDocumentTitle.js"; +export { default as useFields } from "./useFields.js"; +export { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect.js"; +export { useLiveRef } from "./useLiveRef.js"; +export { useMaxScaleIOS } from "./useMaxScaleIOS.js"; +export { useNumberInput } from "./useNumberInput/useNumberInput.js"; +export { useQueryParams } from "./useQueryParams.js"; +export { useResizeObserver } from "./useResizeObserver.js"; +export { useWhatChanged } from "./useWhatChanged.js"; diff --git a/packages/ui/turbo.json b/packages/ui/turbo.json new file mode 100644 index 000000000..0657018cb --- /dev/null +++ b/packages/ui/turbo.json @@ -0,0 +1,8 @@ +{ + "extends": ["//"], + "tasks": { + "package:checks": { + "dependsOn": ["build"] + } + } +} diff --git a/turbo.json b/turbo.json index 7730af4e6..c45cf823f 100644 --- a/turbo.json +++ b/turbo.json @@ -14,7 +14,9 @@ "build-sb": { "outputs": ["storybook-static/**"] }, - "circular-deps": {}, + "circular-deps": { + "dependsOn": [] + }, "clean": { "cache": false }, @@ -26,8 +28,12 @@ "dependsOn": ["^build"], "outputs": ["docs/**"] }, - "format": {}, - "format:fix": {}, + "format": { + "dependsOn": [] + }, + "format:fix": { + "dependsOn": [] + }, "gql-codegen": { /* Always run codegen since it depends on files external to the workspace: */ "cache": false @@ -42,8 +48,8 @@ "cache": false, "persistent": true }, - "package-types": { - "dependsOn": ["build", "^build"] + "package:checks": { + "dependsOn": [] }, "start": { "cache": false, From a836636c44448c36e6f7fd4f58639123e8bbd3fd Mon Sep 17 00:00:00 2001 From: Lightspark Eng Date: Thu, 20 Mar 2025 18:57:07 +0000 Subject: [PATCH 06/26] CI update lock file for PR --- yarn.lock | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/yarn.lock b/yarn.lock index 1bce3768d..1a18f4adc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2149,6 +2149,7 @@ __metadata: lodash-es: "npm:^4.17.21" prettier: "npm:3.0.3" prettier-plugin-organize-imports: "npm:^3.2.4" + publint: "npm:^0.3.9" secp256k1: "npm:^5.0.1" ts-jest: "npm:^29.1.1" tsc-absolute: "npm:^1.0.1" @@ -2253,6 +2254,7 @@ __metadata: jest: "npm:^29.6.2" prettier: "npm:3.0.3" prettier-plugin-organize-imports: "npm:^3.2.4" + publint: "npm:^0.3.9" ts-jest: "npm:^29.1.1" tsc-absolute: "npm:^1.0.1" tsup: "npm:^8.2.4" @@ -2334,6 +2336,7 @@ __metadata: jest: "npm:^29.6.2" prettier: "npm:3.0.3" prettier-plugin-organize-imports: "npm:^3.2.4" + publint: "npm:^0.3.9" ts-jest: "npm:^29.1.1" tsc-absolute: "npm:^1.0.1" tsup: "npm:^8.2.4" @@ -3209,6 +3212,13 @@ __metadata: languageName: node linkType: hard +"@publint/pack@npm:^0.1.2": + version: 0.1.2 + resolution: "@publint/pack@npm:0.1.2" + checksum: 10/83e1de31ae29a0e651f7f91ebe6ad1fdf8cbb61d1eb056476586a234d05fa6fde9f34d3a0e36fbf18a2e9affa1082f758833242fd285637d303130f1a286b928 + languageName: node + linkType: hard + "@remix-run/router@npm:1.6.2": version: 1.6.2 resolution: "@remix-run/router@npm:1.6.2" @@ -11654,6 +11664,13 @@ __metadata: languageName: node linkType: hard +"mri@npm:^1.1.0": + version: 1.2.0 + resolution: "mri@npm:1.2.0" + checksum: 10/6775a1d2228bb9d191ead4efc220bd6be64f943ad3afd4dcb3b3ac8fc7b87034443f666e38805df38e8d047b29f910c3cc7810da0109af83e42c82c73bd3f6bc + languageName: node + linkType: hard + "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -12290,6 +12307,15 @@ __metadata: languageName: node linkType: hard +"package-manager-detector@npm:^0.2.9": + version: 0.2.11 + resolution: "package-manager-detector@npm:0.2.11" + dependencies: + quansync: "npm:^0.2.7" + checksum: 10/2c1a8da0e5895f0be06a8e1f4b4336fb78a19167ca3932dbaeca7260f948e67cf53b32585a13f8108341e7a468b38b4f2a8afc7b11691cb2d856ecd759d570fb + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -12472,6 +12498,13 @@ __metadata: languageName: node linkType: hard +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 + languageName: node + linkType: hard + "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -12828,6 +12861,20 @@ __metadata: languageName: node linkType: hard +"publint@npm:^0.3.9": + version: 0.3.9 + resolution: "publint@npm:0.3.9" + dependencies: + "@publint/pack": "npm:^0.1.2" + package-manager-detector: "npm:^0.2.9" + picocolors: "npm:^1.1.1" + sade: "npm:^1.8.1" + bin: + publint: src/cli.js + checksum: 10/bfa96a78e38df964422602d22afd52561a47591bc5e06ffe39e0029ee0953205ad379506dd643dfbbf89f443b7ae753150b5e6cc03872c2fc852f9a755533c48 + languageName: node + linkType: hard + "punycode@npm:^2.1.0, punycode@npm:^2.1.1": version: 2.3.1 resolution: "punycode@npm:2.3.1" @@ -12878,6 +12925,13 @@ __metadata: languageName: node linkType: hard +"quansync@npm:^0.2.7": + version: 0.2.10 + resolution: "quansync@npm:0.2.10" + checksum: 10/b54d955de867e104025f2666d52b2b67befe4e0f184a96acc9adcbdc572e46dce49c69d1e79f99413beae8a974a576383806a05f85f9a826865dc589ee1bcaf2 + languageName: node + linkType: hard + "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" @@ -13720,6 +13774,15 @@ __metadata: languageName: node linkType: hard +"sade@npm:^1.8.1": + version: 1.8.1 + resolution: "sade@npm:1.8.1" + dependencies: + mri: "npm:^1.1.0" + checksum: 10/1c67ba03c94083e0ae307ff5564ecb86c2104c0f558042fdaa40ea0054f91a63a9783f14069870f2f784336adabb70f90f22a84dc457b5a25e859aaadefe0910 + languageName: node + linkType: hard + "safe-array-concat@npm:^1.0.1": version: 1.0.1 resolution: "safe-array-concat@npm:1.0.1" From bfcee5b7816acee0416a02353655fe3b54c46128 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Thu, 20 Mar 2025 14:07:30 -0700 Subject: [PATCH 07/26] Update uma-bridge scroll behavior (#16163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds blurring and opacity when scrolling. Removes the border once at the top. Also makes the UMA logo at the banner slightly larger per spec. Example: ![Screenshot 2025-03-11 at 10.43.41 AM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/EJphitdgYFvuACNazm1q/a957a8cf-6f3a-49df-833c-4d891c9651b8.png) GitOrigin-RevId: 7a482063a2ffe7e3cb5d03682fcccb122929a021 --- packages/ui/src/components/Banner/Banner.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/ui/src/components/Banner/Banner.tsx b/packages/ui/src/components/Banner/Banner.tsx index 874e424fe..8b8b8129e 100644 --- a/packages/ui/src/components/Banner/Banner.tsx +++ b/packages/ui/src/components/Banner/Banner.tsx @@ -31,6 +31,7 @@ export type BannerProps = { hPadding?: number | undefined; right?: ReactNode | undefined; left?: ReactNode | undefined; + blurScroll?: boolean | undefined; }; export function Banner({ @@ -47,6 +48,7 @@ export function Banner({ minHeight = 0, hPadding = 0, borderColor, + blurScroll = false, }: BannerProps) { const [width, setWidth] = useState(70); const resizeProps = useMemo(() => ["height" as const], []); @@ -104,6 +106,7 @@ export function Banner({ ref={ref} borderProgress={borderProgress} hPadding={hPadding} + blurScroll={blurScroll} > {left} {innerContent} @@ -154,6 +157,7 @@ const StyledBanner = styled.div<{ borderProgress: number | undefined; hPadding: number; borderColor: ThemeOrColorKey | undefined; + blurScroll: boolean | undefined; }>` position: fixed; left: 0; @@ -164,6 +168,13 @@ const StyledBanner = styled.div<{ font-weight: 500; background-color: ${({ colorProp, theme }) => colorProp ? getColor(theme, colorProp) : "none"}; + ${({ blurScroll }) => + blurScroll + ? ` + background: rgba(249, 249, 249, 0.8); + backdrop-filter: blur(32px); + ` + : ""} display: flex; justify-content: ${({ hasSideContent }) => hasSideContent ? "space-between" : "center"}; From b2ddff959274a49e26fab523498ddc9ff2ab1606 Mon Sep 17 00:00:00 2001 From: Shreya Vissamsetti Date: Mon, 24 Mar 2025 15:50:45 -0700 Subject: [PATCH 08/26] User error codes in demo vasp (#15997) GitOrigin-RevId: 388a9e57ccdc5faac6dceec900da520225feaa59 --- apps/examples/uma-vasp-cli/package.json | 2 +- apps/examples/uma-vasp/README.md | 6 +- apps/examples/uma-vasp/package.json | 3 +- apps/examples/uma-vasp/src/ReceivingVasp.ts | 413 ++++++++------ apps/examples/uma-vasp/src/SendingVasp.ts | 602 ++++++++++++-------- apps/examples/uma-vasp/src/server.ts | 112 ++-- packages/ui/package.json | 2 +- 7 files changed, 673 insertions(+), 467 deletions(-) diff --git a/apps/examples/uma-vasp-cli/package.json b/apps/examples/uma-vasp-cli/package.json index c175699a5..9ddce084e 100644 --- a/apps/examples/uma-vasp-cli/package.json +++ b/apps/examples/uma-vasp-cli/package.json @@ -45,7 +45,7 @@ "@inquirer/prompts": "^1.1.3", "@lightsparkdev/core": "1.3.0", "@lightsparkdev/lightspark-sdk": "1.9.0", - "@uma-sdk/core": "^1.2.3", + "@uma-sdk/core": "^1.3.0", "chalk": "^5.3.0", "commander": "^11.0.0" }, diff --git a/apps/examples/uma-vasp/README.md b/apps/examples/uma-vasp/README.md index 867661940..3df4df58c 100644 --- a/apps/examples/uma-vasp/README.md +++ b/apps/examples/uma-vasp/README.md @@ -7,10 +7,10 @@ An example UMA VASP server implementation using Typescript. Configure environment variables needed to talk to Lightspark and UMA messages (API keys, etc.). Information on how to set them can be found in `src/UmaConfig.ts`. 1. Create an API token (`LIGHTSPARK_API_TOKEN_CLIENT_ID`, `LIGHTSPARK_API_TOKEN_CLIENT_SECRET`) -in your account's [API config page](https://app.lightspark.com/api-config). + in your account's [API config page](https://app.lightspark.com/api-config). 1. Find your node credentials (`LIGHTSPARK_UMA_NODE_ID`, `LIGHTSPARK_UMA_OSK_NODE_SIGNING_KEY_PASSWORD`) -in your account's [API config page](https://app.lightspark.com/api-config). + in your account's [API config page](https://app.lightspark.com/api-config). 1. Create a secp256k1 private key to use as your encryption private key (`LIGHTSPARK_UMA_ENCRYPTION_PRIVKEY`) and use this private key to wrap the corresponding encryption public key in an X.509 Certificate (`LIGHTSPARK_UMA_ENCRYPTION_CERT_CHAIN`). Similarly for signing, create a secp256k1 private key to use as your signing private key (`LIGHTSPARK_UMA_SIGNING_PRIVKEY`) and use this private key to wrap the corresponding signing public key in an X.509 Certificate (`LIGHTSPARK_UMA_SIGNING_CERT_CHAIN`). You may choose to use the same keypair for encryption and signing. For information on generating these, see [our docs](https://docs.uma.me/uma-standard/keys-authentication-encryption). @@ -115,7 +115,7 @@ $ PORT=8081 yarn start Now, you can test the full uma flow like: ```bash -# First, call to vasp1 to lookup Bob at vasp2. This will return currency conversion info, etc. It will also contain a +# First, call to vasp1 to lookup Bob at vasp2. This will return currency conversion info, etc. It will also contain a # callback ID that you'll need for the next call $ curl -X GET http://localhost:8080/api/umalookup/\$bob@localhost:8081 -u bob:pa55word diff --git a/apps/examples/uma-vasp/package.json b/apps/examples/uma-vasp/package.json index d2c7eae75..07d55883c 100644 --- a/apps/examples/uma-vasp/package.json +++ b/apps/examples/uma-vasp/package.json @@ -17,8 +17,9 @@ "dependencies": { "@lightsparkdev/core": "1.3.0", "@lightsparkdev/lightspark-sdk": "1.9.0", - "@uma-sdk/core": "^1.2.3", + "@uma-sdk/core": "^1.3.0", "express": "^4.18.2", + "express-async-handler": "^1.2.0", "uuid": "^9.0.0", "zod": "^3.22.4" }, diff --git a/apps/examples/uma-vasp/src/ReceivingVasp.ts b/apps/examples/uma-vasp/src/ReceivingVasp.ts index 92b0c4d0d..bc9d06b03 100644 --- a/apps/examples/uma-vasp/src/ReceivingVasp.ts +++ b/apps/examples/uma-vasp/src/ReceivingVasp.ts @@ -5,6 +5,7 @@ import { } from "@lightsparkdev/lightspark-sdk"; import * as uma from "@uma-sdk/core"; import { Express } from "express"; +import asyncHandler from "express-async-handler"; import { v4 as uuidv4 } from "uuid"; import { z } from "zod"; import ComplianceService from "./ComplianceService.js"; @@ -32,66 +33,81 @@ export default class ReceivingVasp { ) {} registerRoutes(app: Express): void { - app.get("/.well-known/lnurlp/:username", async (req, resp) => { - const response = await this.handleLnurlpRequest( - req.params.username, - fullUrlForRequest(req), - ); - sendResponse(resp, response); - }); - - app.get("/api/lnurl/payreq/:uuid", async (req, resp) => { - const response = await this.handleLnurlPayreq( - req.params.uuid, - fullUrlForRequest(req), - ); - sendResponse(resp, response); - }); - - app.post("/api/uma/payreq/:uuid", async (req, resp) => { - const response = await this.handleUmaPayreq( - req.params.uuid, - fullUrlForRequest(req), - req.body, - ); - sendResponse(resp, response); - }); - - app.post("/api/uma/create_invoice", async (req, resp) => { - const user = await this.userService.getCallingUserFromRequest( - fullUrlForRequest(req), - req.headers, - ); - if (!user) { - return sendResponse(resp, { - httpStatus: 401, - data: "Unauthorized. Check your credentials.", - }); - } - const response = await this.handleUmaCreateInvoice( - user, - fullUrlForRequest(req), - ); - sendResponse(resp, response); - }); - - app.post("/api/uma/create_and_send_invoice", async (req, resp) => { - const user = await this.userService.getCallingUserFromRequest( - fullUrlForRequest(req), - req.headers, - ); - if (!user) { - return sendResponse(resp, { - httpStatus: 401, - data: "Unauthorized. Check your credentials.", - }); - } - const response = await this.handleUmaCreateAndSendInvoice( - user, - fullUrlForRequest(req), - ); - sendResponse(resp, response); - }); + app.get( + "/.well-known/lnurlp/:username", + asyncHandler(async (req, resp) => { + const response = await this.handleLnurlpRequest( + req.params.username, + fullUrlForRequest(req), + ); + sendResponse(resp, response); + }), + ); + + app.get( + "/api/lnurl/payreq/:uuid", + asyncHandler(async (req, resp) => { + const response = await this.handleLnurlPayreq( + req.params.uuid, + fullUrlForRequest(req), + ); + sendResponse(resp, response); + }), + ); + + app.post( + "/api/uma/payreq/:uuid", + asyncHandler(async (req, resp) => { + const response = await this.handleUmaPayreq( + req.params.uuid, + fullUrlForRequest(req), + req.body, + ); + sendResponse(resp, response); + }), + ); + + app.post( + "/api/uma/create_invoice", + asyncHandler(async (req, resp) => { + const user = await this.userService.getCallingUserFromRequest( + fullUrlForRequest(req), + req.headers, + ); + if (!user) { + throw new uma.UmaError( + "User not found.", + uma.ErrorCode.USER_NOT_FOUND, + ); + } + const response = await this.handleUmaCreateInvoice( + user, + fullUrlForRequest(req), + ); + sendResponse(resp, response); + }), + ); + + app.post( + "/api/uma/create_and_send_invoice", + asyncHandler(async (req, resp) => { + const user = await this.userService.getCallingUserFromRequest( + fullUrlForRequest(req), + req.headers, + ); + if (!user) { + throw new uma.UmaError( + "User not found.", + uma.ErrorCode.USER_NOT_FOUND, + ); + } + const response = await this.handleUmaCreateAndSendInvoice( + user, + fullUrlForRequest(req), + ); + sendResponse(resp, response); + }), + ); } private async handleLnurlpRequest( @@ -100,7 +116,7 @@ export default class ReceivingVasp { ): Promise { const user = await this.userService.getUserByUma(username); if (!user) { - return { httpStatus: 404, data: "User not found." }; + throw new uma.UmaError("User not found.", uma.ErrorCode.USER_NOT_FOUND); } let lnurlpRequest: uma.LnurlpRequest; @@ -109,18 +125,12 @@ export default class ReceivingVasp { } catch (e: any) { if (e instanceof uma.UnsupportedVersionError) { // For unsupported versions, return a 412 "Precondition Failed" as per the spec. - return { - httpStatus: 412, - data: { - supportedMajorVersions: e.supportedMajorVersions, - unsupportedVersion: e.unsupportedVersion, - }, - }; + throw e; } - return { - httpStatus: 500, - data: new Error("Invalid lnurlp Query", { cause: e }), - }; + throw new uma.UmaError( + "Invalid lnurlp Query", + uma.ErrorCode.PARSE_LNURLP_REQUEST_ERROR, + ); } if (uma.isLnurlpRequestForUma(lnurlpRequest)) { @@ -148,10 +158,10 @@ export default class ReceivingVasp { user: User, ): Promise { if (!uma.isLnurlpRequestForUma(umaQuery)) { - return { - httpStatus: 400, - data: "Invalid UMA query.", - }; + throw new uma.UmaError( + "Invalid UMA query.", + uma.ErrorCode.INTERNAL_ERROR, + ); } if ( !this.complianceService.shouldAcceptTransactionFromVasp( @@ -159,10 +169,10 @@ export default class ReceivingVasp { umaQuery.receiverAddress, ) ) { - return { - httpStatus: 403, - data: "This user is not allowed to transact with this VASP.", - }; + throw new uma.UmaError( + "This user is not allowed to transact with this VASP.", + uma.ErrorCode.COUNTERPARTY_NOT_ALLOWED, + ); } let pubKeys: uma.PubKeyResponse; @@ -173,10 +183,10 @@ export default class ReceivingVasp { }); } catch (e) { console.error(e); - return { - httpStatus: 500, - data: new Error("Failed to fetch public key.", { cause: e }), - }; + throw new uma.UmaError( + "Failed to fetch public key.", + uma.ErrorCode.COUNTERPARTY_PUBKEY_FETCH_ERROR, + ); } try { @@ -186,26 +196,29 @@ export default class ReceivingVasp { this.nonceCache, ); if (!isSignatureValid) { - return { - httpStatus: 500, - data: "Invalid UMA query signature.", - }; + throw new uma.UmaError( + "Invalid UMA query signature.", + uma.ErrorCode.INVALID_SIGNATURE, + ); } } catch (e) { - return { - httpStatus: 500, - data: new Error("Invalid UMA query signature.", { cause: e }), - }; + if (e instanceof uma.UmaError) { + throw e; + } + throw new uma.UmaError( + "Invalid UMA query signature.", + uma.ErrorCode.INVALID_SIGNATURE, + ); } const currencyPrefs = await this.userService.getCurrencyPreferencesForUser( user.id, ); if (!currencyPrefs) { - return { - httpStatus: 500, - data: "Failed to fetch currency preferences.", - }; + throw new uma.UmaError( + "Failed to fetch currency preferences.", + uma.ErrorCode.INTERNAL_ERROR, + ); } const [minSendableSats, maxSendableSats] = @@ -227,10 +240,13 @@ export default class ReceivingVasp { return { httpStatus: 200, data: response.toJsonSchemaObject() }; } catch (e) { console.error(e); - return { - httpStatus: 500, - data: new Error("Failed to generate UMA response.", { cause: e }), - }; + if (e instanceof uma.UmaError) { + throw e; + } + throw new uma.UmaError( + "Failed to generate UMA response.", + uma.ErrorCode.INTERNAL_ERROR, + ); } } @@ -241,7 +257,7 @@ export default class ReceivingVasp { ): Promise { const user = await this.userService.getUserById(userId); if (!user) { - return { httpStatus: 404, data: "User not found." }; + throw new uma.UmaError("User not found.", uma.ErrorCode.USER_NOT_FOUND); } let payreq: uma.PayRequest; @@ -250,20 +266,26 @@ export default class ReceivingVasp { payreq = uma.PayRequest.fromJson(requestBody); } catch (e) { console.error("Failed to parse pay req", e); - return { - httpStatus: 500, - data: new Error("Invalid UMA pay request.", { cause: e }), - }; + throw new uma.UmaError( + "Invalid UMA pay request.", + uma.ErrorCode.PARSE_PAYREQ_REQUEST_ERROR, + ); } console.log( `Parsed payreq: ${JSON.stringify(payreq.toJsonSchemaObject(), null, 2)}`, ); if (!payreq.isUma()) { - return { httpStatus: 400, data: "Invalid UMA payreq." }; + throw new uma.UmaError( + "Invalid UMA pay request.", + uma.ErrorCode.MISSING_REQUIRED_UMA_PARAMETERS, + ); } if (!payreq.payerData!.identifier) { - return { httpStatus: 400, data: "Payer identifier is missing" }; + throw new uma.UmaError( + "Payer identifier is missing.", + uma.ErrorCode.MISSING_REQUIRED_UMA_PARAMETERS, + ); } let pubKeys: uma.PubKeyResponse; @@ -276,10 +298,10 @@ export default class ReceivingVasp { }); } catch (e) { console.error(e); - return { - httpStatus: 500, - data: new Error("Failed to fetch public key.", { cause: e }), - }; + throw new uma.UmaError( + "Failed to fetch public key.", + uma.ErrorCode.COUNTERPARTY_PUBKEY_FETCH_ERROR, + ); } console.log(`Fetched pubkeys: ${JSON.stringify(pubKeys, null, 2)}`); @@ -291,14 +313,20 @@ export default class ReceivingVasp { this.nonceCache, ); if (!isSignatureValid) { - return { httpStatus: 400, data: "Invalid payreq signature." }; + throw new uma.UmaError( + "Invalid payreq signature.", + uma.ErrorCode.INVALID_SIGNATURE, + ); } } catch (e) { console.error(e); - return { - httpStatus: 500, - data: new Error("Invalid payreq signature.", { cause: e }), - }; + if (e instanceof uma.UmaError) { + throw e; + } + throw new uma.UmaError( + "Invalid payreq signature.", + uma.ErrorCode.INVALID_SIGNATURE, + ); } console.log(`Verified payreq signature.`); @@ -315,25 +343,28 @@ export default class ReceivingVasp { ); if (!currencyPrefs) { console.error("Failed to fetch currency preferences."); - return { - httpStatus: 500, - data: "Failed to fetch currency preferences.", - }; + throw new uma.UmaError( + "Failed to fetch currency preferences.", + uma.ErrorCode.INTERNAL_ERROR, + ); } let receivingCurrency = currencyPrefs.find( (c) => c.code === payreq.receivingCurrencyCode, ); if (payreq.receivingCurrencyCode && !receivingCurrency) { console.error(`Invalid currency: ${payreq.receivingCurrencyCode}`); - return { - httpStatus: 400, - data: `Invalid currency. This user does not accept ${payreq.receivingCurrencyCode}.`, - }; + throw new uma.UmaError( + `Invalid currency. This user does not accept ${payreq.receivingCurrencyCode}.`, + uma.ErrorCode.INVALID_CURRENCY, + ); } else if (!payreq.receivingCurrencyCode) { receivingCurrency = SATS_CURRENCY; } else if (!receivingCurrency) { // This can't actually happen, but TypeScript doesn't know that. - return { httpStatus: 400, data: "Invalid currency." }; + throw new uma.UmaError( + "Invalid currency.", + uma.ErrorCode.INVALID_CURRENCY, + ); } const isSendingAmountMsats = !payreq.sendingAmountCurrencyCode; @@ -345,12 +376,11 @@ export default class ReceivingVasp { : payreq.amount >= receivingCurrency.minSendable && payreq.amount <= receivingCurrency.maxSendable; if (!isCurrencyAmountInBounds) { - return { - httpStatus: 400, - data: - `Invalid amount. This user only accepts between ${receivingCurrency.minSendable} ` + + throw new uma.UmaError( + `Invalid amount. This user only accepts between ${receivingCurrency.minSendable} ` + `and ${receivingCurrency.maxSendable} ${receivingCurrency.code}.`, - }; + uma.ErrorCode.AMOUNT_OUT_OF_RANGE, + ); } // TODO(Jeremy): Move this to the currency service. @@ -359,10 +389,10 @@ export default class ReceivingVasp { payreq.sendingAmountCurrencyCode !== "SAT" && payreq.sendingAmountCurrencyCode !== receivingCurrency.code ) { - return { - httpStatus: 400, - data: `Invalid sending currency. Cannot convert from ${payreq.sendingAmountCurrencyCode}.`, - }; + throw new uma.UmaError( + `Invalid sending currency. Cannot convert from ${payreq.sendingAmountCurrencyCode}.`, + uma.ErrorCode.INVALID_CURRENCY, + ); } const receiverFeesMillisats = 0; const amountMsats = isSendingAmountMsats @@ -378,10 +408,10 @@ export default class ReceivingVasp { payreq.payerData.compliance?.utxos ?? [], ); if (!shouldTransact) { - return { - httpStatus: 403, - data: "This transaction is too risky.", - }; + throw new uma.UmaError( + "This transaction is too risky.", + uma.ErrorCode.COUNTERPARTY_NOT_ALLOWED, + ); } } @@ -449,10 +479,13 @@ export default class ReceivingVasp { } catch (e) { console.log(`Failed to generate UMA response: ${e}`); console.error(e); - return { - httpStatus: 500, - data: new Error("Failed to generate UMA response.", { cause: e }), - }; + if (e instanceof uma.UmaError) { + throw e; + } + throw new uma.UmaError( + "Failed to generate UMA response.", + uma.ErrorCode.INTERNAL_ERROR, + ); } } @@ -466,12 +499,18 @@ export default class ReceivingVasp { private async handleUmaCreateAndSendInvoice(user: User, requestUrl: URL) { const senderUma = requestUrl.searchParams.get("senderUma"); if (!senderUma) { - return { httpStatus: 422, data: "missing parameter senderUma" }; + throw new uma.UmaError( + "missing parameter senderUma", + uma.ErrorCode.MISSING_REQUIRED_UMA_PARAMETERS, + ); } const { httpStatus, data: bech32EncodedInvoice } = await this.parseAndEncodeUmaInvoice(user, requestUrl, senderUma); if (httpStatus !== 200) { - return { httpStatus, data: bech32EncodedInvoice }; + throw new uma.UmaError( + `Failed to parse and encode UMA invoice: ${bech32EncodedInvoice}`, + uma.ErrorCode.INVALID_INVOICE, + ); } const [, senderDomain] = senderUma.split("@"); @@ -485,7 +524,10 @@ export default class ReceivingVasp { ); } catch (e) { console.error("Error fetching UMA configuration:", e); - return { httpStatus: 500, data: "Error fetching UMA configuration." }; + throw new uma.UmaError( + "Error fetching UMA configuration.", + uma.ErrorCode.INTERNAL_ERROR, + ); } const senderUrl = new URL(umaRequestEndpoint); @@ -499,14 +541,17 @@ export default class ReceivingVasp { body: JSON.stringify({ invoice: bech32EncodedInvoice }), }); } catch (e) { - return { httpStatus: 500, data: "Error sending payreq." }; + throw new uma.UmaError( + "Error sending payreq.", + uma.ErrorCode.PAYREQ_REQUEST_FAILED, + ); } if (response.status !== 200) { - return { - httpStatus: 200, - data: `Error: request pay invoice failed: ${response.statusText}`, - }; + throw new uma.UmaError( + `Error sending payreq: ${response.statusText}`, + uma.ErrorCode.PAYREQ_REQUEST_FAILED, + ); } return { @@ -525,32 +570,32 @@ export default class ReceivingVasp { user.id, ); if (!currencyPrefs) { - return { - httpStatus: 500, - data: "Failed to fetch currency preferences.", - }; + throw new uma.UmaError( + "Failed to fetch currency preferences.", + uma.ErrorCode.INTERNAL_ERROR, + ); } let receivingCurrency = currencyPrefs.find((c) => c.code === currencyCode); if (currencyCode && !receivingCurrency) { console.error(`Invalid currency: ${currencyCode}`); - return { - httpStatus: 400, - data: `Invalid currency. This user does not accept ${currencyCode}.`, - }; + throw new uma.UmaError( + `Invalid currency. This user does not accept ${currencyCode}.`, + uma.ErrorCode.INVALID_CURRENCY, + ); } else if (!currencyCode) { - return { - httpStatus: 422, - data: `Invalid target currency code: ${currencyCode}`, - }; + throw new uma.UmaError( + `Invalid target currency code: ${currencyCode}`, + uma.ErrorCode.INVALID_CURRENCY, + ); } const amountResult = z.coerce .number() .safeParse(requestUrl.searchParams.get("amount")); if (!amountResult.success) { - return { - httpStatus: 400, - data: "Invalid amount parameter.", - }; + throw new uma.UmaError( + "Invalid amount parameter.", + uma.ErrorCode.INVALID_INPUT, + ); } const amount = amountResult.data; const { code, minSendable, maxSendable } = @@ -563,12 +608,11 @@ export default class ReceivingVasp { ? amount >= minSendableSats && amount / 1000 <= maxSendableSats : amount >= minSendable && amount <= maxSendable; if (!isCurrencyAmountInBounds) { - return { - httpStatus: 400, - data: - `Invalid amount. This user only accepts between ${minSendable} ` + + throw new uma.UmaError( + `Invalid amount. This user only accepts between ${minSendable} ` + `and ${maxSendable} ${code}.`, - }; + uma.ErrorCode.AMOUNT_OUT_OF_RANGE, + ); } const umaDomain = hostNameWithPort(requestUrl); @@ -591,7 +635,7 @@ export default class ReceivingVasp { ): Promise { const protocol = isDomainLocalhost(fullUrl.hostname) ? "http" : "https"; const umaConfigResponse = await fetch( - `http://${domain}/.well-known/uma-configuration`, + `${protocol}://${domain}/.well-known/uma-configuration`, ); if (!umaConfigResponse.ok) { throw new Error(`HTTP error! status: ${umaConfigResponse.status}`); @@ -651,17 +695,17 @@ export default class ReceivingVasp { ): Promise { const user = await this.userService.getUserById(userId); if (!user) { - return { httpStatus: 404, data: "User not found." }; + throw new uma.UmaError("User not found.", uma.ErrorCode.USER_NOT_FOUND); } let request: uma.PayRequest; try { request = uma.PayRequest.fromUrlSearchParams(requestUrl.searchParams); } catch (e) { - return { - httpStatus: 400, - data: "Invalid pay request: " + e, - }; + throw new uma.UmaError( + "Invalid pay request: " + e, + uma.ErrorCode.PARSE_PAYREQ_REQUEST_ERROR, + ); } return this.handlePayReq(request, user, requestUrl); @@ -690,13 +734,22 @@ export default class ReceivingVasp { try { node = await this.lightsparkClient.executeRawQuery(nodeQuery); } catch (e) { - throw new Error(`Failed to fetch node ${this.config.nodeID}.`); + throw new uma.UmaError( + `Failed to fetch node ${this.config.nodeID}.`, + uma.ErrorCode.INTERNAL_ERROR, + ); } if (!node) { - throw new Error(`Node ${this.config.nodeID} not found.`); + throw new uma.UmaError( + `Node ${this.config.nodeID} not found.`, + uma.ErrorCode.INTERNAL_ERROR, + ); } if (!node.publicKey) { - throw new Error(`Node ${this.config.nodeID} has no known public key.`); + throw new uma.UmaError( + `Node ${this.config.nodeID} has no known public key.`, + uma.ErrorCode.INTERNAL_ERROR, + ); } return node.publicKey; } diff --git a/apps/examples/uma-vasp/src/SendingVasp.ts b/apps/examples/uma-vasp/src/SendingVasp.ts index f50c6ac94..3e1cfff20 100644 --- a/apps/examples/uma-vasp/src/SendingVasp.ts +++ b/apps/examples/uma-vasp/src/SendingVasp.ts @@ -9,7 +9,8 @@ import { TransactionStatus, } from "@lightsparkdev/lightspark-sdk"; import * as uma from "@uma-sdk/core"; -import { Express, Request } from "express"; +import { Express } from "express"; +import asyncHandler from "express-async-handler"; import ComplianceService from "./ComplianceService.js"; import InternalLedgerService from "./InternalLedgerService.js"; import SendingVaspRequestCache, { @@ -44,120 +45,137 @@ export default class SendingVasp { private readonly nonceCache: uma.NonceValidator, ) {} - registerRoutes(app: Express) { - app.get("/api/umalookup/:receiver", async (req: Request, resp) => { - const user = await this.userService.getCallingUserFromRequest( - fullUrlForRequest(req), - req.headers, - ); - if (!user) { - return sendResponse(resp, { - httpStatus: 401, - data: "Unauthorized. Check your credentials.", - }); - } - const response = await this.handleClientUmaLookup( - user, - req.params.receiver, - fullUrlForRequest(req), - ); - sendResponse(resp, response); - }); - - app.get("/api/umapayreq/:callbackUuid", async (req: Request, resp) => { - const user = await this.userService.getCallingUserFromRequest( - fullUrlForRequest(req), - req.headers, - ); - if (!user) { - return sendResponse(resp, { - httpStatus: 401, - data: "Unauthorized. Check your credentials.", - }); - } - const response = await this.handleClientUmaPayreq( - user, - req.params.callbackUuid, - fullUrlForRequest(req), - ); - sendResponse(resp, response); - }); + registerRoutes(app: Express): void { + app.get( + "/api/umalookup/:receiver", + asyncHandler(async (req, resp) => { + const user = await this.userService.getCallingUserFromRequest( + fullUrlForRequest(req), + req.headers, + ); + if (!user) { + throw new uma.UmaError( + "Unauthorized. Check your credentials.", + uma.ErrorCode.FORBIDDEN, + ); + } + const response = await this.handleClientUmaLookup( + user, + req.params.receiver, + fullUrlForRequest(req), + ); + sendResponse(resp, response); + }), + ); - app.post("/api/sendpayment/:callbackUuid", async (req, resp) => { - const user = await this.userService.getCallingUserFromRequest( - fullUrlForRequest(req), - req.headers, - ); - if (!user) { - return sendResponse(resp, { - httpStatus: 401, - data: "Unauthorized. Check your credentials.", - }); - } - const response = await this.handleClientSendPayment( - user, - req.params.callbackUuid, - fullUrlForRequest(req), - ); - sendResponse(resp, response); - }); + app.get( + "/api/umapayreq/:callbackUuid", + asyncHandler(async (req, resp) => { + const user = await this.userService.getCallingUserFromRequest( + fullUrlForRequest(req), + req.headers, + ); + if (!user) { + throw new uma.UmaError( + "Unauthorized. Check your credentials.", + uma.ErrorCode.FORBIDDEN, + ); + } + const response = await this.handleClientUmaPayreq( + user, + req.params.callbackUuid, + fullUrlForRequest(req), + ); + sendResponse(resp, response); + }), + ); - app.post("/api/uma/pay_invoice", async (req, resp) => { - const user = await this.userService.getCallingUserFromRequest( - fullUrlForRequest(req), - req.headers, - ); - if (!user) { - return sendResponse(resp, { - httpStatus: 401, - data: "Unauthorized. Check your credentials.", - }); - } - const response = await this.handlePayInvoice( - user, - fullUrlForRequest(req), - ); - sendResponse(resp, response); - }); + app.post( + "/api/sendpayment/:callbackUuid", + asyncHandler(async (req, resp) => { + const user = await this.userService.getCallingUserFromRequest( + fullUrlForRequest(req), + req.headers, + ); + if (!user) { + throw new uma.UmaError( + "Unauthorized. Check your credentials.", + uma.ErrorCode.FORBIDDEN, + ); + } + const response = await this.handleClientSendPayment( + user, + req.params.callbackUuid, + fullUrlForRequest(req), + ); + sendResponse(resp, response); + }), + ); - app.post("/api/uma/request_invoice_payment", async (req, resp) => { - let invoiceBech32Str; - try { - invoiceBech32Str = JSON.parse(req.body)["invoice"]; - } catch (e) { - return sendResponse(resp, { - httpStatus: 500, - data: "Error. unable to parse uma invoice .", - }); - } - if (!invoiceBech32Str || typeof invoiceBech32Str !== "string") { - return sendResponse(resp, { - httpStatus: 401, - data: "Error. Required invoice not provided.", - }); - } - const invoice = uma.InvoiceSerializer.fromBech32(invoiceBech32Str); - if (!invoice.senderUma) { - return sendResponse(resp, { - httpStatus: 401, - data: "Error. Sender Uma not present on invoice.", - }); - } + app.post( + "/api/uma/pay_invoice", + asyncHandler(async (req, resp) => { + const user = await this.userService.getCallingUserFromRequest( + fullUrlForRequest(req), + req.headers, + ); + if (!user) { + throw new uma.UmaError( + "Unauthorized. Check your credentials.", + uma.ErrorCode.FORBIDDEN, + ); + } + const response = await this.handlePayInvoice( + user, + fullUrlForRequest(req), + ); + sendResponse(resp, response); + }), + ); - const response = await this.handleRequestPayInvoice( - invoice, - invoiceBech32Str, - ); - sendResponse(resp, response); - }); + app.post( + "/api/uma/request_invoice_payment", + asyncHandler(async (req, resp) => { + let invoiceBech32Str; + try { + invoiceBech32Str = JSON.parse(req.body)["invoice"]; + } catch (e) { + throw new uma.UmaError( + "Error. unable to parse uma invoice .", + uma.ErrorCode.INVALID_INPUT, + ); + } + if (!invoiceBech32Str || typeof invoiceBech32Str !== "string") { + throw new uma.UmaError( + "Error. Required invoice not provided.", + uma.ErrorCode.INVALID_INPUT, + ); + } + const invoice = uma.InvoiceSerializer.fromBech32(invoiceBech32Str); + if (!invoice.senderUma) { + throw new uma.UmaError( + "Error. Sender Uma not present on invoice.", + uma.ErrorCode.INVALID_INPUT, + ); + } + const response = await this.handleRequestPayInvoice( + invoice, + invoiceBech32Str, + ); + sendResponse(resp, response); + }), + ); - app.get("/api/uma/pending_requests", async (req, resp) => { - const pendingRequests = this.requestCache.getPendingPayReqs(); - sendResponse(resp, { - httpStatus: 200, - data: pendingRequests, - }); - }); + app.get( + "/api/uma/pending_requests", + asyncHandler(async (req, resp) => { + const pendingRequests = this.requestCache.getPendingPayReqs(); + sendResponse(resp, { + httpStatus: 200, + data: pendingRequests, + }); + }), + ); } private async handleClientUmaLookup( @@ -166,13 +184,13 @@ export default class SendingVasp { requestUrl: URL, ): Promise { if (!receiverUmaAddress) { - return { httpStatus: 400, data: "Missing receiver" }; + throw new uma.UmaError("Missing receiver", uma.ErrorCode.INVALID_INPUT); } const [receiverId, receivingVaspDomain] = receiverUmaAddress.split("@"); if (!receiverId || !receivingVaspDomain) { console.error(`Invalid receiver: ${receiverUmaAddress}`); - return { httpStatus: 400, data: "Invalid receiver" }; + throw new uma.UmaError("Invalid receiver", uma.ErrorCode.INVALID_INPUT); } if ( @@ -182,10 +200,10 @@ export default class SendingVasp { receiverUmaAddress, ) ) { - return { - httpStatus: 400, - data: `Transaction not allowed to ${receiverUmaAddress}.`, - }; + throw new uma.UmaError( + `Transaction not allowed to ${receiverUmaAddress}.`, + uma.ErrorCode.COUNTERPARTY_NOT_ALLOWED, + ); } let lnurlpRequestUrl: URL; @@ -210,7 +228,10 @@ export default class SendingVasp { response = await fetch(lnurlpRequestUrl); } catch (e) { console.error("Error fetching Lnurlp request.", e); - return { httpStatus: 424, data: "Error fetching Lnurlp request." }; + throw new uma.UmaError( + "Error fetching Lnurlp request.", + uma.ErrorCode.INTERNAL_ERROR, + ); } if (response.status === 412) { @@ -222,18 +243,18 @@ export default class SendingVasp { ); } catch (e) { console.error("Error fetching Lnurlp request.", e); - return { - httpStatus: 424, - data: new Error("Error fetching Lnurlp request.", { cause: e }), - }; + throw new uma.UmaError( + "Error fetching Lnurlp request.", + uma.ErrorCode.INTERNAL_ERROR, + ); } } if (!response.ok) { - return { - httpStatus: 424, - data: `Error fetching Lnurlp request. ${response.status}`, - }; + throw new uma.UmaError( + `Error fetching Lnurlp request. ${response.status}`, + uma.ErrorCode.LNURLP_REQUEST_FAILED, + ); } let lnurlpResponse: uma.LnurlpResponse; @@ -242,7 +263,10 @@ export default class SendingVasp { lnurlpResponse = uma.LnurlpResponse.fromJson(responseJson); } catch (e) { console.error("Error parsing lnurlp response.", e, responseJson); - return { httpStatus: 424, data: `Error parsing Lnurlp response. ${e}` }; + throw new uma.UmaError( + `Error parsing Lnurlp response. ${e}`, + uma.ErrorCode.PARSE_LNURLP_RESPONSE_ERROR, + ); } if (!lnurlpResponse.isUma()) { @@ -256,10 +280,10 @@ export default class SendingVasp { let pubKeys = await this.fetchPubKeys(receivingVaspDomain); if (!pubKeys) - return { - httpStatus: 424, - data: "Error fetching receiving vasp public key.", - }; + throw new uma.UmaError( + "Error fetching receiving vasp public key.", + uma.ErrorCode.COUNTERPARTY_PUBKEY_FETCH_ERROR, + ); try { const isSignatureValid = await uma.verifyUmaLnurlpResponseSignature( @@ -268,16 +292,20 @@ export default class SendingVasp { this.nonceCache, ); if (!isSignatureValid) { - return { httpStatus: 424, data: "Invalid UMA response signature." }; + throw new uma.UmaError( + "Invalid UMA response signature.", + uma.ErrorCode.INVALID_SIGNATURE, + ); } } catch (e) { console.error("Error verifying UMA response signature.", e); - return { - httpStatus: 424, - data: new Error("Error verifying UMA response signature.", { - cause: e, - }), - }; + if (e instanceof uma.UmaError) { + throw e; + } + throw new uma.UmaError( + "Error verifying UMA response signature.", + uma.ErrorCode.INVALID_SIGNATURE, + ); } const callbackUuid = this.requestCache.saveLnurlpResponseData( @@ -351,26 +379,35 @@ export default class SendingVasp { requestUrl: URL, ): Promise { if (!callbackUuid || callbackUuid === "") { - return { httpStatus: 400, data: "Missing callbackUuid" }; + throw new uma.UmaError( + "Missing callbackUuid", + uma.ErrorCode.INVALID_INPUT, + ); } const initialRequestData = this.requestCache.getLnurlpResponseData(callbackUuid); if (!initialRequestData) { - return { httpStatus: 400, data: "callbackUuid not found" }; + throw new uma.UmaError( + "callbackUuid not found", + uma.ErrorCode.REQUEST_NOT_FOUND, + ); } const amountStr = requestUrl.searchParams.get("amount"); if (!amountStr || typeof amountStr !== "string") { - return { httpStatus: 400, data: "Missing amount" }; + throw new uma.UmaError("Missing amount", uma.ErrorCode.INVALID_INPUT); } const amount = parseFloat(amountStr); if (isNaN(amount)) { - return { httpStatus: 400, data: "Invalid amount" }; + throw new uma.UmaError("Invalid amount", uma.ErrorCode.INVALID_INPUT); } if (!initialRequestData.lnurlpResponse) { - return { httpStatus: 400, data: "Invalid callbackUuid" }; + throw new uma.UmaError( + "Invalid callbackUuid", + uma.ErrorCode.INVALID_INPUT, + ); } const receivingCurrencyCode = requestUrl.searchParams.get( @@ -401,18 +438,27 @@ export default class SendingVasp { } if (!payerProfile) { - return { httpStatus: 400, data: "Missing payerData" }; + throw new uma.UmaError( + "Missing payerData", + uma.ErrorCode.REQUEST_NOT_FOUND, + ); } if (!receivingCurrencyCode || typeof receivingCurrencyCode !== "string") { - return { httpStatus: 400, data: "Missing currencyCode" }; + throw new uma.UmaError( + "Missing currencyCode", + uma.ErrorCode.INVALID_INPUT, + ); } const selectedCurrency = ( initialRequestData.lnurlpResponse.currencies || [] ).find((c) => c.code === receivingCurrencyCode); if (selectedCurrency === undefined) { - return { httpStatus: 400, data: "Currency code not supported" }; + throw new uma.UmaError( + "Currency code not supported", + uma.ErrorCode.INVALID_CURRENCY, + ); } const msatsParam = requestUrl.searchParams.get("isAmountInMsats"); @@ -426,7 +472,10 @@ export default class SendingVasp { await this.userService.getCurrencyPreferencesForUser(user.id) )?.find((c) => c.code === sendingCurrencyCode); if (sendingCurrency === undefined) { - return { httpStatus: 400, data: "Sending currency code not supported" }; + throw new uma.UmaError( + "Sending currency code not supported", + uma.ErrorCode.INVALID_CURRENCY, + ); } const sendingCurrencyAmount = amountValueMillisats / sendingCurrency.multiplier; @@ -439,17 +488,20 @@ export default class SendingVasp { sendingCurrencyCode, ) ) { - return { httpStatus: 400, data: "Insufficient balance." }; + throw new uma.UmaError( + "Insufficient balance.", + uma.ErrorCode.INTERNAL_ERROR, + ); } let pubKeys = await this.fetchPubKeys( initialRequestData.receivingVaspDomain, ); if (!pubKeys) { - return { - httpStatus: 424, - data: "Error fetching receiving vasp public key.", - }; + throw new uma.UmaError( + "Error fetching receiving vasp public key.", + uma.ErrorCode.COUNTERPARTY_PUBKEY_FETCH_ERROR, + ); } const umaVersion = @@ -541,7 +593,13 @@ export default class SendingVasp { }); } catch (e) { console.error("Error generating payreq.", e); - return { httpStatus: 500, data: "Error generating payreq." }; + if (e instanceof uma.UmaError) { + throw e; + } + throw new uma.UmaError( + "Error generating payreq.", + uma.ErrorCode.INTERNAL_ERROR, + ); } console.log( @@ -557,14 +615,20 @@ export default class SendingVasp { body: payReq.toJsonString(), }); } catch (e) { - return { httpStatus: 500, data: "Error sending payreq." }; + if (e instanceof uma.UmaError) { + throw e; + } + throw new uma.UmaError( + "Error sending payreq.", + uma.ErrorCode.INTERNAL_ERROR, + ); } if (!response.ok || response.status !== 200) { - return { - httpStatus: 424, - data: `Payreq failed. ${response.status}, ${response.body}`, - }; + throw new uma.UmaError( + `Payreq failed. ${response.status}, ${response.body}`, + uma.ErrorCode.PAYREQ_REQUEST_FAILED, + ); } let payResponse: uma.PayReqResponse; @@ -574,15 +638,18 @@ export default class SendingVasp { } catch (e) { console.error("Error parsing payreq response. Raw response: " + bodyText); console.error("Error:", e); - return { httpStatus: 424, data: "Error parsing payreq response." }; + throw new uma.UmaError( + "Error parsing payreq response.", + uma.ErrorCode.PARSE_PAYREQ_RESPONSE_ERROR, + ); } if (!payResponse.isUma()) { console.log("Received non-uma response for uma payreq."); - return { - httpStatus: 424, - data: "Received non-uma response for uma payreq.", - }; + throw new uma.UmaError( + "Received non-uma response for uma payreq.", + uma.ErrorCode.MISSING_REQUIRED_UMA_PARAMETERS, + ); } try { @@ -595,18 +662,21 @@ export default class SendingVasp { this.nonceCache, ); if (!isSignatureValid) { - return { - httpStatus: 424, - data: "Invalid payreq response signature.", - }; + throw new uma.UmaError( + "Invalid payreq response signature.", + uma.ErrorCode.INVALID_SIGNATURE, + ); } } } catch (e) { console.error(e); - return { - httpStatus: 424, - data: new Error("Invalid payreq response signature.", { cause: e }), - }; + if (e instanceof uma.UmaError) { + throw e; + } + throw new uma.UmaError( + "Invalid payreq response signature.", + uma.ErrorCode.INVALID_SIGNATURE, + ); } console.log(`Verified payreq response signature.`); @@ -619,10 +689,10 @@ export default class SendingVasp { payResponse.payeeData?.compliance?.utxos ?? [], ); if (!shouldTransact) { - return { - httpStatus: 424, - data: "Transaction not allowed due to risk rating.", - }; + throw new uma.UmaError( + "Transaction not allowed due to risk rating.", + uma.ErrorCode.COUNTERPARTY_NOT_ALLOWED, + ); } let invoice: InvoiceData; @@ -630,7 +700,10 @@ export default class SendingVasp { invoice = await this.lightsparkClient.decodeInvoice(payResponse.pr); } catch (e) { console.error("Error decoding invoice.", e); - return { httpStatus: 500, data: "Error decoding invoice." }; + throw new uma.UmaError( + "Error decoding invoice.", + uma.ErrorCode.INTERNAL_ERROR, + ); } const senderCurrencies = @@ -697,28 +770,37 @@ export default class SendingVasp { }); } catch (e) { console.error("Error sending payreq.", e); - return { httpStatus: 500, data: "Error sending payreq." }; + throw new uma.UmaError( + "Error sending payreq.", + uma.ErrorCode.INTERNAL_ERROR, + ); } const responseText = await response.text(); if (!response.ok) { - return { httpStatus: 424, data: `Payreq failed. ${response.status}` }; + throw new uma.UmaError( + `Payreq failed. ${response.status}`, + uma.ErrorCode.PAYREQ_REQUEST_FAILED, + ); } const responseJson = JSON.parse(responseText); if (responseJson.status === "ERROR") { console.error("Error on pay request.", responseJson.reason); - return { - httpStatus: 424, - data: `Error on pay request. reason: ${responseJson.reason}`, - }; + throw new uma.UmaError( + `Error on pay request. reason: ${responseJson.reason} code: ${responseJson.code}`, + uma.ErrorCode.PAYREQ_REQUEST_FAILED, + ); } let payreqResponse: uma.PayReqResponse; try { payreqResponse = uma.PayReqResponse.fromJson(responseText); } catch (e) { console.error("Error parsing payreq response.", e); - return { httpStatus: 424, data: "Error parsing payreq response." }; + throw new uma.UmaError( + "Error parsing payreq response.", + uma.ErrorCode.PARSE_PAYREQ_RESPONSE_ERROR, + ); } const encodedInvoice = payreqResponse.pr; @@ -728,7 +810,10 @@ export default class SendingVasp { invoice = await this.lightsparkClient.decodeInvoice(encodedInvoice); } catch (e) { console.error("Error decoding invoice.", e); - return { httpStatus: 500, data: "Error decoding invoice." }; + throw new uma.UmaError( + "Error decoding invoice.", + uma.ErrorCode.INVALID_INVOICE, + ); } const newCallbackUuid = this.requestCache.savePayReqData( @@ -758,7 +843,7 @@ export default class SendingVasp { getLightsparkNodeQuery(this.config.nodeID), ); if (!node) { - throw new Error("Node not found."); + throw new uma.UmaError("Node not found.", uma.ErrorCode.INTERNAL_ERROR); } return node; @@ -772,7 +857,13 @@ export default class SendingVasp { }); } catch (e) { console.error("Error fetching public key.", e); - return null; + if (e instanceof uma.UmaError) { + throw e; + } + throw new uma.UmaError( + "Error fetching public key.", + uma.ErrorCode.COUNTERPARTY_PUBKEY_FETCH_ERROR, + ); } } @@ -782,23 +873,29 @@ export default class SendingVasp { requestUrl: URL, ): Promise { if (!callbackUuid || callbackUuid === "") { - return { httpStatus: 400, data: "Missing callbackUuid" }; + throw new uma.UmaError( + "Missing callbackUuid", + uma.ErrorCode.INVALID_INPUT, + ); } const payReqData = this.requestCache.getPayReqData(callbackUuid); if (!payReqData || !payReqData?.invoiceData) { - return { httpStatus: 400, data: "callbackUuid not found" }; + throw new uma.UmaError( + "Could not find pay request associated with uuid", + uma.ErrorCode.REQUEST_NOT_FOUND, + ); } if (new Date(payReqData.invoiceData.expiresAt) < new Date()) { - return { httpStatus: 400, data: "Invoice expired" }; + throw new uma.UmaError("Invoice expired", uma.ErrorCode.INVOICE_EXPIRED); } if (payReqData.invoiceData.amount.originalValue <= 0) { - return { - httpStatus: 400, - data: "Invalid invoice amount. Positive amount required.", - }; + throw new uma.UmaError( + "Invalid invoice amount. Positive amount required.", + uma.ErrorCode.INVALID_INPUT, + ); } const sendingCurrencyCode = @@ -807,7 +904,10 @@ export default class SendingVasp { await this.userService.getCurrencyPreferencesForUser(user.id) )?.find((c) => c.code === sendingCurrencyCode); if (sendingCurrency === undefined) { - return { httpStatus: 400, data: "Sending currency code not supported" }; + throw new uma.UmaError( + "Sending currency code not supported", + uma.ErrorCode.INVALID_CURRENCY, + ); } const amountMsats = convertCurrencyAmount( @@ -817,16 +917,16 @@ export default class SendingVasp { const sendingCurrencyAmount = amountMsats / sendingCurrency.multiplier; if (sendingCurrencyAmount < sendingCurrency.minSendable) { - return { - httpStatus: 400, - data: `Invalid invoice amount. Minimum amount is ${sendingCurrency.minSendable} ${sendingCurrency.code}.`, - }; + throw new uma.UmaError( + `Invalid invoice amount. Minimum amount is ${sendingCurrency.minSendable} ${sendingCurrency.code}.`, + uma.ErrorCode.AMOUNT_OUT_OF_RANGE, + ); } if (sendingCurrencyAmount > sendingCurrency.maxSendable) { - return { - httpStatus: 400, - data: `Invalid invoice amount. Maximum amount is ${sendingCurrency.maxSendable} ${sendingCurrency.code}.`, - }; + throw new uma.UmaError( + `Invalid invoice amount. Maximum amount is ${sendingCurrency.maxSendable} ${sendingCurrency.code}.`, + uma.ErrorCode.AMOUNT_OUT_OF_RANGE, + ); } if ( @@ -837,7 +937,10 @@ export default class SendingVasp { sendingCurrencyCode, ) ) { - return { httpStatus: 400, data: "Insufficient balance." }; + throw new uma.UmaError( + "Insufficient balance.", + uma.ErrorCode.INTERNAL_ERROR, + ); } let payment: OutgoingPayment; @@ -845,7 +948,10 @@ export default class SendingVasp { try { const signingKeyLoaded = await this.loadNodeSigningKey(); if (!signingKeyLoaded) { - throw new Error("Error loading signing key."); + throw new uma.UmaError( + "Error loading signing key.", + uma.ErrorCode.INTERNAL_ERROR, + ); } const paymentResult = await this.lightsparkClient.payUmaInvoice( this.config.nodeID, @@ -853,7 +959,10 @@ export default class SendingVasp { /* maxFeesMsats */ 1_000_000, ); if (!paymentResult) { - throw new Error("Payment request failed."); + throw new uma.UmaError( + "Payment request failed.", + uma.ErrorCode.INTERNAL_ERROR, + ); } paymentId = paymentResult.id; await this.ledgerService.recordOutgoingTransactionBegan( @@ -885,7 +994,13 @@ export default class SendingVasp { paymentId, ); } - return { httpStatus: 500, data: "Error paying invoice." }; + if (e instanceof uma.UmaError) { + throw e; + } + throw new uma.UmaError( + "Error paying invoice.", + uma.ErrorCode.INTERNAL_ERROR, + ); } await this.sendPostTransactionCallback(payment, payReqData, requestUrl); @@ -912,7 +1027,10 @@ export default class SendingVasp { private async handlePayInvoice(user: User, requestUrl: URL) { const invoiceStr = requestUrl.searchParams.get("invoice"); if (!invoiceStr) { - return { httpStatus: 422, data: "Missing argument: invoice" }; + throw new uma.UmaError( + "Missing argument: invoice", + uma.ErrorCode.INVALID_INPUT, + ); } // payments can also be done via uuid of cached request // by checking uma prefix, determine if invoiceStr is a bech32 encoded invoice. @@ -926,13 +1044,13 @@ export default class SendingVasp { try { invoice = uma.InvoiceSerializer.fromBech32(encodedInvoice ?? invoiceStr); } catch (e) { - return { - httpStatus: 500, - data: "Cannot parse Invoice from invoice paramater", - }; + throw new uma.UmaError( + "Cannot parse Invoice from invoice parameter", + uma.ErrorCode.INVALID_INVOICE, + ); } - let payerPofile = this.getPayerProfile( + let payerProfile = this.getPayerProfile( user, invoice.requiredPayerData ?? {}, this.getSendingVaspDomain(requestUrl), @@ -942,24 +1060,27 @@ export default class SendingVasp { const [, receivingVaspDomain] = invoice.receiverUma.split("@"); let pubKeys = await this.fetchPubKeys(receivingVaspDomain); if (!pubKeys) - return { - httpStatus: 424, - data: "Error fetching receiving vasp public key.", - }; + throw new uma.UmaError( + "Error fetching receiving vasp public key.", + uma.ErrorCode.COUNTERPARTY_PUBKEY_FETCH_ERROR, + ); const signatureVerified = uma.verifyUmaInvoiceSignature( invoice, pubKeys.getSigningPubKey(), ); if (!signatureVerified) { - return { httpStatus: 500, data: "Unable to verify invoice signature" }; + throw new uma.UmaError( + "Unable to verify invoice signature", + uma.ErrorCode.INVALID_SIGNATURE, + ); } if (!isCurrencyType(invoice.receivingCurrency.code)) { - return { - httpStatus: 500, - data: `Invalid currency code ${invoice.receivingCurrency.code}`, - }; + throw new uma.UmaError( + `Invalid currency code ${invoice.receivingCurrency.code}`, + uma.ErrorCode.INVALID_CURRENCY, + ); } const isAmountInMsats = invoice.receivingCurrency.code == SATS_CURRENCY.code; @@ -967,7 +1088,10 @@ export default class SendingVasp { const currencyInMsats = invoice.amount * currency.multiplier; if (invoice.expiration < Date.now()) { - return { httpStatus: 500, data: "Unable to process expired invoice" }; + throw new uma.UmaError( + "Unable to process expired invoice", + uma.ErrorCode.INVOICE_EXPIRED, + ); } return this.handleUmaPayReqInternal({ @@ -982,7 +1106,7 @@ export default class SendingVasp { invoice.senderUma ?? this.formatUma(user.umaUserName, this.getSendingVaspDomain(requestUrl)), requestUrl: requestUrl, - senderProfile: payerPofile, + senderProfile: payerProfile, pubKeys: pubKeys, umaVersion: invoice.umaVersions, invoiceUUID: invoice.invoiceUUID, @@ -1001,20 +1125,20 @@ export default class SendingVasp { let pubKeys = await this.fetchPubKeys(receivingVaspDomain); if (!pubKeys) { - return { - httpStatus: 500, - data: "Error, unable to get receiving Vasp public signing key", - }; + throw new uma.UmaError( + "Error, unable to get receiving Vasp public signing key", + uma.ErrorCode.COUNTERPARTY_PUBKEY_FETCH_ERROR, + ); } const verified = uma.verifyUmaInvoiceSignature( invoice, pubKeys.getSigningPubKey(), ); if (!verified) { - return { - httpStatus: 500, - data: "Error, unable to verify uma invoice", - }; + throw new uma.UmaError( + "Error, unable to verify uma invoice", + uma.ErrorCode.INVALID_SIGNATURE, + ); } // save request this.requestCache.savePayReqData( @@ -1085,7 +1209,10 @@ export default class SendingVasp { OutgoingPayment.getOutgoingPaymentQuery(paymentResult.id), ); if (!payment) { - throw new Error("Payment not found."); + throw new uma.UmaError( + "Payment not found.", + uma.ErrorCode.INTERNAL_ERROR, + ); } if (payment.status !== TransactionStatus.PENDING) { @@ -1094,7 +1221,10 @@ export default class SendingVasp { const maxRetries = 40; if (retryNum >= maxRetries) { - throw new Error("Payment timed out."); + throw new uma.UmaError( + "Payment timed out.", + uma.ErrorCode.INTERNAL_ERROR, + ); } await new Promise((resolve) => setTimeout(resolve, 250)); @@ -1147,7 +1277,7 @@ export default class SendingVasp { getLightsparkNodeQuery(this.config.nodeID), ); if (!node) { - throw new Error("Node not found."); + throw new uma.UmaError("Node not found.", uma.ErrorCode.INTERNAL_ERROR); } if (node.typename.includes("OSK")) { @@ -1155,9 +1285,10 @@ export default class SendingVasp { !this.config.oskSigningKeyPassword || this.config.oskSigningKeyPassword === "" ) { - throw new Error( + throw new uma.UmaError( "Node is an OSK, but no signing key password was provided in the config. " + "Set the LIGHTSPARK_UMA_OSK_NODE_SIGNING_KEY_PASSWORD environment variable", + uma.ErrorCode.INTERNAL_ERROR, ); } return await this.lightsparkClient.loadNodeSigningKey( @@ -1171,9 +1302,10 @@ export default class SendingVasp { // Assume remote signing node. const remoteSigningMasterSeed = this.config.remoteSigningMasterSeed(); if (!remoteSigningMasterSeed) { - throw new Error( + throw new uma.UmaError( "Node is a remote signing node, but no master seed was provided in the config. " + "Set the LIGHTSPARK_UMA_REMOTE_SIGNING_NODE_MASTER_SEED environment variable", + uma.ErrorCode.INTERNAL_ERROR, ); } return await this.lightsparkClient.loadNodeSigningKey(this.config.nodeID, { diff --git a/apps/examples/uma-vasp/src/server.ts b/apps/examples/uma-vasp/src/server.ts index c02231227..3d84a306c 100644 --- a/apps/examples/uma-vasp/src/server.ts +++ b/apps/examples/uma-vasp/src/server.ts @@ -1,15 +1,18 @@ import { LightsparkClient } from "@lightsparkdev/lightspark-sdk"; import { + ErrorCode, fetchPublicKeyForVasp, getPubKeyResponse, InMemoryPublicKeyCache, NonceValidator, parsePostTransactionCallback, PubKeyResponse, + UmaError, verifyPostTransactionCallbackSignature, } from "@uma-sdk/core"; import bodyParser from "body-parser"; import express from "express"; +import asyncHandler from "express-async-handler"; import ComplianceService from "./ComplianceService.js"; import InternalLedgerService from "./InternalLedgerService.js"; import ReceivingVasp from "./ReceivingVasp.js"; @@ -18,7 +21,10 @@ import SendingVaspRequestCache from "./SendingVaspRequestCache.js"; import UmaConfig from "./UmaConfig.js"; import UserService from "./UserService.js"; import { errorMessage } from "./errors.js"; -import { fullUrlForRequest } from "./networking/expressAdapters.js"; +import { + fullUrlForRequest, + sendResponse, +} from "./networking/expressAdapters.js"; export const createUmaServer = ( config: UmaConfig, @@ -62,7 +68,7 @@ export const createUmaServer = ( ); receivingVasp.registerRoutes(app); - app.get("/.well-known/lnurlpubkey", (req, res) => { + app.get("/.well-known/lnurlpubkey", (_req, res) => { res.send( getPubKeyResponse({ signingCertChainPem: config.umaSigningCertChain, @@ -81,54 +87,64 @@ export const createUmaServer = ( }); }); - app.post("/api/uma/utxoCallback", async (req, res) => { - const postTransactionCallback = parsePostTransactionCallback(req.body); + app.post( + "/api/uma/utxoCallback", + asyncHandler(async (req, resp) => { + const postTransactionCallback = parsePostTransactionCallback(req.body); - let pubKeys: PubKeyResponse; - try { - pubKeys = await fetchPublicKeyForVasp({ - cache: pubKeyCache, - vaspDomain: postTransactionCallback.vaspDomain, - }); - } catch (e) { - console.error(e); - return { - httpStatus: 500, - data: new Error("Failed to fetch public key.", { cause: e }), - }; - } + let pubKeys: PubKeyResponse; + try { + pubKeys = await fetchPublicKeyForVasp({ + cache: pubKeyCache, + vaspDomain: postTransactionCallback.vaspDomain, + }); + } catch (e) { + console.error(e); + if (e instanceof UmaError) { + throw e; + } + throw new UmaError( + "Failed to fetch public key.", + ErrorCode.COUNTERPARTY_PUBKEY_FETCH_ERROR, + ); + } - console.log(`Fetched pubkeys: ${JSON.stringify(pubKeys, null, 2)}`); + console.log(`Fetched pubkeys: ${JSON.stringify(pubKeys, null, 2)}`); - try { - const isSignatureValid = await verifyPostTransactionCallbackSignature( - postTransactionCallback, - pubKeys, - nonceCache, - ); - if (!isSignatureValid) { - return { - httpStatus: 400, - data: "Invalid post transaction callback signature.", - }; + try { + const isSignatureValid = await verifyPostTransactionCallbackSignature( + postTransactionCallback, + pubKeys, + nonceCache, + ); + if (!isSignatureValid) { + throw new UmaError( + "Invalid post transaction callback signature.", + ErrorCode.INVALID_SIGNATURE, + ); + } + } catch (e) { + console.error(e); + if (e instanceof UmaError) { + throw e; + } + throw new UmaError( + "Invalid post transaction callback signature.", + ErrorCode.INVALID_SIGNATURE, + ); } - } catch (e) { - console.error(e); - return { - httpStatus: 500, - data: new Error("Invalid post transaction callback signature.", { - cause: e, - }), - }; - } - console.log(`Received UTXO callback for ${req.query.txid}`); - console.log(` ${req.body}`); - res.send("ok"); - }); + console.log(`Received UTXO callback for ${req.query.txid}`); + console.log(` ${req.body}`); + sendResponse(resp, { + httpStatus: 200, + data: "ok", + }); + }), + ); // Default 404 handler. - app.use(function (req, res, next) { + app.use(function (_req, res) { res.status(404); res.send(errorMessage("Not found.")); }); @@ -139,12 +155,16 @@ export const createUmaServer = ( return next(err); } - if (err.message === "User not found.") { - res.status(404).send(errorMessage(err.message)); + if (err instanceof UmaError) { + res.status(err.httpStatusCode).json(JSON.parse(err.toJSON())); return; } - res.status(500).send(errorMessage(`Something broke! ${err.message}`)); + const error = new UmaError( + `Something broke! ${err.message}`, + ErrorCode.INTERNAL_ERROR, + ); + res.status(error.httpStatusCode).json(JSON.parse(error.toJSON())); }); return app; diff --git a/packages/ui/package.json b/packages/ui/package.json index 0b32a979d..4f36ff758 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -91,7 +91,7 @@ "@simbathesailor/use-what-changed": "^2.0.0", "@svgr/core": "^8.1.0", "@tanstack/react-table": "^8.20.5", - "@uma-sdk/core": "^1.2.3", + "@uma-sdk/core": "^1.3.0", "@wojtekmaj/react-daterange-picker": "^5.5.0", "@wojtekmaj/react-datetimerange-picker": "^5.5.0", "@zxing/browser": "^0.1.1", From 2f8277241d70749d5cfd84159d81187e417c5626 Mon Sep 17 00:00:00 2001 From: Lightspark Eng Date: Mon, 24 Mar 2025 23:06:46 +0000 Subject: [PATCH 09/26] CI update lock file for PR --- yarn.lock | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1a18f4adc..38766f08c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2444,7 +2444,7 @@ __metadata: "@testing-library/user-event": "npm:^14.4.3" "@types/jest": "npm:^29.5.3" "@types/prismjs": "npm:^1.26.0" - "@uma-sdk/core": "npm:^1.2.3" + "@uma-sdk/core": "npm:^1.3.0" "@wojtekmaj/react-daterange-picker": "npm:^5.5.0" "@wojtekmaj/react-datetimerange-picker": "npm:^5.5.0" "@zxing/browser": "npm:^0.1.1" @@ -2495,7 +2495,7 @@ __metadata: "@lightsparkdev/tsconfig": "npm:0.0.1" "@types/chalk": "npm:^2.2.0" "@types/node": "npm:^20.2.5" - "@uma-sdk/core": "npm:^1.2.3" + "@uma-sdk/core": "npm:^1.3.0" chalk: "npm:^5.3.0" commander: "npm:^11.0.0" eslint: "npm:^8.3.0" @@ -2522,8 +2522,9 @@ __metadata: "@types/express": "npm:^4.17.21" "@types/supertest": "npm:^2.0.14" "@types/uuid": "npm:^9.0.7" - "@uma-sdk/core": "npm:^1.2.3" + "@uma-sdk/core": "npm:^1.3.0" express: "npm:^4.18.2" + express-async-handler: "npm:^1.2.0" jest: "npm:^29.6.2" nodemon: "npm:^2.0.22" prettier: "npm:3.0.3" @@ -4609,14 +4610,14 @@ __metadata: languageName: node linkType: hard -"@uma-sdk/core@npm:^1.2.3": - version: 1.2.3 - resolution: "@uma-sdk/core@npm:1.2.3" +"@uma-sdk/core@npm:^1.3.0": + version: 1.3.0 + resolution: "@uma-sdk/core@npm:1.3.0" dependencies: eciesjs: "npm:^0.4.4" secp256k1: "npm:^5.0.0" zod: "npm:^3.22.2" - checksum: 10/593630b7394336a214c82864709ecc3ba5071b61dfd66be582ffee0c8b322fe6bbae3b80b59130ca0fc8087e6e257d2e49cf962c95893dde40c8d830681dfdae + checksum: 10/457bf388cf1129c70fd066ca1c5b72513015e4383f7e838ba6c60c37765e18c61cedf55b30f51cda89da6aa82dfedc327f4dea46bc47d3bd5fbc5af752656b44 languageName: node linkType: hard @@ -8443,6 +8444,13 @@ __metadata: languageName: node linkType: hard +"express-async-handler@npm:^1.2.0": + version: 1.2.0 + resolution: "express-async-handler@npm:1.2.0" + checksum: 10/214a7f383db1946d0c17fa2731164082c251ce2ad173d29cb048eeaa20f27a3d36e27c9889e515780b583168ab4ceb43285260562d4abb32d8f68f8543cf6735 + languageName: node + linkType: hard + "express@npm:^4.18.2": version: 4.18.2 resolution: "express@npm:4.18.2" From 2c0042f0656c990d1c0851b22af0e2f3e5180545 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Wed, 26 Mar 2025 10:42:35 -0700 Subject: [PATCH 10/26] Update account creation page for uma-bridge (#16235) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Note**: This change is purely cosmetic since first name and last name weren't being used to create the bridge user credentials anyway. Contributes to PX-122 ![Screenshot 2025-03-25 at 12.44.12 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/EJphitdgYFvuACNazm1q/44f5cacc-35c7-484b-a1dd-d74982176252.png) ![Screenshot 2025-03-25 at 12.42.42 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/EJphitdgYFvuACNazm1q/9f9bfdc2-8737-4919-90cf-47b1b19de414.png) ![Screenshot 2025-03-25 at 12.42.35 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/EJphitdgYFvuACNazm1q/e8b4930b-1cd5-4e64-9f69-ace79e648b12.png) GitOrigin-RevId: 5b46fd8e93c23e233ba0766ce98c2ec786800974 --- packages/ui/src/components/Drawer.tsx | 4 ++-- packages/ui/src/components/Modal.tsx | 13 ++++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/ui/src/components/Drawer.tsx b/packages/ui/src/components/Drawer.tsx index dacc579c8..5bc8db93d 100644 --- a/packages/ui/src/components/Drawer.tsx +++ b/packages/ui/src/components/Drawer.tsx @@ -9,7 +9,7 @@ import { Button } from "./Button.js"; import { Icon } from "./Icon/Icon.js"; import { UnstyledButton } from "./UnstyledButton.js"; -type DrawerKind = "default" | "floating"; +export type DrawerKind = "default" | "floating"; interface Props { children?: React.ReactNode; @@ -18,7 +18,7 @@ interface Props { closeButton?: boolean; nonDismissable?: boolean; handleBack?: (() => void) | undefined; - padding?: string; + padding?: string | undefined; kind?: DrawerKind | undefined; } diff --git a/packages/ui/src/components/Modal.tsx b/packages/ui/src/components/Modal.tsx index c4263df32..687ecd45d 100644 --- a/packages/ui/src/components/Modal.tsx +++ b/packages/ui/src/components/Modal.tsx @@ -31,13 +31,12 @@ import { type ToReactNodesArgs, } from "../utils/toReactNodes/toReactNodes.js"; import { Button, ButtonSelector } from "./Button.js"; -import { Drawer } from "./Drawer.js"; +import { Drawer, type DrawerKind } from "./Drawer.js"; import { Icon } from "./Icon/Icon.js"; import { IconWithCircleBackground } from "./IconWithCircleBackground.js"; import { type LoadingKind } from "./Loading.js"; import { ProgressBar, type ProgressBarProps } from "./ProgressBar.js"; import { UnstyledButton } from "./UnstyledButton.js"; - type IconProps = ComponentProps; type ExtraAction = ComponentProps & { @@ -117,6 +116,9 @@ type ModalProps = { appendToElement?: HTMLElement; bottomContent?: ReactNode | undefined; topLeftIcon?: ComponentProps | undefined; + drawerKind?: DrawerKind; + drawerPadding?: number; + drawerCloseButton?: boolean; }; export function Modal({ @@ -152,6 +154,9 @@ export function Modal({ appendToElement, bottomContent, topLeftIcon, + drawerKind, + drawerPadding, + drawerCloseButton = true, }: ModalProps) { const visibleChangedRef = useRef(false); const [visibleChanged, setVisibleChanged] = useState(false); @@ -403,9 +408,11 @@ export function Modal({ content = ( onClickCloseButton()} - closeButton + closeButton={drawerCloseButton} nonDismissable={nonDismissable} handleBack={handleBack} + kind={drawerKind ?? "default"} + padding={drawerPadding !== undefined ? `${drawerPadding}px` : undefined} > {modalContent} From f01d328645cbb53b7b327ee28cd462f1ed2d1d57 Mon Sep 17 00:00:00 2001 From: Brian Siao Tick Chong Date: Wed, 26 Mar 2025 15:24:30 -0700 Subject: [PATCH 11/26] [bridge] update cardform border and padding, input border radius, button border radius (#16425) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These changes should make it easier to get the new screens looking closer to the designs - updates default card form border radius - updates default card form border color - removes card form shadow - updates default card form padding - removes paddingY in favor of paddingTop and paddingBottom in CardForm - adds temporary paddingTop to steps which look strange without the progress header (yet to be implemented) - updates default button radiuses to be fully round - adjusts text input paddings - adds padding to title and descriptions of cardforms - adjusts button paddings to account for border - makes CardForm skinnier old: ![Screenshot 2025-03-21 at 3.48.27 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/NU8OmLauzLqa61yWDJkY/41134f04-6937-4cd0-9ed8-1fa123db962b.png) new: ![Screenshot 2025-03-26 at 2.23.31 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/NU8OmLauzLqa61yWDJkY/c958876d-7ad3-4696-9cd3-16bfe9e5b4a5.png) ![Screenshot 2025-03-26 at 2.23.37 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/NU8OmLauzLqa61yWDJkY/569bc235-cf5f-4bff-a406-ccbee14f1ace.png) ![Screenshot 2025-03-26 at 2.27.21 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/NU8OmLauzLqa61yWDJkY/d01550f6-d8ff-4700-a132-ae4bdacfbc8d.png) GitOrigin-RevId: dad07d03ce168dbf8b78ab80309578bdbb4a52f6 --- .../ui/src/components/CardForm/CardForm.tsx | 71 ++++++++++++++----- .../components/CodeInput/SingleCodeInput.tsx | 1 + packages/ui/src/components/PhoneInput.tsx | 7 ++ packages/ui/src/components/TextInput.tsx | 9 ++- packages/ui/src/styles/fields.tsx | 25 +++++-- .../ui/src/styles/themeDefaults/cardForm.tsx | 19 +++-- packages/ui/src/styles/themes.tsx | 28 +++++--- 7 files changed, 121 insertions(+), 39 deletions(-) diff --git a/packages/ui/src/components/CardForm/CardForm.tsx b/packages/ui/src/components/CardForm/CardForm.tsx index b6ebadd60..d11b8512e 100644 --- a/packages/ui/src/components/CardForm/CardForm.tsx +++ b/packages/ui/src/components/CardForm/CardForm.tsx @@ -23,8 +23,9 @@ import { type CardFormBorderRadius, type CardFormBorderWidth, type CardFormKind, + type CardFormPaddingBottom, + type CardFormPaddingTop, type CardFormPaddingX, - type CardFormPaddingY, type CardFormShadow, type CardFormTextAlign, type CardFormThemeKey, @@ -69,6 +70,8 @@ type CardFormProps = { kind?: CardFormKind; textAlign?: CardFormTextAlign; shadow?: CardFormShadow; + paddingTop?: CardFormPaddingTop | undefined; + paddingBottom?: CardFormPaddingBottom | undefined; belowFormContent?: ToReactNodesArgs | undefined; belowFormContentGap?: BelowCardFormContentGap | undefined; }; @@ -77,10 +80,23 @@ type ResolvePropsArgs = { kind: CardFormKind; shadow?: CardFormShadow | undefined; textAlign?: CardFormTextAlign | undefined; + paddingTop?: CardFormPaddingTop | undefined; + paddingBottom?: CardFormPaddingBottom | undefined; }; function resolveProps(args: ResolvePropsArgs, theme: Theme) { - const paddingY = resolveCardFormProp(undefined, args.kind, "paddingY", theme); + const paddingTop = resolveCardFormProp( + args.paddingTop, + args.kind, + "paddingTop", + theme, + ); + const paddingBottom = resolveCardFormProp( + args.paddingBottom, + args.kind, + "paddingBottom", + theme, + ); const paddingX = resolveCardFormProp(undefined, args.kind, "paddingX", theme); const textAlign = resolveCardFormProp( args.textAlign, @@ -133,7 +149,8 @@ function resolveProps(args: ResolvePropsArgs, theme: Theme) { ); const props = { - paddingY, + paddingTop, + paddingBottom, paddingX, shadow, borderRadius, @@ -164,12 +181,15 @@ export function CardForm({ kind = "primary", shadow: shadowProp, textAlign: textAlignProp, + paddingTop: paddingTopProp, + paddingBottom: paddingBottomProp, belowFormContent, belowFormContentGap = 0, }: CardFormProps) { const theme = useTheme(); const { - paddingY, + paddingTop, + paddingBottom, paddingX, shadow, borderRadius, @@ -181,7 +201,13 @@ export function CardForm({ smBorderWidth, defaultDescriptionTypographyMap, } = resolveProps( - { kind, textAlign: textAlignProp, shadow: shadowProp }, + { + kind, + textAlign: textAlignProp, + shadow: shadowProp, + paddingTop: paddingTopProp, + paddingBottom: paddingBottomProp, + }, theme, ); @@ -240,7 +266,8 @@ export function CardForm({ borderColor, textAlign, paddingX, - paddingY, + paddingTop, + paddingBottom, backgroundColor, smBackgroundColor, smBorderWidth, @@ -287,7 +314,9 @@ const BelowCardFormContent = styled.div` margin-bottom: ${Spacing.px.xl}; `; -const CardFormSubtitle = styled.div``; +const CardFormSubtitle = styled.div` + padding: 0 ${Spacing.px.xs}; +`; export const CardFormButtonRow = styled.div<{ justify?: "left" | "center" | undefined; @@ -361,20 +390,26 @@ const StyledCardFormCheckboxParagraph = styled.div` type CardFormInsetProps = { wide: boolean; paddingX: number; - paddingY: number; + paddingTop: number; + paddingBottom: number; }; -const formInset = ({ wide, paddingX, paddingY }: CardFormInsetProps) => css` +const formInset = ({ + wide, + paddingX, + paddingTop, + paddingBottom, +}: CardFormInsetProps) => css` margin-left: auto; margin-right: auto; max-width: 100%; padding-right: ${paddingX}px; padding-left: ${paddingX}px; - padding-top: ${paddingY}px; - padding-bottom: ${paddingY}px; + padding-top: ${paddingTop}px; + padding-bottom: ${paddingBottom}px; ${bp.minSm(` - width: ${wide ? 700 : 505}px; + width: ${wide ? 700 : 408}px; `)} ${bp.sm(` @@ -397,7 +432,7 @@ const formInset = ({ wide, paddingX, paddingY }: CardFormInsetProps) => css` & ${CardFormFullTopContent} { ${bp.minSm(` - margin-top: -${paddingY}px; + margin-top: -${paddingTop}px; `)} } `; @@ -410,7 +445,8 @@ type StyledCardFormStyleProps = { borderColor: CardFormBorderColor; textAlign: CardFormTextAlign; paddingX: CardFormPaddingX; - paddingY: CardFormPaddingY; + paddingTop: CardFormPaddingTop; + paddingBottom: CardFormPaddingBottom; backgroundColor: CardFormBackgroundColor; smBackgroundColor: CardFormBackgroundColor; smBorderWidth: CardFormBorderWidth; @@ -425,13 +461,14 @@ const StyledCardFormStyle = ({ borderColor, textAlign, paddingX, - paddingY, + paddingTop, + paddingBottom, backgroundColor, smBackgroundColor, smBorderWidth, }: StyledCardFormStyleProps & { theme: Theme }) => { return css` - ${formInset({ wide, paddingX, paddingY })} + ${formInset({ wide, paddingX, paddingTop, paddingBottom })} ${shadow === "soft" ? standardCardShadow : shadow === "hard" @@ -518,6 +555,8 @@ const StyledCardFormDiv = styled.div(StyledCardFormStyle); const CardHeadline = styled.div<{ hasTopContent: boolean }>` + padding: 0 ${Spacing.px.xs}; + ${({ hasTopContent }) => (hasTopContent ? "margin-top: 32px;" : "")} & + *:not(${CardFormSubtitle}) { diff --git a/packages/ui/src/components/CodeInput/SingleCodeInput.tsx b/packages/ui/src/components/CodeInput/SingleCodeInput.tsx index e82cd45b1..5849a9bd8 100644 --- a/packages/ui/src/components/CodeInput/SingleCodeInput.tsx +++ b/packages/ui/src/components/CodeInput/SingleCodeInput.tsx @@ -64,6 +64,7 @@ export function SingleCodeInput({ theme, disabled: Boolean(disabled), hasError: false, + borderRadius: 8, }).styles } width: ${width}; diff --git a/packages/ui/src/components/PhoneInput.tsx b/packages/ui/src/components/PhoneInput.tsx index 57aa81280..41b110d66 100644 --- a/packages/ui/src/components/PhoneInput.tsx +++ b/packages/ui/src/components/PhoneInput.tsx @@ -10,6 +10,7 @@ import { type ComponentProps, type FocusEvent, } from "react"; +import { type TextInputBorderRadius } from "../styles/fields.js"; import { countryCodesToNames } from "../utils/countryCodesToNames.js"; import { TextInput } from "./TextInput.js"; import { type PartialSimpleTypographyProps } from "./typography/types.js"; @@ -42,6 +43,7 @@ export type PhoneInputOnChangeArg = { type PhoneInputProps = { pxPerChar?: number; + borderRadius?: TextInputBorderRadius | undefined; onChange?: ({ number, countryCallingCode, @@ -62,6 +64,7 @@ export function PhoneInput({ error: errorProp, pxPerChar = 6, defaultCountryCode = "US", + borderRadius = 8 as TextInputBorderRadius, }: PhoneInputProps) { /* countryCode only controls the country used for parsing phone number input. User may still enter or paste the country phone code */ @@ -132,6 +135,7 @@ export function PhoneInput({ value: countryCode, onChange: onChangeSelect, width, + height: 54, }} pattern="[0-9,]*" inputMode="numeric" @@ -145,6 +149,9 @@ export function PhoneInput({ onChange={onChange} error={errorProp || error} typography={typography} + borderRadius={borderRadius} + borderWidth={0.5} + paddingY={13.5} /> ); } diff --git a/packages/ui/src/components/TextInput.tsx b/packages/ui/src/components/TextInput.tsx index 9e456404b..3e8a0807b 100644 --- a/packages/ui/src/components/TextInput.tsx +++ b/packages/ui/src/components/TextInput.tsx @@ -102,9 +102,12 @@ export type TextInputProps = { onChange: (value: string) => void; /* A specified width is required to ensure left input padding is correct */ width: number; + /* A specified height is required to ensure the select is the same height as the input */ + height: number; } | undefined; borderRadius?: TextInputBorderRadius | undefined; + borderWidth?: number | undefined; width?: "full" | "short" | undefined; paddingX?: number; paddingY?: number; @@ -246,6 +249,7 @@ export function TextInput(textInputProps: TextInputProps) { } }} borderRadius={props.borderRadius} + borderWidth={props.borderWidth} enterKeyHint={props.enterKeyHint} autoFocus={props.autoFocus} /> @@ -318,6 +322,7 @@ export function TextInput(textInputProps: TextInputProps) { { select.onChange(event.target.value); @@ -346,6 +351,7 @@ export function TextInput(textInputProps: TextInputProps) { const TextInputSelect = styled.select<{ widthProp: number; + heightProp: number; typography: RequiredSimpleTypographyProps; }>` ${({ typography, theme }) => @@ -356,7 +362,7 @@ const TextInputSelect = styled.select<{ background-color: transparent; top: 0; left: ${selectLeftOffset}px; - height: 48px; + height: ${({ heightProp }) => `${heightProp}px`}; width: ${({ widthProp }) => `${widthProp}px`}; `; @@ -411,6 +417,7 @@ interface InputProps { activeOutlineColor?: ThemeOrColorKey | undefined; typography: RequiredSimpleTypographyProps; borderRadius?: TextInputBorderRadius | undefined; + borderWidth?: number | undefined; } const Input = styled.input` diff --git a/packages/ui/src/styles/fields.tsx b/packages/ui/src/styles/fields.tsx index 3e5763c18..df35ea040 100644 --- a/packages/ui/src/styles/fields.tsx +++ b/packages/ui/src/styles/fields.tsx @@ -52,7 +52,6 @@ export const inputBlockStyle = ({ export const textInputPlaceholderColor = ({ theme }: ThemeProp) => theme.c4Neutral; export const textInputFontWeight = 600; -export const textInputBorderRadiusPx = 8; export const textInputPaddingPx = 12; export const textInputPadding = `${textInputPaddingPx}px`; @@ -65,11 +64,15 @@ const textInputActiveStyles = ({ theme, paddingLeftPx, paddingRightPx, + paddingTopPx, + paddingBottomPx, activeOutline, activeOutlineColor, }: WithTheme<{ paddingLeftPx?: number | undefined; paddingRightPx?: number | undefined; + paddingTopPx?: number | undefined; + paddingBottomPx?: number | undefined; activeOutline?: boolean | undefined; activeOutlineColor?: ThemeOrColorKey | undefined; }>) => { @@ -90,6 +93,8 @@ const textInputActiveStyles = ({ padding: ${textInputPaddingPx - 1}px; ${paddingLeftPx ? `padding-left: ${paddingLeftPx - 1}px;` : ""} ${paddingRightPx ? `padding-right: ${paddingRightPx - 1}px;` : ""} + ${paddingTopPx ? `padding-top: ${paddingTopPx - 1}px;` : ""} + ${paddingBottomPx ? `padding-bottom: ${paddingBottomPx - 1}px;` : ""} `; }; @@ -99,7 +104,7 @@ export const defaultTextInputTypography = { color: "text", } as const; -export type TextInputBorderRadius = "round" | "default"; +export type TextInputBorderRadius = 8 | 16 | 999; export const textInputStyle = ({ theme, @@ -114,6 +119,7 @@ export const textInputStyle = ({ activeOutlineColor, typography, borderRadius, + borderWidth, }: WithTheme<{ // In some cases we want to show an active state when another element is focused. active?: boolean | undefined; @@ -127,10 +133,9 @@ export const textInputStyle = ({ activeOutlineColor?: ThemeOrColorKey | undefined; typography?: PartialSimpleTypographyProps | undefined; borderRadius?: TextInputBorderRadius | undefined; + borderWidth?: number | undefined; }>) => css` - border-radius: ${borderRadius === "round" - ? "999" - : textInputBorderRadiusPx}px; + border-radius: ${borderRadius}px; background-color: ${disabled ? theme.vlcNeutral : theme.inputBackground}; cursor: ${disabled ? "not-allowed" : "auto"}; box-sizing: border-box; @@ -151,7 +156,11 @@ export const textInputStyle = ({ ? `padding-bottom: ${paddingBottomPx - (hasError ? 1 : 0)}px;` : ""} border-style: solid; - border-width: ${hasError ? "2" : "1"}px; + border-width: ${hasError + ? "2" + : borderWidth !== undefined + ? borderWidth + : "1"}px; border-color: ${hasError ? theme.danger : textInputBorderColor({ theme })}; line-height: 22px; outline: none; @@ -173,6 +182,8 @@ export const textInputStyle = ({ theme, paddingLeftPx, paddingRightPx, + paddingTopPx, + paddingBottomPx, activeOutline, activeOutlineColor, })} @@ -183,6 +194,8 @@ export const textInputStyle = ({ theme, paddingLeftPx, paddingRightPx, + paddingTopPx, + paddingBottomPx, activeOutline, activeOutlineColor, })} diff --git a/packages/ui/src/styles/themeDefaults/cardForm.tsx b/packages/ui/src/styles/themeDefaults/cardForm.tsx index d6c9c7fa6..bb4092694 100644 --- a/packages/ui/src/styles/themeDefaults/cardForm.tsx +++ b/packages/ui/src/styles/themeDefaults/cardForm.tsx @@ -2,17 +2,20 @@ import type { ThemeOrColorKey } from "../themes.js"; import type { TokenSizeKey, TypographyTypeKey } from "../tokens/typography.js"; export type CardFormPaddingY = 40 | 56; -export type CardFormPaddingX = 40 | 56; +export type CardFormPaddingX = 32 | 40 | 56; +export type CardFormPaddingTop = 24 | 32 | 40 | 56; +export type CardFormPaddingBottom = 32 | 40 | 56 | 64; export type CardFormBorderWidth = 0 | 1; -export type CardFormBorderRadius = 8 | 24; +export type CardFormBorderRadius = 8 | 24 | 32; export type CardFormShadow = "soft" | "hard" | "none"; export type CardFormTextAlign = "center" | "left"; export type CardFormBorderColor = "vlcNeutral" | "grayBlue94"; export type CardFormBackgroundColor = "bg" | "white"; const cardFormThemeBaseProps = { - paddingY: 56 as CardFormPaddingY, paddingX: 56 as CardFormPaddingX, + paddingTop: 56 as CardFormPaddingTop, + paddingBottom: 56 as CardFormPaddingBottom, shadow: "soft" as CardFormShadow, borderRadius: 24 as CardFormBorderRadius, borderWidth: 0 as CardFormBorderWidth, @@ -57,13 +60,19 @@ export const defaultCardFormTheme = { kinds: { primary: {}, secondary: { - paddingY: 40, + paddingTop: 40, + paddingBottom: 40, paddingX: 40, shadow: "none", borderRadius: 8, borderWidth: 1, borderColor: "vlcNeutral", }, - tertiary: { paddingY: 56, paddingX: 40, shadow: "hard" }, + tertiary: { + paddingTop: 56, + paddingBottom: 56, + paddingX: 40, + shadow: "hard", + }, } as Partial, }; diff --git a/packages/ui/src/styles/themes.tsx b/packages/ui/src/styles/themes.tsx index 68388a5c1..f555c2b15 100644 --- a/packages/ui/src/styles/themes.tsx +++ b/packages/ui/src/styles/themes.tsx @@ -532,14 +532,14 @@ const bridgeBaseSettings = { buttons: merge(buttonsThemeBase, { defaultTypographyType: "Title", defaultSize: "Medium", - defaultBorderRadius: 8, + defaultBorderRadius: 999, defaultPaddingsY: { - ExtraSmall: 14, - Small: 14, - Schmedium: 14, - Medium: 14, - Mlarge: 14, - Large: 14, + ExtraSmall: 13, + Small: 13, + Schmedium: 13, + Medium: 13, + Mlarge: 13, + Large: 13, }, kinds: { primary: { @@ -624,6 +624,7 @@ const bridgeBaseSettings = { cardForm: merge(cardFormThemeBase, { backgroundColor: "white", smBackgroundColor: "bg", + borderRadius: 32, defaultDescriptionTypographyMap: { default: { type: "Body", @@ -638,16 +639,21 @@ const bridgeBaseSettings = { }, kinds: { primary: { - paddingY: 56, + borderWidth: 1, + smBorderWidth: 0, + borderColor: "grayBlue94", + paddingTop: 24, + paddingBottom: 64, paddingX: 40, - shadow: "hard", + shadow: "none", }, secondary: { borderWidth: 1, smBorderWidth: 0, borderColor: "grayBlue94", - paddingY: 40, - paddingX: 40, + paddingTop: 32, + paddingBottom: 64, + paddingX: 32, shadow: "none", }, }, From 5e478ffe6cd5973cbe0f69bed04f8fead25a6d48 Mon Sep 17 00:00:00 2001 From: Brian Siao Tick Chong Date: Wed, 26 Mar 2025 16:42:55 -0700 Subject: [PATCH 12/26] [bridge] adjust banner styles, add default TextInput border radius (#16503) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - adds option to not have the Banner position: fixed - wraps BridgeCardForm's banner in position: fixed to accommodate adding another banner on top - shrinks size of uma icon if small screen - changes how minHeight works in Banner - adjusts banner styles - fixes border that should appear as you scroll underneath the banner - fixes TextInput, adds default border radius back - updates hamburger icon in mobile nav ![Screenshot 2025-03-26 at 3.56.32 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/NU8OmLauzLqa61yWDJkY/7fb2a55a-f31f-46be-a13a-a1a8a410d728.png) ![Screenshot 2025-03-26 at 3.57.00 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/NU8OmLauzLqa61yWDJkY/3120a904-3fca-4f8f-919d-a00201018356.png) GitOrigin-RevId: 8b7cfc046c38f0babc96ff0838dcc645f1cce296 --- packages/ui/src/components/Banner/Banner.tsx | 21 +++++++++++++------- packages/ui/src/styles/fields.tsx | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/Banner/Banner.tsx b/packages/ui/src/components/Banner/Banner.tsx index 8b8b8129e..3768c0f54 100644 --- a/packages/ui/src/components/Banner/Banner.tsx +++ b/packages/ui/src/components/Banner/Banner.tsx @@ -29,6 +29,8 @@ export type BannerProps = { onHeightChange?: (height: number) => void; minHeight?: number | "auto" | undefined; hPadding?: number | undefined; + vPadding?: number | undefined; + fixed?: boolean | undefined; right?: ReactNode | undefined; left?: ReactNode | undefined; blurScroll?: boolean | undefined; @@ -43,10 +45,12 @@ export function Banner({ onHeightChange, right, left, + fixed, bgProgressDuration, borderProgress, minHeight = 0, hPadding = 0, + vPadding = 0, borderColor, blurScroll = false, }: BannerProps) { @@ -90,7 +94,6 @@ export function Banner({ {contentNode} @@ -106,6 +109,9 @@ export function Banner({ ref={ref} borderProgress={borderProgress} hPadding={hPadding} + vPadding={vPadding} + minHeight={minHeight} + fixed={fixed} blurScroll={blurScroll} > {left} @@ -125,7 +131,6 @@ export function Banner({ const BannerInnerContent = styled.div<{ isVisible: boolean; maxMdContentJustify: MaxMdContentJustify; - minHeight: number | "auto"; }>` z-index: 1; padding: ${({ isVisible }) => (isVisible ? `0px 17.5px` : "0px")}; @@ -134,8 +139,6 @@ const BannerInnerContent = styled.div<{ flex-direction: column; align-items: center; justify-content: center; - min-height: ${({ minHeight }) => - typeof minHeight === "number" ? `${minHeight}px` : "auto"}; & > * { position: relative; @@ -156,11 +159,13 @@ const StyledBanner = styled.div<{ hasSideContent: boolean; borderProgress: number | undefined; hPadding: number; + vPadding: number; + minHeight: number | "auto"; + fixed: boolean | undefined; borderColor: ThemeOrColorKey | undefined; blurScroll: boolean | undefined; }>` - position: fixed; - left: 0; + ${({ fixed }) => (fixed ? "position: fixed; left: 0;" : "")}; width: 100%; z-index: ${z.notificationBanner}; @@ -179,7 +184,9 @@ const StyledBanner = styled.div<{ justify-content: ${({ hasSideContent }) => hasSideContent ? "space-between" : "center"}; align-items: center; - padding: ${({ hPadding }) => `0 ${hPadding}px`}; + padding: ${({ hPadding, vPadding }) => `${vPadding}px ${hPadding}px`}; + min-height: ${({ minHeight }) => + typeof minHeight === "number" ? `${minHeight}px` : "auto"}; ${({ borderColor, theme }) => borderColor ? `border-bottom: 1px solid ${getColor(theme, borderColor)};` diff --git a/packages/ui/src/styles/fields.tsx b/packages/ui/src/styles/fields.tsx index df35ea040..9d37215c2 100644 --- a/packages/ui/src/styles/fields.tsx +++ b/packages/ui/src/styles/fields.tsx @@ -135,7 +135,7 @@ export const textInputStyle = ({ borderRadius?: TextInputBorderRadius | undefined; borderWidth?: number | undefined; }>) => css` - border-radius: ${borderRadius}px; + border-radius: ${borderRadius ?? 8}px; background-color: ${disabled ? theme.vlcNeutral : theme.inputBackground}; cursor: ${disabled ? "not-allowed" : "auto"}; box-sizing: border-box; From 3f024cd5f40a072271c17bb98d58ceed052480a8 Mon Sep 17 00:00:00 2001 From: Brian Siao Tick Chong Date: Wed, 26 Mar 2025 17:01:11 -0700 Subject: [PATCH 13/26] [bridge] fix typography letter-spacing (#16504) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ![Screenshot 2025-03-26 at 4.44.09 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/NU8OmLauzLqa61yWDJkY/f89635ad-41c0-46b0-b20f-8435e31cb012.png) - fixes tight letter spacing for typography tokens (affects Display, Headline, Title) GitOrigin-RevId: 0c72959ba7c1b2cfe24fc78da73bdac6bdd7254a --- packages/ui/src/styles/tokens/typography.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/styles/tokens/typography.ts b/packages/ui/src/styles/tokens/typography.ts index 58e7897b8..7965a8a3f 100644 --- a/packages/ui/src/styles/tokens/typography.ts +++ b/packages/ui/src/styles/tokens/typography.ts @@ -211,8 +211,8 @@ const LETTER_SPACING = { loose: ".1em", }, [TypographyGroup.Bridge]: { - tight: "-2%", - normal: "0%", + tight: "-0.48px", + normal: "0", wide: "10%", }, }; From c9c31ab7ebbbd54298baa8eb4e8f983fa90a0602 Mon Sep 17 00:00:00 2001 From: Joel Weinberger Date: Wed, 26 Mar 2025 17:31:15 -0700 Subject: [PATCH 14/26] Update next.js version (#16506) Update next.js past the version with a critical vulnerability (that doesn't actually affect us). Also update the build image to allow it to build on GH workflows. GitOrigin-RevId: bdf8ce5f60df7edf9af12f5efae0fc20f7ccb326 --- packages/ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/package.json b/packages/ui/package.json index 4f36ff758..13af4ec52 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -101,7 +101,7 @@ "libphonenumber-js": "^1.11.1", "lodash-es": "^4.17.21", "nanoid": "^4.0.0", - "next": "^13.4.19", + "next": "^13.5.10", "prismjs": "^1.29.0", "qrcode.react": "^4.0.1", "react": "^18.2.0", From aebe75ce3cbbff2173adb630728d84b580cd21e2 Mon Sep 17 00:00:00 2001 From: Lightspark Eng Date: Thu, 27 Mar 2025 00:40:56 +0000 Subject: [PATCH 15/26] CI update lock file for PR --- yarn.lock | 92 +++++++++++++++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/yarn.lock b/yarn.lock index 38766f08c..a778e1db7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2463,7 +2463,7 @@ __metadata: lodash-es: "npm:^4.17.21" madge: "npm:^6.1.0" nanoid: "npm:^4.0.0" - next: "npm:^13.4.19" + next: "npm:^13.5.10" prettier: "npm:3.0.3" prettier-plugin-organize-imports: "npm:^3.2.4" prismjs: "npm:^1.29.0" @@ -2692,10 +2692,10 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:13.5.6": - version: 13.5.6 - resolution: "@next/env@npm:13.5.6" - checksum: 10/c81bd6052db366407da701e4e431becbc80ef36a88bec7883b0266cdfeb45a7da959d37c38e1a816006cd2da287e5ff5b928bdb71025e3d4aa59e07dea3edd59 +"@next/env@npm:13.5.11": + version: 13.5.11 + resolution: "@next/env@npm:13.5.11" + checksum: 10/2d34ec742e28b4da54b7bfe62eb27c1c90f927f0dfe387a0af8c1a535faf75d80475e58a33472bdf722bab02349be074935e2bdf512b5fd0a46dab364700dd3d languageName: node linkType: hard @@ -2708,65 +2708,65 @@ __metadata: languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-darwin-arm64@npm:13.5.6" +"@next/swc-darwin-arm64@npm:13.5.9": + version: 13.5.9 + resolution: "@next/swc-darwin-arm64@npm:13.5.9" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-x64@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-darwin-x64@npm:13.5.6" +"@next/swc-darwin-x64@npm:13.5.9": + version: 13.5.9 + resolution: "@next/swc-darwin-x64@npm:13.5.9" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-linux-arm64-gnu@npm:13.5.6" +"@next/swc-linux-arm64-gnu@npm:13.5.9": + version: 13.5.9 + resolution: "@next/swc-linux-arm64-gnu@npm:13.5.9" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-linux-arm64-musl@npm:13.5.6" +"@next/swc-linux-arm64-musl@npm:13.5.9": + version: 13.5.9 + resolution: "@next/swc-linux-arm64-musl@npm:13.5.9" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-linux-x64-gnu@npm:13.5.6" +"@next/swc-linux-x64-gnu@npm:13.5.9": + version: 13.5.9 + resolution: "@next/swc-linux-x64-gnu@npm:13.5.9" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-linux-x64-musl@npm:13.5.6" +"@next/swc-linux-x64-musl@npm:13.5.9": + version: 13.5.9 + resolution: "@next/swc-linux-x64-musl@npm:13.5.9" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-win32-arm64-msvc@npm:13.5.6" +"@next/swc-win32-arm64-msvc@npm:13.5.9": + version: 13.5.9 + resolution: "@next/swc-win32-arm64-msvc@npm:13.5.9" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@next/swc-win32-ia32-msvc@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-win32-ia32-msvc@npm:13.5.6" +"@next/swc-win32-ia32-msvc@npm:13.5.9": + version: 13.5.9 + resolution: "@next/swc-win32-ia32-msvc@npm:13.5.9" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:13.5.6": - version: 13.5.6 - resolution: "@next/swc-win32-x64-msvc@npm:13.5.6" +"@next/swc-win32-x64-msvc@npm:13.5.9": + version: 13.5.9 + resolution: "@next/swc-win32-x64-msvc@npm:13.5.9" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -11757,20 +11757,20 @@ __metadata: languageName: node linkType: hard -"next@npm:^13.4.19": - version: 13.5.6 - resolution: "next@npm:13.5.6" +"next@npm:^13.5.10": + version: 13.5.11 + resolution: "next@npm:13.5.11" dependencies: - "@next/env": "npm:13.5.6" - "@next/swc-darwin-arm64": "npm:13.5.6" - "@next/swc-darwin-x64": "npm:13.5.6" - "@next/swc-linux-arm64-gnu": "npm:13.5.6" - "@next/swc-linux-arm64-musl": "npm:13.5.6" - "@next/swc-linux-x64-gnu": "npm:13.5.6" - "@next/swc-linux-x64-musl": "npm:13.5.6" - "@next/swc-win32-arm64-msvc": "npm:13.5.6" - "@next/swc-win32-ia32-msvc": "npm:13.5.6" - "@next/swc-win32-x64-msvc": "npm:13.5.6" + "@next/env": "npm:13.5.11" + "@next/swc-darwin-arm64": "npm:13.5.9" + "@next/swc-darwin-x64": "npm:13.5.9" + "@next/swc-linux-arm64-gnu": "npm:13.5.9" + "@next/swc-linux-arm64-musl": "npm:13.5.9" + "@next/swc-linux-x64-gnu": "npm:13.5.9" + "@next/swc-linux-x64-musl": "npm:13.5.9" + "@next/swc-win32-arm64-msvc": "npm:13.5.9" + "@next/swc-win32-ia32-msvc": "npm:13.5.9" + "@next/swc-win32-x64-msvc": "npm:13.5.9" "@swc/helpers": "npm:0.5.2" busboy: "npm:1.6.0" caniuse-lite: "npm:^1.0.30001406" @@ -11808,7 +11808,7 @@ __metadata: optional: true bin: next: dist/bin/next - checksum: 10/ec6defc7958b575d93306a2dcb05b7b14e27474e226ec350f412d03832407a5ae7139c21f7c7437b93cca2c8a79d7197c468308d82a903b2a86d579f84ccac9f + checksum: 10/9f116a7391bb81b6c7b96b825b68be2be849ef3ac85a6b9d0939f0ca8e0b5aa1b53db2a96f6b44fdf0b3d31ea0a2b7150218ed033660b99780f820db1baba709 languageName: node linkType: hard From 55cf6736835418584afc795df85b4f86a8a24211 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Fri, 28 Mar 2025 10:21:01 -0700 Subject: [PATCH 16/26] New callout component for bridge (#16347) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ![Screenshot 2025-03-24 at 10.31.37 AM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/EJphitdgYFvuACNazm1q/fae62c8b-879b-4873-a85a-8d13990d5f6f.png) Contributes to PX-246 GitOrigin-RevId: 0ce0b70212bb8110bd577e3fcb3933d73569fc90 --- packages/ui/src/icons/central/Leading.tsx | 26 +++++++++++++++++++++++ packages/ui/src/icons/central/index.tsx | 1 + packages/ui/src/styles/colors.tsx | 4 ++++ 3 files changed, 31 insertions(+) create mode 100644 packages/ui/src/icons/central/Leading.tsx diff --git a/packages/ui/src/icons/central/Leading.tsx b/packages/ui/src/icons/central/Leading.tsx new file mode 100644 index 000000000..8cf209549 --- /dev/null +++ b/packages/ui/src/icons/central/Leading.tsx @@ -0,0 +1,26 @@ +import { type PathProps } from "../types.js"; + +export function Leading({ + strokeWidth = "1.5", + strokeLinecap = "square", + strokeLinejoin = "round", +}: PathProps) { + return ( + + + + ); +} diff --git a/packages/ui/src/icons/central/index.tsx b/packages/ui/src/icons/central/index.tsx index e1d4f7d67..a7f296ceb 100644 --- a/packages/ui/src/icons/central/index.tsx +++ b/packages/ui/src/icons/central/index.tsx @@ -35,6 +35,7 @@ export { FingerPrint1 as CentralFingerPrint1 } from "./FingerPrint1.js"; export { Globus as CentralGlobus } from "./Globus.js"; export { Group3 as CentralGroup3 } from "./Group3.js"; export { Key2 as CentralKey2 } from "./Key2.js"; +export { Leading as CentralLeading } from "./Leading.js"; export { Loader as CentralLoader } from "./Loader.js"; export { Lock as CentralLock } from "./Lock.js"; export { MagnifyingGlass as CentralMagnifyingGlass } from "./MagnifyingGlass.js"; diff --git a/packages/ui/src/styles/colors.tsx b/packages/ui/src/styles/colors.tsx index d08451db2..55ee5187d 100644 --- a/packages/ui/src/styles/colors.tsx +++ b/packages/ui/src/styles/colors.tsx @@ -19,6 +19,7 @@ const neutral = { gray91: "#171717", gray95: "#F2F2F2", gray98: "#F9F9F9", + gray99: "#EDEEF1", white: "#FFFFFF", }; @@ -40,6 +41,7 @@ const baseColors = { green33: "#179257", green35: "#19981E", // blue + blue10: "#E4EFFB", blue43: "#145BC6", blue22: "#0E2E60", blue39: "#0068C9", @@ -74,6 +76,8 @@ const baseColors = { red42a10: "#D800271A", red42a20: "#D800272D", red42a30: "#D800273F", + errorText: "#E41C1B", + errorBackground: "#FEE2E1", // yellow primary, warning: primary, From 925d2e41e7c637c1420b0f9ab4c12767976a5107 Mon Sep 17 00:00:00 2001 From: Aaryaman Bhute <35084309+AaryamanBhute@users.noreply.github.com> Date: Fri, 28 Mar 2025 14:24:57 -0700 Subject: [PATCH 17/26] Updated Verify Identity Page (#16522) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added configuration to existing components to support new design for Verify Identity Page ![Screenshot 2025-03-27 at 10.45.22 AM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/oOuIssbrOKuvWkHIBu32/934b18c7-5cb4-454f-9eef-95984f217b0c.png) ![Screenshot 2025-03-27 at 10.45.37 AM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/oOuIssbrOKuvWkHIBu32/e055320d-5aea-4f68-a570-c695bf591158.png) GitOrigin-RevId: dd2c6a3504e0169e41ba0a1733b6f4fffe129c76 --- .../ui/src/components/CardForm/CardForm.tsx | 47 +++++++++++++++---- packages/ui/src/components/Flex.tsx | 15 ++++++ .../components/IconWithCircleBackground.tsx | 25 ++++++++-- packages/ui/src/styles/colors.tsx | 1 + .../toReactNodes/setReactNodesTypography.tsx | 2 + 5 files changed, 78 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/components/CardForm/CardForm.tsx b/packages/ui/src/components/CardForm/CardForm.tsx index d11b8512e..5d2d3a2ac 100644 --- a/packages/ui/src/components/CardForm/CardForm.tsx +++ b/packages/ui/src/components/CardForm/CardForm.tsx @@ -32,6 +32,7 @@ import { } from "../../styles/themeDefaults/cardForm.js"; import { getColor } from "../../styles/themes.js"; import { Spacing } from "../../styles/tokens/spacing.js"; +import { type TokenSizeKey } from "../../styles/tokens/typography.js"; import { pxToRems } from "../../styles/utils.js"; import { type NewRoutesType } from "../../types/index.js"; import { select } from "../../utils/emotion.js"; @@ -60,10 +61,12 @@ type CardFormProps = { disabled?: boolean; topContent?: ReactNode; title?: string; + titleSize?: TokenSizeKey; titleRightIcon?: | ComponentProps["rightIcon"] | undefined; description?: ToReactNodesArgs | undefined; + full?: boolean; onSubmit?: (e: FormEvent) => void; hasChildForm?: boolean; wide?: boolean; @@ -74,6 +77,7 @@ type CardFormProps = { paddingBottom?: CardFormPaddingBottom | undefined; belowFormContent?: ToReactNodesArgs | undefined; belowFormContentGap?: BelowCardFormContentGap | undefined; + forceMarginAfterSubtitle?: boolean; }; type ResolvePropsArgs = { @@ -171,7 +175,9 @@ export function CardForm({ disabled, topContent = null, title, + titleSize = "Large", description, + full = false, onSubmit, titleRightIcon, /* In some cases eg third party libs we can't avoid the need for child forms, so the @@ -185,6 +191,7 @@ export function CardForm({ paddingBottom: paddingBottomProp, belowFormContent, belowFormContentGap = 0, + forceMarginAfterSubtitle = true, }: CardFormProps) { const theme = useTheme(); const { @@ -228,6 +235,7 @@ export function CardForm({ ? toReactNodesWithTypographyMap( description, defaultDescriptionTypographyMap, + false, ) : null; @@ -235,8 +243,10 @@ export function CardForm({ ? toReactNodes(belowFormContent) : null; + const CardFormContentTarget = full ? CardFormContentFull : CardFormContent; + const content = ( - + {topContent} {title && ( @@ -248,6 +258,7 @@ export function CardForm({ : []), ]} display="inline-flex" + size={titleSize} /> )} @@ -255,7 +266,7 @@ export function CardForm({ {formattedDescription} )} {children} - + ); const commonProps = { @@ -271,21 +282,29 @@ export function CardForm({ backgroundColor, smBackgroundColor, smBorderWidth, + forceMarginAfterSubtitle, }; + const Container = full ? CardFormContentFull : CardFormContainer; + return ( - + {hasChildForm ? ( {content} ) : ( - + {content} )} {belowFormContentNodes} - + ); } @@ -300,6 +319,13 @@ const CardFormContent = styled.div` align-self: center; `; +const CardFormContentFull = styled.div` + display: flex; + flex-direction: column; + align-self: center; + height: 100%; +`; + type BelowCardFormContentProps = { gap: BelowCardFormContentGap; }; @@ -450,6 +476,7 @@ type StyledCardFormStyleProps = { backgroundColor: CardFormBackgroundColor; smBackgroundColor: CardFormBackgroundColor; smBorderWidth: CardFormBorderWidth; + forceMarginAfterSubtitle: boolean | undefined; }; const StyledCardFormStyle = ({ @@ -466,6 +493,7 @@ const StyledCardFormStyle = ({ backgroundColor, smBackgroundColor, smBorderWidth, + forceMarginAfterSubtitle = true, }: StyledCardFormStyleProps & { theme: Theme }) => { return css` ${formInset({ wide, paddingX, paddingTop, paddingBottom })} @@ -496,9 +524,11 @@ const StyledCardFormStyle = ({ text-align: ${textAlign}; } - ${CardFormSubtitle} + * { + ${forceMarginAfterSubtitle + ? `${CardFormSubtitle.toString()} + * { margin-top: 40px !important; - } + }` + : ""} ${CardFormButtonRow}, ${StyledButtonRowButton} { ${ButtonSelector()} { @@ -551,13 +581,14 @@ const StyledCardFormStyle = ({ const StyledCardForm = styled.form(StyledCardFormStyle); + const StyledCardFormDiv = styled.div(StyledCardFormStyle); const CardHeadline = styled.div<{ hasTopContent: boolean }>` padding: 0 ${Spacing.px.xs}; - ${({ hasTopContent }) => (hasTopContent ? "margin-top: 32px;" : "")} + ${({ hasTopContent }) => (hasTopContent ? "margin-top: 24px;" : "")} & + *:not(${CardFormSubtitle}) { margin-top: 40px; diff --git a/packages/ui/src/components/Flex.tsx b/packages/ui/src/components/Flex.tsx index a45d4f52c..021d6ee9d 100644 --- a/packages/ui/src/components/Flex.tsx +++ b/packages/ui/src/components/Flex.tsx @@ -1,4 +1,5 @@ import styled from "@emotion/styled"; +import { isNumber } from "lodash-es"; import { type ElementType, type ReactNode } from "react"; type FlexProps = { @@ -35,6 +36,8 @@ type FlexProps = { | "pre-wrap" | "pre-line" | undefined; + height?: number | "auto" | "100%" | undefined; + width?: number | "auto" | "100%" | undefined; mt?: number | "auto" | undefined; mr?: number | "auto" | undefined; mb?: number | "auto" | undefined; @@ -67,6 +70,8 @@ export function Flex({ pr, pb, pl, + height, + width, }: FlexProps) { const justify = justifyProp ? justifyProp : center ? "center" : "stretch"; const align = alignProp ? alignProp : center ? "center" : "stretch"; @@ -92,6 +97,8 @@ export function Flex({ pb={pb} pl={pl} gap={gap} + height={height} + width={width} {...asButtonProps} > {children} @@ -128,6 +135,8 @@ type StyledFlexProps = { pl: FlexProps["pl"]; gap: number | undefined; as?: ElementType | undefined; + height?: FlexProps["height"]; + width?: FlexProps["width"]; }; export const StyledFlex = styled.div` @@ -166,4 +175,10 @@ export const StyledFlex = styled.div` ${({ pr }) => pr && `padding-right: ${pr}px;`} ${({ pb }) => pb && `padding-bottom: ${pb}px;`} ${({ pl }) => pl && `padding-left: ${pl}px;`} + + ${({ height }) => + height && + (isNumber(height) ? `height: ${height}px;` : `height: ${height};`)} + ${({ width }) => + width && (isNumber(width) ? `width: ${width}px;` : `width: ${width};`)} `; diff --git a/packages/ui/src/components/IconWithCircleBackground.tsx b/packages/ui/src/components/IconWithCircleBackground.tsx index 134fd4b1a..52f8d96af 100644 --- a/packages/ui/src/components/IconWithCircleBackground.tsx +++ b/packages/ui/src/components/IconWithCircleBackground.tsx @@ -1,22 +1,25 @@ import styled from "@emotion/styled"; import { Link } from "../router.js"; -import { getColor } from "../styles/themes.js"; +import { getColor, type ThemeOrColorKey } from "../styles/themes.js"; import { type NewRoutesType } from "../types/index.js"; import { Flex } from "./Flex.js"; import { Icon } from "./Icon/Icon.js"; import { type IconName } from "./Icon/types.js"; -type IconWidth = 40 | 30 | 16.5; +type IconWidth = 40 | 36 | 30 | 20 | 16.5; type IconWithCircleBackgroundProps = { iconName?: IconName; iconWidth?: IconWidth | undefined; + iconColor?: ThemeOrColorKey | undefined; to?: NewRoutesType | undefined; darkBg?: boolean; noBg?: boolean; + bgColor?: ThemeOrColorKey | undefined; shouldRotate?: boolean; onClick?: () => void; disabled?: boolean; + iconStrokeWidth?: number; }; export function IconWithCircleBackground({ @@ -28,6 +31,9 @@ export function IconWithCircleBackground({ shouldRotate = false, noBg = false, disabled = false, + bgColor, + iconColor, + iconStrokeWidth, }: IconWithCircleBackgroundProps) { const content = ( @@ -67,14 +75,17 @@ type StyledIconWithCircleBackgroundProps = { darkBg: boolean; shouldRotate: boolean; noBg: boolean; + bgColor: ThemeOrColorKey | undefined; }; const StyledIconWithCircleBackground = styled.div` - background: ${({ theme, darkBg, noBg }) => + background: ${({ theme, darkBg, noBg, bgColor }) => darkBg ? `linear-gradient(291.4deg, #1C243F 0%, #21283A 100%)` : noBg ? "transparent" + : bgColor + ? getColor(theme, bgColor) : getColor(theme, "grayBlue94")}; border-radius: 50%; padding: ${({ size }) => getPadding(size)}px; @@ -105,5 +116,11 @@ function getPadding(size: IconWidth) { if (size === 40) { return 20; } + if (size === 36) { + return 14; + } + if (size === 20) { + return 10; + } return 16; } diff --git a/packages/ui/src/styles/colors.tsx b/packages/ui/src/styles/colors.tsx index 55ee5187d..878fcffb0 100644 --- a/packages/ui/src/styles/colors.tsx +++ b/packages/ui/src/styles/colors.tsx @@ -46,6 +46,7 @@ const baseColors = { blue22: "#0E2E60", blue39: "#0068C9", blue37: "#21529c", + blue50: "#2483D1", blue58: "#28BFFF", // less than 50% saturated blue grayBlue5: "#0c0d0f", diff --git a/packages/ui/src/utils/toReactNodes/setReactNodesTypography.tsx b/packages/ui/src/utils/toReactNodes/setReactNodesTypography.tsx index 496ee12f1..a5ca2c0ae 100644 --- a/packages/ui/src/utils/toReactNodes/setReactNodesTypography.tsx +++ b/packages/ui/src/utils/toReactNodes/setReactNodesTypography.tsx @@ -126,6 +126,7 @@ function setTextNodeTypography( export function toReactNodesWithTypographyMap( nodesArg: ToReactNodesArgs, nodesTypographyMap: SetReactNodesTypographyMap, + replaceExistingTypography = true, ) { if (!nodesArg) { return null; @@ -134,6 +135,7 @@ export function toReactNodesWithTypographyMap( const nodesWithTypography = setReactNodesTypography( nodesArg, nodesTypographyMap, + replaceExistingTypography, ); const nodes = toReactNodes(nodesWithTypography); From 9373fd41669f4cc080568f952a701de9c952f538 Mon Sep 17 00:00:00 2001 From: Aaryaman Bhute <35084309+AaryamanBhute@users.noreply.github.com> Date: Wed, 2 Apr 2025 11:39:53 -0700 Subject: [PATCH 18/26] Created Uma Splash (#16611) Created new UMA splash ![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/oOuIssbrOKuvWkHIBu32/173065b7-e1d8-4c84-a322-98ffdd405b7a.png) GitOrigin-RevId: 62935d2ea044b3082c3a102f49064db71c667de4 --- packages/ui/src/components/Flex.tsx | 5 +++++ packages/ui/src/icons/Globe.tsx | 26 +++++++++++++++++++------- packages/ui/src/styles/colors.tsx | 1 + 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/components/Flex.tsx b/packages/ui/src/components/Flex.tsx index 021d6ee9d..45ed54582 100644 --- a/packages/ui/src/components/Flex.tsx +++ b/packages/ui/src/components/Flex.tsx @@ -47,6 +47,7 @@ type FlexProps = { pb?: number | undefined; pl?: number | undefined; gap?: number | undefined; + position?: "absolute" | "relative" | undefined; }; export function Flex({ @@ -72,6 +73,7 @@ export function Flex({ pl, height, width, + position, }: FlexProps) { const justify = justifyProp ? justifyProp : center ? "center" : "stretch"; const align = alignProp ? alignProp : center ? "center" : "stretch"; @@ -99,6 +101,7 @@ export function Flex({ gap={gap} height={height} width={width} + position={position} {...asButtonProps} > {children} @@ -137,6 +140,7 @@ type StyledFlexProps = { as?: ElementType | undefined; height?: FlexProps["height"]; width?: FlexProps["width"]; + position?: FlexProps["position"]; }; export const StyledFlex = styled.div` @@ -181,4 +185,5 @@ export const StyledFlex = styled.div` (isNumber(height) ? `height: ${height}px;` : `height: ${height};`)} ${({ width }) => width && (isNumber(width) ? `width: ${width}px;` : `width: ${width};`)} + ${({ position }) => position && `position: ${position};`} `; diff --git a/packages/ui/src/icons/Globe.tsx b/packages/ui/src/icons/Globe.tsx index 8519169b2..01db75310 100644 --- a/packages/ui/src/icons/Globe.tsx +++ b/packages/ui/src/icons/Globe.tsx @@ -5,13 +5,7 @@ export function Globe({ strokeLinecap = "round", }: PathProps) { return ( - + + + + ); } diff --git a/packages/ui/src/styles/colors.tsx b/packages/ui/src/styles/colors.tsx index 878fcffb0..05c9c2c39 100644 --- a/packages/ui/src/styles/colors.tsx +++ b/packages/ui/src/styles/colors.tsx @@ -90,6 +90,7 @@ const baseColors = { secondary: neutral.black, gray: "#242526", gray2: "#6D7685", + gray3: "#D9DBDF", // transparent transparent: "transparent", transparenta02: "#00000005", From cbcf5bec05e2b314621c4a9b79edeabca4485a17 Mon Sep 17 00:00:00 2001 From: Aaryaman Bhute <35084309+AaryamanBhute@users.noreply.github.com> Date: Wed, 2 Apr 2025 13:27:13 -0700 Subject: [PATCH 19/26] Connect Bank Screen (#16583) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # UMA Bridge: Enhanced Bank Connection UI This PR enhances the bank connection flow in the UMA Bridge application with several UI improvements: - Added a new RTP disclosure modal with scrollable content for better user experience - Updated the `BridgeModal` component to support scrollable content and custom positioning - Redesigned the Link Bank screen with clearer information architecture: - Added informative bullet points with icons highlighting key benefits - Improved layout with better spacing and alignment - Added RTP agreement footer with proper linking - Enhanced the `IconWithCircleBackground` component to support opaque icons - Updated the Modal and Drawer components to support custom top positioning - Added new language strings to support the updated UI - Fixed styling issues in various components for better mobile responsiveness These changes improve the user experience when connecting bank accounts while maintaining compliance with financial regulations. **** ![Screenshot 2025-03-31 at 2.25.33 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/oOuIssbrOKuvWkHIBu32/a4e4abd2-f813-40e5-ac94-f83da1f48582.png) ![Screenshot 2025-03-31 at 2.26.03 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/oOuIssbrOKuvWkHIBu32/8c96a3b7-5d02-48a5-a16b-3b06889a2872.png) **** ![Screenshot 2025-03-31 at 2.27.11 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/oOuIssbrOKuvWkHIBu32/635603bd-5394-41a5-97e6-4d0b0b6a5278.png) ![Screenshot 2025-03-31 at 2.27.44 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/oOuIssbrOKuvWkHIBu32/27123aa6-98e9-4624-85dc-fe45be18bb02.png) GitOrigin-RevId: ff2e37d3e439693bc274ef0d7cd46da92848b326 --- packages/ui/src/components/CardForm/CardForm.tsx | 16 ++++++++++++---- packages/ui/src/components/Drawer.tsx | 6 +++++- .../src/components/IconWithCircleBackground.tsx | 9 +++++++-- packages/ui/src/components/Modal.tsx | 1 + packages/ui/src/icons/Bank.tsx | 3 ++- packages/ui/src/icons/types.tsx | 1 + 6 files changed, 28 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/CardForm/CardForm.tsx b/packages/ui/src/components/CardForm/CardForm.tsx index 5d2d3a2ac..581fcd7f2 100644 --- a/packages/ui/src/components/CardForm/CardForm.tsx +++ b/packages/ui/src/components/CardForm/CardForm.tsx @@ -65,6 +65,7 @@ type CardFormProps = { titleRightIcon?: | ComponentProps["rightIcon"] | undefined; + afterTitleMargin?: 24 | 40 | undefined; description?: ToReactNodesArgs | undefined; full?: boolean; onSubmit?: (e: FormEvent) => void; @@ -192,6 +193,7 @@ export function CardForm({ belowFormContent, belowFormContentGap = 0, forceMarginAfterSubtitle = true, + afterTitleMargin = 40, }: CardFormProps) { const theme = useTheme(); const { @@ -249,7 +251,10 @@ export function CardForm({ {topContent} {title && ( - + (StyledCardFormStyle); -const CardHeadline = styled.div<{ hasTopContent: boolean }>` +const CardHeadline = styled.div<{ + hasTopContent: boolean; + afterTitleMargin: number; +}>` padding: 0 ${Spacing.px.xs}; ${({ hasTopContent }) => (hasTopContent ? "margin-top: 24px;" : "")} - & + *:not(${CardFormSubtitle}) { - margin-top: 40px; + & + *:not(${CardFormSubtitle.toString()}) { + margin-top: ${({ afterTitleMargin }) => afterTitleMargin}px; } & + ${CardFormSubtitle} { diff --git a/packages/ui/src/components/Drawer.tsx b/packages/ui/src/components/Drawer.tsx index 5bc8db93d..7b65fde42 100644 --- a/packages/ui/src/components/Drawer.tsx +++ b/packages/ui/src/components/Drawer.tsx @@ -20,6 +20,7 @@ interface Props { handleBack?: (() => void) | undefined; padding?: string | undefined; kind?: DrawerKind | undefined; + top?: number | undefined; } export const Drawer = (props: Props) => { @@ -105,6 +106,7 @@ export const Drawer = (props: Props) => { totalDeltaY={totalDeltaY} grabbing={grabbing} ref={drawerContainerRef} + top={props.top} > {props.grabber && !props.nonDismissable && ( @@ -170,6 +172,7 @@ const DrawerContainer = styled.div<{ isOpen: boolean; totalDeltaY: number; grabbing: boolean; + top?: number | undefined; }>` position: fixed; max-height: 100dvh; @@ -182,6 +185,8 @@ const DrawerContainer = styled.div<{ flex-direction: column; align-items: center; + ${(props) => props.top && `top: ${props.top}px;`} + // Only smooth transition when not grabbing, otherwise dragging will feel very laggy ${(props) => props.grabbing && props.isOpen @@ -233,7 +238,6 @@ const DrawerInnerContainer = styled.div<{ ? `${props.padding}` : `${Spacing.px["6xl"]} ${Spacing.px.xl} ${Spacing.px["2xl"]} ${Spacing.px.xl}`}; - overflow-y: auto; `; const Grabber = styled.div` diff --git a/packages/ui/src/components/IconWithCircleBackground.tsx b/packages/ui/src/components/IconWithCircleBackground.tsx index 52f8d96af..ae088f80a 100644 --- a/packages/ui/src/components/IconWithCircleBackground.tsx +++ b/packages/ui/src/components/IconWithCircleBackground.tsx @@ -6,7 +6,7 @@ import { Flex } from "./Flex.js"; import { Icon } from "./Icon/Icon.js"; import { type IconName } from "./Icon/types.js"; -type IconWidth = 40 | 36 | 30 | 20 | 16.5; +type IconWidth = 40 | 36 | 30 | 20 | 16.5 | 14; type IconWithCircleBackgroundProps = { iconName?: IconName; @@ -20,6 +20,7 @@ type IconWithCircleBackgroundProps = { onClick?: () => void; disabled?: boolean; iconStrokeWidth?: number; + opaque?: boolean; }; export function IconWithCircleBackground({ @@ -34,6 +35,7 @@ export function IconWithCircleBackground({ bgColor, iconColor, iconStrokeWidth, + opaque = false, }: IconWithCircleBackgroundProps) { const content = ( diff --git a/packages/ui/src/components/Modal.tsx b/packages/ui/src/components/Modal.tsx index 687ecd45d..8d6dce741 100644 --- a/packages/ui/src/components/Modal.tsx +++ b/packages/ui/src/components/Modal.tsx @@ -413,6 +413,7 @@ export function Modal({ handleBack={handleBack} kind={drawerKind ?? "default"} padding={drawerPadding !== undefined ? `${drawerPadding}px` : undefined} + top={top} > {modalContent} diff --git a/packages/ui/src/icons/Bank.tsx b/packages/ui/src/icons/Bank.tsx index 00853999d..a8d2693ef 100644 --- a/packages/ui/src/icons/Bank.tsx +++ b/packages/ui/src/icons/Bank.tsx @@ -6,12 +6,13 @@ export function Bank({ strokeWidth = "1.5", strokeLinecap = "square", strokeLinejoin = "round", + fill = "none", }: PathProps) { return ( Date: Thu, 3 Apr 2025 12:36:58 -0700 Subject: [PATCH 20/26] Updating textput and bridge login page (#16422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ![Screenshot 2025-03-21 at 3.59.13 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/EJphitdgYFvuACNazm1q/4f002966-c459-428a-be49-ed35a43ca675.png) ![Screenshot 2025-03-21 at 3.59.16 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/EJphitdgYFvuACNazm1q/37d1ea27-4bc8-4295-91ec-52415c1e50d9.png) GitOrigin-RevId: 9558bd24d62022f122292856d7ed78938648db2e --- packages/ui/src/components/TextInput.tsx | 12 +++++++++--- packages/ui/src/styles/colors.tsx | 1 + packages/ui/src/styles/fields.tsx | 6 +++++- packages/ui/src/styles/themes.tsx | 3 +++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/TextInput.tsx b/packages/ui/src/components/TextInput.tsx index 3e8a0807b..29634f9fd 100644 --- a/packages/ui/src/components/TextInput.tsx +++ b/packages/ui/src/components/TextInput.tsx @@ -111,6 +111,7 @@ export type TextInputProps = { width?: "full" | "short" | undefined; paddingX?: number; paddingY?: number; + marginTop?: number | undefined; // Outline that appears outside/offset when the input is focused activeOutline?: boolean; activeOutlineColor?: ThemeOrColorKey; @@ -314,7 +315,7 @@ export function TextInput(textInputProps: TextInputProps) { const { select } = props; return ( - + {props.label ? ( {props.label} ) : null} @@ -422,7 +423,6 @@ interface InputProps { const Input = styled.input` ${textInputStyle}; - // disable autofill styles in chrome https://stackoverflow.com/a/68240841/9808766 &:-webkit-autofill, &:-webkit-autofill:focus, @@ -480,9 +480,15 @@ export const TextInputHalfRow = styled.div` } `; -const StyledTextInput = styled.div<{ widthProp: string }>` +const StyledTextInput = styled.div<{ + widthProp: string; + marginTop: number | undefined; +}>` width: ${({ widthProp }) => widthProp}; position: relative; + /* Apply marginTop to every TextInput when specified */ + margin-top: ${({ marginTop }) => + marginTop !== undefined ? `${marginTop}px` : ""}; /* eg forms, should be left consistent: */ & + & { diff --git a/packages/ui/src/styles/colors.tsx b/packages/ui/src/styles/colors.tsx index 05c9c2c39..0476d5e34 100644 --- a/packages/ui/src/styles/colors.tsx +++ b/packages/ui/src/styles/colors.tsx @@ -91,6 +91,7 @@ const baseColors = { gray: "#242526", gray2: "#6D7685", gray3: "#D9DBDF", + gray4: "#B6BAC3", // transparent transparent: "transparent", transparenta02: "#00000005", diff --git a/packages/ui/src/styles/fields.tsx b/packages/ui/src/styles/fields.tsx index 9d37215c2..8189d0e15 100644 --- a/packages/ui/src/styles/fields.tsx +++ b/packages/ui/src/styles/fields.tsx @@ -2,9 +2,11 @@ import { css } from "@emotion/react"; import styled from "@emotion/styled"; import { useLayoutEffect, useRef, useState } from "react"; import { type PartialSimpleTypographyProps } from "../components/typography/types.js"; +import { colors } from "./colors.js"; import { standardBorderRadius, subtext } from "./common.js"; import { getColor, + isBridge, themeOr, type ThemeOrColorKey, type ThemeProp, @@ -56,7 +58,9 @@ export const textInputPaddingPx = 12; export const textInputPadding = `${textInputPaddingPx}px`; export const textInputBorderColor = ({ theme }: ThemeProp) => - themeOr(theme.c1Neutral, theme.c3Neutral)({ theme }); + isBridge(theme) + ? colors.gray4 + : themeOr(theme.c1Neutral, theme.c3Neutral)({ theme }); export const textInputBorderColorFocused = ({ theme }: ThemeProp) => themeOr(theme.hcNeutral, theme.hcNeutral)({ theme }); diff --git a/packages/ui/src/styles/themes.tsx b/packages/ui/src/styles/themes.tsx index f555c2b15..334137033 100644 --- a/packages/ui/src/styles/themes.tsx +++ b/packages/ui/src/styles/themes.tsx @@ -817,6 +817,9 @@ export const isLight = (theme: Theme) => Themes.UmaAuthSdkLight, ].includes(theme.type); +export const isBridge = (theme: Theme) => + [Themes.BridgeLight, Themes.BridgeDark].includes(theme.type); + export const themeOr = (lightValue: string, darkValue: string) => ({ theme }: { theme: Theme }) => { From 5594ea22a5e915907be6b92482ca71041efe3117 Mon Sep 17 00:00:00 2001 From: Jeremy Klein Date: Thu, 3 Apr 2025 17:29:29 -0700 Subject: [PATCH 21/26] Adding a banner notifying of the switch to uma.money. (#16651) * WIP: Adding a banner notifying of the switch to uma.money. Mostly looks good. Still TODO: - Update to only show on one page load - Check on desktop styling with design - Check on underline not working * lint * Fix the underline and move x all the way right GitOrigin-RevId: e431cf3c55dac687102ad42e52d8b125ffb655c4 --- packages/ui/src/router.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/router.tsx b/packages/ui/src/router.tsx index f4698dc0f..e6affdeb6 100644 --- a/packages/ui/src/router.tsx +++ b/packages/ui/src/router.tsx @@ -142,6 +142,7 @@ export const LinkBase = forwardRef( size: typography.size, color: typography.color, children: text, + underline: typography.underline, }) : text; } From 74bf633fdc0e9c3918b6119f16cccbdd2b85a5ba Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Fri, 4 Apr 2025 12:55:31 -0700 Subject: [PATCH 22/26] Update currency selection and send page for demo send (#16410) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contributes to PX-247. Got design sign off from Pat https://www.figma.com/design/p1r9oT0wU2NxDLfPPefRYV/US%3EMX-Grid-Switch?node-id=2417-23652&m=dev This PR: - Adds banner type to callout component - Adds new demo mode modal - Adds new send demo flow - Updates the back button per in the BridgeCardForm per spec - Adds subtitle to send flow ![Screenshot 2025-03-25 at 1.36.04 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/EJphitdgYFvuACNazm1q/f0f6191e-ef6b-4b29-8c98-3eb980c39931.png) ![Screenshot 2025-03-25 at 1.57.53 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/EJphitdgYFvuACNazm1q/65f389ff-2474-4369-b7b1-240fb888436d.png) GitOrigin-RevId: ca992a573566de2b21e8ec20aaeaa32c34f323ca --- packages/ui/src/icons/central/ArrowUpDown.tsx | 25 +++++++++++++++++++ packages/ui/src/icons/central/index.tsx | 1 + packages/ui/src/styles/colors.tsx | 1 + packages/ui/src/styles/themes.tsx | 1 + 4 files changed, 28 insertions(+) create mode 100644 packages/ui/src/icons/central/ArrowUpDown.tsx diff --git a/packages/ui/src/icons/central/ArrowUpDown.tsx b/packages/ui/src/icons/central/ArrowUpDown.tsx new file mode 100644 index 000000000..1c7e10670 --- /dev/null +++ b/packages/ui/src/icons/central/ArrowUpDown.tsx @@ -0,0 +1,25 @@ +import { type PathProps } from "../types.js"; + +export function ArrowUpDown({ + strokeWidth = "1.5", + strokeLinecap = "round", + strokeLinejoin = "round", +}: PathProps = {}) { + return ( + + + + ); +} diff --git a/packages/ui/src/icons/central/index.tsx b/packages/ui/src/icons/central/index.tsx index a7f296ceb..d68a035f5 100644 --- a/packages/ui/src/icons/central/index.tsx +++ b/packages/ui/src/icons/central/index.tsx @@ -3,6 +3,7 @@ export { ArrowDownLeft as CentralArrowDownLeft } from "./ArrowDownLeft.js"; export { ArrowInbox as CentralArrowInbox } from "./ArrowInbox.js"; export { ArrowLeft as CentralArrowLeft } from "./ArrowLeft.js"; export { ArrowsRepeatLeftRight as CentralArrowsRepeatLeftRight } from "./ArrowsRepeatLeftRight.js"; +export { ArrowUpDown as CentralArrowUpDown } from "./ArrowUpDown.js"; export { ArrowUpRight as CentralArrowUpRight } from "./ArrowUpRight.js"; export { Bank as CentralBank } from "./Bank.js"; export { BarsThree as CentralBarsThree } from "./BarsThree.js"; diff --git a/packages/ui/src/styles/colors.tsx b/packages/ui/src/styles/colors.tsx index 0476d5e34..a32f95c37 100644 --- a/packages/ui/src/styles/colors.tsx +++ b/packages/ui/src/styles/colors.tsx @@ -92,6 +92,7 @@ const baseColors = { gray2: "#6D7685", gray3: "#D9DBDF", gray4: "#B6BAC3", + gray6: "#8E95A2", // transparent transparent: "transparent", transparenta02: "#00000005", diff --git a/packages/ui/src/styles/themes.tsx b/packages/ui/src/styles/themes.tsx index 334137033..f25f05fda 100644 --- a/packages/ui/src/styles/themes.tsx +++ b/packages/ui/src/styles/themes.tsx @@ -670,6 +670,7 @@ const bridgeLightTheme = extend(lightTheme, { smBg: colors.gray98, text: colors.grayBlue9, secondary: colors.grayBlue43, + tertiary: colors.gray3, inputBackground: colors.white, danger: colors.red50, }); From be770b22a7ff2b6e7edff7f83bf577c5645c49ab Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Fri, 4 Apr 2025 13:13:20 -0700 Subject: [PATCH 23/26] add onboarding steps complete modal (#16414) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Adds a modal which shows completed steps shows all remaining onboarding tasks. completed tasks will replace their icon with a checkmark closes PX-259 | FIGMA| bridge | | -------- | ------- | | ![Screenshot 2025-03-24 at 1.32.49 PM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/kXS0LK7Piva9kiHcEnGv/00c42c96-cee1-4290-a50a-85bad0f076ea.png) | ![Screenshot 2025-04-04 at 11.59.10 AM.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/kXS0LK7Piva9kiHcEnGv/696e07ee-5edb-4680-9876-98a1d044de13.png) | GitOrigin-RevId: a671d1d6fc8c0359f21a339aa0d18174df8a9754 --- packages/ui/src/components/Modal.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/ui/src/components/Modal.tsx b/packages/ui/src/components/Modal.tsx index 8d6dce741..55ab3bb19 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 type { ButtonBorderRadius } from "../styles/themeDefaults/buttons.js"; import { Spacing } from "../styles/tokens/spacing.js"; import { overflowAutoWithoutScrollbars } from "../styles/utils.js"; import { z } from "../styles/z-index.js"; @@ -108,6 +109,8 @@ type ModalProps = { buttonOrder?: "submit-first" | "cancel-first"; /** Determines if buttons are laid out horizontally or vertically */ buttonLayout?: "horizontal" | "vertical"; + /** Determines the border radius of the buttons */ + buttonBorderRadius?: ButtonBorderRadius | undefined; /** Allows placing extra buttons in the same button layout */ extraActions?: ExtraAction[] | undefined; /** Displays a back button at the top of the modal which calls this function */ @@ -149,6 +152,7 @@ export function Modal({ progressBar, buttonOrder, buttonLayout = "horizontal", + buttonBorderRadius, extraActions, handleBack, appendToElement, @@ -317,6 +321,7 @@ export function Modal({ type={submitLink ? "button" : "submit"} /* The form element handles submit events when submit button is not a link: */ onClick={submitLink ? onSubmit : undefined} + borderRadius={buttonBorderRadius} /> )} {displayCancelBelow && !cancelHidden && ( From aaaf42ca5dc55f89ea3d2e3df24487d958ba37a9 Mon Sep 17 00:00:00 2001 From: Jeremy Klein Date: Fri, 4 Apr 2025 13:16:46 -0700 Subject: [PATCH 24/26] [JS SDKs] Add errorName from extensions when requests fail. (#16674) GitOrigin-RevId: 6850d721419ec09c6cd8acc80aec22539767eb95 --- packages/core/src/requester/Requester.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/core/src/requester/Requester.ts b/packages/core/src/requester/Requester.ts index 651f75428..cedabadc2 100644 --- a/packages/core/src/requester/Requester.ts +++ b/packages/core/src/requester/Requester.ts @@ -208,9 +208,22 @@ class Requester { }; const data = responseJson.data; if (!data) { + let firstErrorName: string | undefined = undefined; + if ( + Array.isArray(responseJson.errors) && + responseJson.errors.length > 0 + ) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const firstError = responseJson.errors[0]; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + firstErrorName = firstError["extensions"]?.["error_name"] as + | string + | undefined; + } throw new LightsparkException( "RequestFailed", `Request ${operation} failed. ${JSON.stringify(responseJson.errors)}`, + { errorName: firstErrorName }, ); } return data; From 980ea39aaf41078bfd0702275cf16556ca0fd9f2 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Fri, 4 Apr 2025 13:27:23 -0700 Subject: [PATCH 25/26] remove border radius param from modal (#16675) # remove border radius parameter from Modal GitOrigin-RevId: b073087678f0ef187bb5e79289f8fc010a4616c5 --- packages/ui/src/components/Modal.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/ui/src/components/Modal.tsx b/packages/ui/src/components/Modal.tsx index 55ab3bb19..8d6dce741 100644 --- a/packages/ui/src/components/Modal.tsx +++ b/packages/ui/src/components/Modal.tsx @@ -19,7 +19,6 @@ import { standardContentInset, standardFocusOutline, } from "../styles/common.js"; -import type { ButtonBorderRadius } from "../styles/themeDefaults/buttons.js"; import { Spacing } from "../styles/tokens/spacing.js"; import { overflowAutoWithoutScrollbars } from "../styles/utils.js"; import { z } from "../styles/z-index.js"; @@ -109,8 +108,6 @@ type ModalProps = { buttonOrder?: "submit-first" | "cancel-first"; /** Determines if buttons are laid out horizontally or vertically */ buttonLayout?: "horizontal" | "vertical"; - /** Determines the border radius of the buttons */ - buttonBorderRadius?: ButtonBorderRadius | undefined; /** Allows placing extra buttons in the same button layout */ extraActions?: ExtraAction[] | undefined; /** Displays a back button at the top of the modal which calls this function */ @@ -152,7 +149,6 @@ export function Modal({ progressBar, buttonOrder, buttonLayout = "horizontal", - buttonBorderRadius, extraActions, handleBack, appendToElement, @@ -321,7 +317,6 @@ export function Modal({ type={submitLink ? "button" : "submit"} /* The form element handles submit events when submit button is not a link: */ onClick={submitLink ? onSubmit : undefined} - borderRadius={buttonBorderRadius} /> )} {displayCancelBelow && !cancelHidden && ( From 963174d5b5983eda385d16a509f976c6655df7ce Mon Sep 17 00:00:00 2001 From: Jeremy Klein Date: Fri, 4 Apr 2025 13:38:46 -0700 Subject: [PATCH 26/26] Create happy-spoons-sort.md --- .changeset/happy-spoons-sort.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/happy-spoons-sort.md diff --git a/.changeset/happy-spoons-sort.md b/.changeset/happy-spoons-sort.md new file mode 100644 index 000000000..a8b6243b5 --- /dev/null +++ b/.changeset/happy-spoons-sort.md @@ -0,0 +1,10 @@ +--- +"@lightsparkdev/lightspark-sdk": patch +"@lightsparkdev/oauth": patch +"@lightsparkdev/core": patch +"@lightsparkdev/ui": patch +--- + +* Surface error name when the requester hits a graphQL error. +* Update Turbo +* Several small UI package improvements.