From 6c275099cea73d8aa016f00794439c50029b5217 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:26:11 +0000 Subject: [PATCH 1/2] Initial plan From 59b2afe354d0ca6f872a06ebebd5f76157838527 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:52:33 +0000 Subject: [PATCH 2/2] feat: add Buy G$ widget package with storybook fixtures and smoke tests Co-authored-by: sirpy <671095+sirpy@users.noreply.github.com> --- examples/storybook/package.json | 3 +- .../storybook/src/fixtures/buyGdWidgetMock.ts | 54 + .../buy-gd-widget/BuyGdWidget.stories.tsx | 113 + packages/buy-gd-widget/package.json | 51 + packages/buy-gd-widget/src/BuyGdView.tsx | 112 + packages/buy-gd-widget/src/BuyGdWidget.tsx | 163 + .../src/LiveBuyGdWidgetInner.tsx | 14 + packages/buy-gd-widget/src/element.ts | 12 + packages/buy-gd-widget/src/index.ts | 16 + packages/buy-gd-widget/src/integration.ts | 10 + packages/buy-gd-widget/src/register.ts | 12 + packages/buy-gd-widget/src/useBuyGdAdapter.ts | 167 + packages/buy-gd-widget/src/web3sdkv2.d.ts | 14 + .../src/widgetRuntimeContract.ts | 75 + packages/buy-gd-widget/tsconfig.build.json | 11 + packages/buy-gd-widget/tsconfig.json | 14 + packages/buy-gd-widget/tsup.config.ts | 15 + pnpm-lock.yaml | 30114 ++++++++++++---- tests/widgets/buy-gd-widget/states.spec.ts | 66 + .../test-results/bgw-01-no-wallet.png | Bin 0 -> 25120 bytes .../test-results/bgw-02-idle.png | Bin 0 -> 21648 bytes .../test-results/bgw-03-loading.png | Bin 0 -> 25225 bytes .../test-results/bgw-04-onramper.png | Bin 0 -> 30496 bytes .../bgw-05-transaction-pending.png | Bin 0 -> 24952 bytes .../test-results/bgw-06-success.png | Bin 0 -> 32194 bytes .../test-results/bgw-07-error.png | Bin 0 -> 31144 bytes 26 files changed, 23286 insertions(+), 7750 deletions(-) create mode 100644 examples/storybook/src/fixtures/buyGdWidgetMock.ts create mode 100644 examples/storybook/src/stories/buy-gd-widget/BuyGdWidget.stories.tsx create mode 100644 packages/buy-gd-widget/package.json create mode 100644 packages/buy-gd-widget/src/BuyGdView.tsx create mode 100644 packages/buy-gd-widget/src/BuyGdWidget.tsx create mode 100644 packages/buy-gd-widget/src/LiveBuyGdWidgetInner.tsx create mode 100644 packages/buy-gd-widget/src/element.ts create mode 100644 packages/buy-gd-widget/src/index.ts create mode 100644 packages/buy-gd-widget/src/integration.ts create mode 100644 packages/buy-gd-widget/src/register.ts create mode 100644 packages/buy-gd-widget/src/useBuyGdAdapter.ts create mode 100644 packages/buy-gd-widget/src/web3sdkv2.d.ts create mode 100644 packages/buy-gd-widget/src/widgetRuntimeContract.ts create mode 100644 packages/buy-gd-widget/tsconfig.build.json create mode 100644 packages/buy-gd-widget/tsconfig.json create mode 100644 packages/buy-gd-widget/tsup.config.ts create mode 100644 tests/widgets/buy-gd-widget/states.spec.ts create mode 100644 tests/widgets/buy-gd-widget/test-results/bgw-01-no-wallet.png create mode 100644 tests/widgets/buy-gd-widget/test-results/bgw-02-idle.png create mode 100644 tests/widgets/buy-gd-widget/test-results/bgw-03-loading.png create mode 100644 tests/widgets/buy-gd-widget/test-results/bgw-04-onramper.png create mode 100644 tests/widgets/buy-gd-widget/test-results/bgw-05-transaction-pending.png create mode 100644 tests/widgets/buy-gd-widget/test-results/bgw-06-success.png create mode 100644 tests/widgets/buy-gd-widget/test-results/bgw-07-error.png diff --git a/examples/storybook/package.json b/examples/storybook/package.json index 1258d105..009e0aa5 100644 --- a/examples/storybook/package.json +++ b/examples/storybook/package.json @@ -21,7 +21,8 @@ "react": "^18.3.0", "react-dom": "^18.3.0", "react-native-web": "^0.19.13", - "viem": "^2.0.0" + "viem": "^2.0.0", + "@goodwidget/buy-gd-widget": "workspace:*" }, "devDependencies": { "@storybook/addon-essentials": "^8.6.17", diff --git a/examples/storybook/src/fixtures/buyGdWidgetMock.ts b/examples/storybook/src/fixtures/buyGdWidgetMock.ts new file mode 100644 index 00000000..fbac1ac2 --- /dev/null +++ b/examples/storybook/src/fixtures/buyGdWidgetMock.ts @@ -0,0 +1,54 @@ +import type { BuyGdWidgetState } from '@goodwidget/buy-gd-widget' + +export const buyGdWidgetMockStates: Record> = { + noWallet: { + status: 'no_wallet', + hasProvider: false, + chainId: null, + address: null, + }, + idle: { + status: 'idle', + hasProvider: true, + chainId: 42220, + address: '0x1111111111111111111111111111111111111111', + fiatAmount: '100', + stableMinAmount: '0', + currency: 'USD', + }, + loading: { + status: 'loading', + hasProvider: true, + chainId: 42220, + address: '0x1111111111111111111111111111111111111111', + }, + onramper: { + status: 'onramper', + hasProvider: true, + chainId: 42220, + address: '0x1111111111111111111111111111111111111111', + fiatAmount: '120', + currency: 'EUR', + }, + transactionPending: { + status: 'transaction_pending', + hasProvider: true, + chainId: 42220, + address: '0x1111111111111111111111111111111111111111', + txHash: '0xabc1230000000000000000000000000000000000000000000000000000000000', + }, + success: { + status: 'success', + hasProvider: true, + chainId: 42220, + address: '0x1111111111111111111111111111111111111111', + txHash: '0xabc1230000000000000000000000000000000000000000000000000000000000', + }, + error: { + status: 'error', + hasProvider: true, + chainId: 42220, + address: '0x1111111111111111111111111111111111111111', + error: 'Onramper payout failed. Retry the buy flow.', + }, +} diff --git a/examples/storybook/src/stories/buy-gd-widget/BuyGdWidget.stories.tsx b/examples/storybook/src/stories/buy-gd-widget/BuyGdWidget.stories.tsx new file mode 100644 index 00000000..7ff1684e --- /dev/null +++ b/examples/storybook/src/stories/buy-gd-widget/BuyGdWidget.stories.tsx @@ -0,0 +1,113 @@ +import React from 'react' +import type { Meta, StoryObj } from '@storybook/react' +import { + BuyGdWidget, + type BuyGdWidgetAdapterFactory, + type BuyGdWidgetAdapterResult, + type BuyGdWidgetState, +} from '@goodwidget/buy-gd-widget' +import { createCustodialEip1193Provider } from '../../fixtures/custodialEip1193' +import { buyGdWidgetMockStates } from '../../fixtures/buyGdWidgetMock' +import { + getInjectedEip1193Provider, + isInjectedProviderUsable, +} from '../../fixtures/injectedEip1193' + +const provider = createCustodialEip1193Provider() + +const meta: Meta = { + title: 'QA/BuyGdWidget/Runtime Fixtures', + component: BuyGdWidget, + tags: ['autodocs', 'qa'], + parameters: { + layout: 'padded', + }, +} + +export default meta +type Story = StoryObj + +function createAdapterFactory(mockState: Partial): BuyGdWidgetAdapterFactory { + return () => { + const state: BuyGdWidgetState = { + status: 'idle', + chainId: 42220, + address: '0x1111111111111111111111111111111111111111', + hasProvider: true, + fiatAmount: '100', + stableMinAmount: '0', + currency: 'USD', + error: null, + txHash: null, + ...mockState, + } + + const actions: BuyGdWidgetAdapterResult['actions'] = { + connect: async () => {}, + openOnramper: () => {}, + setFiatAmount: () => {}, + setStableMinAmount: () => {}, + setCurrency: () => {}, + startBuy: async () => {}, + retry: () => {}, + refresh: async () => {}, + } + + return { state, actions } + } +} + +function renderStory(state: Partial) { + return +} + +export const NoWallet: Story = { + render: () => renderStory(buyGdWidgetMockStates.noWallet), +} + +export const Idle: Story = { + render: () => renderStory(buyGdWidgetMockStates.idle), +} + +export const Loading: Story = { + render: () => renderStory(buyGdWidgetMockStates.loading), +} + +export const Onramper: Story = { + render: () => renderStory(buyGdWidgetMockStates.onramper), +} + +export const TransactionPending: Story = { + render: () => renderStory(buyGdWidgetMockStates.transactionPending), +} + +export const Success: Story = { + render: () => renderStory(buyGdWidgetMockStates.success), +} + +export const Error: Story = { + render: () => renderStory(buyGdWidgetMockStates.error), +} + +function InjectedWalletStory() { + const injectedProvider = getInjectedEip1193Provider() + + if (!isInjectedProviderUsable(injectedProvider)) { + return ( +
+ No injected wallet found +

Install/enable MetaMask (or another EIP-1193 wallet), then refresh Storybook.

+
+ ) + } + + return ( +
+ +
+ ) +} + +export const InjectedWallet: Story = { + render: () => , +} diff --git a/packages/buy-gd-widget/package.json b/packages/buy-gd-widget/package.json new file mode 100644 index 00000000..4f3fdd00 --- /dev/null +++ b/packages/buy-gd-widget/package.json @@ -0,0 +1,51 @@ +{ + "name": "@goodwidget/buy-gd-widget", + "version": "0.1.0", + "description": "Buy G$ widget for fiat onramp flow", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./element": { + "types": "./dist/element.d.ts", + "import": "./dist/element.js", + "require": "./dist/element.cjs" + }, + "./register": { + "types": "./dist/register.d.ts", + "import": "./dist/register.js", + "require": "./dist/register.cjs" + } + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "lint": "eslint src/", + "clean": "rm -rf dist .turbo" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + }, + "dependencies": { + "@gooddollar/web3sdk-v2": "0.4.44", + "@goodwidget/core": "workspace:*", + "@goodwidget/embed": "workspace:*", + "@goodwidget/ui": "workspace:*", + "@usedapp/core": "1.2.16" + }, + "devDependencies": { + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "react": "^18.3.0", + "react-dom": "^18.3.0", + "tsup": "^8.4.0", + "typescript": "^5.7.0" + } +} diff --git a/packages/buy-gd-widget/src/BuyGdView.tsx b/packages/buy-gd-widget/src/BuyGdView.tsx new file mode 100644 index 00000000..c8582466 --- /dev/null +++ b/packages/buy-gd-widget/src/BuyGdView.tsx @@ -0,0 +1,112 @@ +import React from 'react' +import { Button, ButtonText, Card, Input, Spinner, Text, XStack, YStack } from '@goodwidget/ui' +import type { BuyGdWidgetAdapterResult } from './widgetRuntimeContract' + +function OnramperFrame({ url }: { url: string }) { + return ( + +