Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
494567f
Initial plan
Copilot May 28, 2026
e10621e
chore: outline staking migration plan
Copilot May 28, 2026
25ae9bc
feat: add staking migration widget package and state coverage
Copilot May 28, 2026
ce66e76
refactor: unify staking migration states into single journey card
Copilot May 29, 2026
f09ad55
refactor: evolve staking migration CTA and compact status states
Copilot May 29, 2026
2e803a3
fix: restore migration summary visual weight during journey states
Copilot May 29, 2026
0dbdfc1
feat: add staking migration widget to react demo app
Copilot May 29, 2026
b61df67
Refine migration step flow visibility
Copilot Jun 1, 2026
1882939
feat: enhance migration widget with detailed step descriptions and st…
blueogin Jun 1, 2026
70e8cae
feat: improve migration step row styling and status indication
blueogin Jun 1, 2026
8c5b9a2
feat: enhance staking migration widget with action handling and impro…
blueogin Jun 1, 2026
0b67cdb
feat: enhance migration widget with attention indicators and status m…
blueogin Jun 1, 2026
55373b9
feat: add migration step marker component and integrate with migratio…
blueogin Jun 1, 2026
54032c9
feat: add 'Ready' state to StakingMigrationWidget and update related …
blueogin Jun 1, 2026
f58b138
feat: enhance MigrationProgressTimeline and MigrationStepRow with con…
blueogin Jun 1, 2026
0c7bd8a
feat: implement Stepper component and integrate with migration progre…
blueogin Jun 1, 2026
c6d2e0d
refactor: remove 'failed' and 'attention' status from connector color…
blueogin Jun 3, 2026
f7790fa
feat: add animated glyphs for stepper markers and improve visual repr…
blueogin Jun 3, 2026
514f9d0
feat: introduce StepperMarkerGlyph for animated stepper icons and enh…
blueogin Jun 3, 2026
bf6832a
refactor: remove unused MigrationStatusNotice component and related l…
blueogin Jun 3, 2026
58f3b99
feat: replace button with CircularActionButton in MigrationSummaryCar…
blueogin Jun 3, 2026
b65605a
feat: enhance StakingMigrationWidget with environment configuration a…
blueogin Jun 8, 2026
257fcf1
feat: add migration API token support and enhance StakingMigrationWid…
blueogin Jun 9, 2026
5b934dd
feat: update StakingMigrationWidget to use new migration API configur…
blueogin Jun 9, 2026
ceb3caa
chore: remove GoodDollar skill pack and related documentation files
blueogin Jun 10, 2026
078ea2d
refactor: remove migration API token from StakingMigrationWidget and …
blueogin Jun 10, 2026
9c09ff6
test: commit staking migration screenshots
Copilot Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/react-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@goodwidget/ui": "workspace:*",
"@goodwidget/embed": "workspace:*",
"@goodwidget/claim-widget-theme-demo": "workspace:*",
"@goodwidget/staking-migration-widget": "workspace:*",
"react": "^18.3.0",
"react-dom": "^18.3.0",
"react-native-web": "^0.19.13"
Expand Down
8 changes: 8 additions & 0 deletions examples/react-web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react'
import { GoodWidgetProvider, useWallet, useHost } from '@goodwidget/core'
import { ClaimWidget } from '@goodwidget/claim-widget-theme-demo'
import { StakingMigrationWidget } from '@goodwidget/staking-migration-widget'
import {
getThemeManifest,
MiniAppShell,
Expand Down Expand Up @@ -162,6 +163,13 @@ function OverrideShowcase() {
<ClaimWidget />
</YStack>

<YStack gap="$2">
<Text variant="label">StakingMigrationWidget:</Text>
<StakingMigrationWidget
migrationApiBaseUrl={import.meta.env.VITE_MIGRATION_API_BASE_URL}
/>
</YStack>

<Card>
<Heading level={4}>Form Controls</Heading>
<Input label="Name" placeholder="Enter your name..." />
Expand Down
8 changes: 8 additions & 0 deletions examples/react-web/src/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,12 @@ declare const process: {
env: Record<string, string | undefined>
}

interface ImportMetaEnv {
readonly VITE_MIGRATION_API_BASE_URL?: string
}

interface ImportMeta {
readonly env: ImportMetaEnv
}

declare function setImmediate(callback: (...args: unknown[]) => void, ...args: unknown[]): number
1 change: 1 addition & 0 deletions examples/storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@goodwidget/ui": "workspace:*",
"@goodwidget/claim-widget-theme-demo": "workspace:*",
"@goodwidget/citizen-claim-widget": "workspace:*",
"@goodwidget/staking-migration-widget": "workspace:*",
"react": "^18.3.0",
"react-dom": "^18.3.0",
"react-native-web": "^0.19.13",
Expand Down
35 changes: 35 additions & 0 deletions examples/storybook/src/stories/design-system/Stepper.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react'
import type { Meta, StoryObj } from '@storybook/react'
import { Stepper, Text, YStack, type StepperStepItem } from '@goodwidget/ui'

const meta: Meta<typeof Stepper> = {
title: 'Design System/Stepper',
component: Stepper,
tags: ['autodocs'],
parameters: { layout: 'padded' },
}

export default meta
type Story = StoryObj<typeof meta>

const STEPS: StepperStepItem[] = [
{ id: 'connect', title: 'Connect wallet', status: 'completed' },
{ id: 'approve', title: 'Approve transaction', status: 'completed' },
{ id: 'submit', title: 'Submit migration', status: 'active', description: 'Waiting for wallet confirmation.' },
{ id: 'bridge', title: 'Bridge to Celo', status: 'pending' },
{ id: 'stake', title: 'Stake on Celo', status: 'pending' },
{ id: 'confirm', title: 'Confirm receipt', status: 'pending' },
]

export const TransactionFlow: Story = {
render: () => (
<YStack width={420}>
<Stepper
steps={STEPS}
activeStepId="submit"
header={<Text fontWeight="700">Transaction steps</Text>}
maxHeight={280}
/>
</YStack>
),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import React from 'react'
import type { Meta, StoryObj } from '@storybook/react'
import { YStack } from '@goodwidget/ui'
import {
StakingMigrationWidget,
derivePrimaryAction,
derivePrimaryLabel,
type MigrationStep,
type StakingMigrationWidgetAdapterFactory,
type StakingMigrationWidgetState,
type StakingMigrationWidgetStatus,
} from '@goodwidget/staking-migration-widget'
import { createCustodialEip1193Provider } from '../../fixtures/custodialEip1193'

function createMockState(
status: StakingMigrationWidgetStatus,
overrides: {
stakedAmount?: string
stakedAmountRaw?: bigint
completedSteps?: MigrationStep[]
activeStep?: MigrationStep | null
failedStep?: MigrationStep | null
error?: string | null
hasRequiredConfig?: boolean
isWrongNetwork?: boolean
} = {},
): StakingMigrationWidgetState {
const stakedAmountRaw = overrides.stakedAmountRaw ?? 250000n
const state: StakingMigrationWidgetState = {
status,
address: '0x329377cbeeF39f01b0Ea04B80465c9eB47D3ED1',
chainId: 122,
stakedAmount: overrides.stakedAmount ?? '2500',
stakedAmountRaw,
stakedTokenSymbol: 'sG$',
hasRequiredConfig: overrides.hasRequiredConfig ?? true,
isWrongNetwork: overrides.isWrongNetwork ?? false,
isBalanceLoading: false,
completedSteps: overrides.completedSteps ?? [],
activeStep: overrides.activeStep ?? null,
failedStep: overrides.failedStep ?? null,
approvalTxHash: '0xapprovalhash',
migrationId: 'migration-1',
error: overrides.error ?? null,
primaryAction: 'none',
primaryLabel: '',
}
const primaryAction = derivePrimaryAction(state)
return {
...state,
primaryAction,
primaryLabel: derivePrimaryLabel(state, primaryAction),
}
}

function createAdapterFactory(
status: StakingMigrationWidgetStatus,
overrides: Parameters<typeof createMockState>[1] = {},
): StakingMigrationWidgetAdapterFactory {
return () => ({
state: createMockState(status, overrides),
actions: {
connect: async () => {},
switchToFuse: async () => {},
refresh: async () => {},
approveAndMigrate: async () => {},
retryMigration: async () => {},
},
})
}

function StoryShell({ adapterFactory }: { adapterFactory: StakingMigrationWidgetAdapterFactory }) {
try {
const provider = createCustodialEip1193Provider()
return (
<YStack style={{ width: 420 }}>
<StakingMigrationWidget provider={provider} adapterFactory={adapterFactory} />
</YStack>
)
} catch (error: unknown) {
return (
<YStack style={{ width: 420 }}>
{error instanceof Error ? error.message : 'Custodial fixture not configured'}
</YStack>
)
}
}

const meta: Meta<typeof StakingMigrationWidget> = {
title: 'Widgets/StakingMigrationWidget',
component: StakingMigrationWidget,
tags: ['autodocs'],
parameters: { layout: 'padded' },
}

export default meta
type Story = StoryObj<typeof meta>

export const EmptyBalance: Story = {
render: () => (
<StoryShell
adapterFactory={createAdapterFactory('summary', {
stakedAmount: '0',
stakedAmountRaw: 0n,
})}
/>
),
}

export const Ready: Story = {
render: () => <StoryShell adapterFactory={createAdapterFactory('summary')} />,
}

export const WrongNetwork: Story = {
render: () => (
<StoryShell
adapterFactory={createAdapterFactory('wrong-network', {
isWrongNetwork: true,
})}
/>
),
}

export const ApprovalPending: Story = {
render: () => <StoryShell adapterFactory={createAdapterFactory('approval-pending')} />,
}

export const Migrating: Story = {
render: () => (
<StoryShell
adapterFactory={createAdapterFactory('migrating', {
completedSteps: ['unstake', 'bridge sent'],
activeStep: 'bridge received',
})}
/>
),
}

export const Success: Story = {
render: () => (
<StoryShell
adapterFactory={createAdapterFactory('success', {
completedSteps: ['unstake', 'bridge sent', 'bridge received', 'stake'],
})}
/>
),
}

export const Error: Story = {
render: () => (
<StoryShell
adapterFactory={createAdapterFactory('error', {
completedSteps: ['unstake', 'bridge sent'],
activeStep: 'bridge received',
failedStep: 'bridge received',
error: 'Bridge finalization timeout',
})}
/>
),
}
50 changes: 50 additions & 0 deletions packages/staking-migration-widget/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@goodwidget/staking-migration-widget",
"version": "0.1.0",
"description": "Fuse staking migration widget for moving sG$ positions to Celo savings",
"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": {
"@goodwidget/core": "workspace:*",
"@goodwidget/embed": "workspace:*",
"@goodwidget/ui": "workspace:*",
"viem": "^2.0.0"
},
"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"
}
}
Loading