Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 11 additions & 4 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
"exclude": [
"Trans",
"Icon",
"TablerIcon"
"TablerIcon",
"code"
]
},
"jsx-attributes": {
Expand All @@ -92,6 +93,7 @@
"data-state",
"data-side",
"data-align",
"testId",
"to",
"href",
"src",
Expand Down Expand Up @@ -297,7 +299,13 @@
"^loading$",
"loading...",
"^--$",
"^≈ --$"
"^≈ --$",
"^/help$",
"^/clear$",
"^/vars$",
"^/copy$",
"^\\$[\\w{}]+$",
"^✕$"
]
}
}
Expand Down Expand Up @@ -329,8 +337,7 @@
"**/*.stories.ts",
"src/test/**",
"scripts/**",
"e2e/**",
"src/services/bioforest-sdk/bioforest-chain-bundle.*"
"e2e/**"
],
"settings": {
"react": {
Expand Down
21 changes: 14 additions & 7 deletions miniapps/forge/scripts/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ async function main() {
const updateSnapshots = args.has('--update-snapshots') || args.has('-u')

const port = await findAvailablePort(5184)
console.log(`[e2e] Using port ${port}`)
process.stdout.write(`[e2e] Using port ${port}\n`)

// Start vite dev server
const vite = spawn('pnpm', ['vite', '--port', String(port)], {
Expand All @@ -48,23 +48,30 @@ async function main() {
})

vite.stderr?.on('data', (data) => {
console.error(data.toString())
process.stderr.write(data.toString())
})

// Wait for server to be ready
const maxWait = 30000
const startTime = Date.now()
while (!serverReady && Date.now() - startTime < maxWait) {
await new Promise(r => setTimeout(r, 200))
}
await new Promise<void>((resolve) => {
const checkReady = () => {
if (serverReady || Date.now() - startTime >= maxWait) {
resolve()
return
}
setTimeout(checkReady, 200)
}
checkReady()
})

if (!serverReady) {
console.error('[e2e] Server failed to start')
process.stderr.write('[e2e] Server failed to start\n')
vite.kill()
process.exit(1)
}

console.log('[e2e] Server ready, running tests...')
process.stdout.write('[e2e] Server ready, running tests...\n')

// Run playwright
const playwrightArgs = ['playwright', 'test']
Expand Down
49 changes: 37 additions & 12 deletions miniapps/forge/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ export default function App() {

{/* Redemption Mode */}
{mode === 'redemption' && config && !configLoading && (
<RedemptionForm config={config} onSuccess={(orderId) => {}} />
<RedemptionForm config={config} onSuccess={(_orderId) => {}} />
)}

{/* Recharge Mode */}
Expand Down Expand Up @@ -429,7 +429,10 @@ export default function App() {
<Card>
<CardHeader className="pb-2">
<CardDescription>
{t('forge.pay')} ({getChainName(selectedOption.externalChain)})
{t('common.chainLabel', {
label: t('forge.pay'),
chain: getChainName(selectedOption.externalChain),
})}
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
Expand Down Expand Up @@ -477,7 +480,10 @@ export default function App() {
<Card>
<CardHeader className="pb-2">
<CardDescription>
{t('forge.receive')} ({getChainName(selectedOption.internalChain)})
{t('common.chainLabel', {
label: t('forge.receive'),
chain: getChainName(selectedOption.internalChain),
})}
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
Expand All @@ -502,7 +508,7 @@ export default function App() {
<CardContent className="space-y-2 py-3 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">{t('forge.ratio')}</span>
<span>1:1</span>
<span>{t('forge.ratioValue')}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground">{t('forge.depositAddress')}</span>
Expand Down Expand Up @@ -540,7 +546,10 @@ export default function App() {
<CardContent className="space-y-4 py-6 text-center">
<div>
<CardDescription className="mb-1">
{t('forge.pay')} ({getChainName(selectedOption.externalChain)})
{t('common.chainLabel', {
label: t('forge.pay'),
chain: getChainName(selectedOption.externalChain),
})}
</CardDescription>
<div className="flex items-center justify-center gap-2 text-3xl font-bold">
<TokenAvatar symbol={selectedOption.externalAsset} size="sm" />
Expand All @@ -556,7 +565,10 @@ export default function App() {
</div>
<div>
<CardDescription className="mb-1">
{t('forge.receive')} ({getChainName(selectedOption.internalChain)})
{t('common.chainLabel', {
label: t('forge.receive'),
chain: getChainName(selectedOption.internalChain),
})}
</CardDescription>
<div className="flex items-center justify-center gap-2 text-3xl font-bold text-blue-500">
<TokenAvatar symbol={selectedOption.internalAsset} size="sm" />
Expand All @@ -570,14 +582,14 @@ export default function App() {
<CardContent className="space-y-3 py-4 text-sm">
<div className="flex justify-between">
<span className="text-muted-foreground">{t('forge.ratio')}</span>
<span>1:1</span>
<span>{t('forge.ratioValue')}</span>
</div>
<Separator />
<div className="flex justify-between">
<span className="text-muted-foreground">{t('forge.network')}</span>
<div className="flex gap-2">
<Badge variant="outline">{getChainName(selectedOption.externalChain)}</Badge>
<span></span>
<span>{t('common.arrow')}</span>
<Badge variant="outline">{getChainName(selectedOption.internalChain)}</Badge>
</div>
</div>
Expand Down Expand Up @@ -659,7 +671,9 @@ export default function App() {
</p>
{forgeHook.orderId && (
<p className="text-muted-foreground font-mono text-xs">
{t('success.orderId')}: {forgeHook.orderId.slice(0, 16)}...
{t('success.orderIdLabel', {
id: `${forgeHook.orderId.slice(0, 16)}...`,
})}
</p>
)}
</div>
Expand All @@ -674,7 +688,12 @@ export default function App() {
{/* Token Picker Modal */}
{pickerOpen && (
<div className="fixed inset-0 z-50">
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={() => setPickerOpen(false)} />
<button
type="button"
aria-label={t('picker.close')}
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
onClick={() => setPickerOpen(false)}
/>
<div className="bg-card border-border animate-in slide-in-from-bottom absolute right-0 bottom-0 left-0 rounded-t-2xl border-t">
<div className="border-border flex items-center justify-between border-b p-4">
<CardTitle>{t('picker.title')}</CardTitle>
Expand Down Expand Up @@ -702,10 +721,16 @@ export default function App() {
<TokenAvatar symbol={option.externalAsset} size="md" />
<div className="flex-1">
<CardTitle className="text-base">
{option.externalAsset} → {option.internalAsset}
{t('picker.optionAssets', {
from: option.externalAsset,
to: option.internalAsset,
})}
</CardTitle>
<CardDescription>
{getChainName(option.externalChain)} → {getChainName(option.internalChain)}
{t('picker.optionChains', {
from: getChainName(option.externalChain),
to: getChainName(option.internalChain),
})}
</CardDescription>
</div>
{selectedOption?.externalAsset === option.externalAsset &&
Expand Down
18 changes: 13 additions & 5 deletions miniapps/forge/src/api/recharge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,28 @@ async function convertTronAddresses(item: ExternalAssetInfoItem): Promise<Extern
*/
async function transformSupportResponse(response: RechargeSupportResDto): Promise<RechargeSupportResDto> {
const recharge = { ...response.recharge }
const pending: Array<Promise<void>> = []

for (const chainName of Object.keys(recharge)) {
const assets = recharge[chainName]
for (const assetType of Object.keys(assets)) {
const item = assets[assetType]
if (item.supportChain?.TRON) {
item.supportChain = {
...item.supportChain,
TRON: await convertTronAddresses(item.supportChain.TRON),
}
const task = convertTronAddresses(item.supportChain.TRON).then((tronAddresses) => {
item.supportChain = {
...item.supportChain,
TRON: tronAddresses,
}
})
pending.push(task)
}
}
}


if (pending.length > 0) {
await Promise.all(pending)
}

return { recharge }
}

Expand Down
10 changes: 6 additions & 4 deletions miniapps/forge/src/components/FireButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import { FireButton } from './FireButton'
import { Zap } from 'lucide-react'
import { t } from 'i18next'
import '../i18n'

const meta = {
title: 'Components/FireButton',
Expand All @@ -16,7 +18,7 @@ type Story = StoryObj<typeof meta>

export const Default: Story = {
args: {
children: '连接钱包',
children: t('connect.button'),
},
}

Expand All @@ -25,22 +27,22 @@ export const WithIcon: Story = {
children: (
<>
<Zap className="size-4" />
<span>开始锻造</span>
<span>{t('forge.start')}</span>
</>
),
},
}

export const Disabled: Story = {
args: {
children: '处理中...',
children: t('processing.default'),
disabled: true,
},
}

export const Wide: Story = {
args: {
children: '确认交易',
children: t('forge.confirm'),
className: 'max-w-xs',
},
decorators: [
Expand Down
38 changes: 30 additions & 8 deletions miniapps/forge/src/components/RedemptionForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ export function RedemptionForm({ config, onSuccess }: RedemptionFormProps) {
useEffect(() => {
if (redemption.step === 'success') {
setStep('success')
onSuccess?.(redemption.orderId!)
if (redemption.orderId) {
onSuccess?.(redemption.orderId)
}
} else if (redemption.step === 'error') {
setError(redemption.error)
setStep('confirm')
Expand Down Expand Up @@ -255,7 +257,10 @@ export function RedemptionForm({ config, onSuccess }: RedemptionFormProps) {
)}
>
<button type="button" onClick={() => setSelectedOption(opt)}>
{opt.internalAsset} → {getChainName(opt.externalChain)}
{t('redemption.optionLabel', {
asset: opt.internalAsset,
chain: getChainName(opt.externalChain),
})}
</button>
</Badge>
))}
Expand All @@ -278,7 +283,10 @@ export function RedemptionForm({ config, onSuccess }: RedemptionFormProps) {
<Card>
<CardHeader className="pb-2">
<CardDescription>
{t('redemption.from')} ({getChainName(selectedOption.internalChain)})
{t('common.chainLabel', {
label: t('redemption.from'),
chain: getChainName(selectedOption.internalChain),
})}
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
Expand All @@ -300,7 +308,10 @@ export function RedemptionForm({ config, onSuccess }: RedemptionFormProps) {
</div>
{selectedOption.rechargeItem.redemption && (
<div className="text-xs text-muted-foreground">
{t('redemption.limits')}: {formatAmount(String(selectedOption.rechargeItem.redemption.min))} - {formatAmount(String(selectedOption.rechargeItem.redemption.max))}
{t('redemption.limitsRange', {
min: formatAmount(String(selectedOption.rechargeItem.redemption.min)),
max: formatAmount(String(selectedOption.rechargeItem.redemption.max)),
})}
</div>
)}
</CardContent>
Expand All @@ -319,7 +330,10 @@ export function RedemptionForm({ config, onSuccess }: RedemptionFormProps) {
<Card>
<CardHeader className="pb-2">
<CardDescription>
{t('redemption.to')} ({getChainName(selectedOption.externalChain)})
{t('common.chainLabel', {
label: t('redemption.to'),
chain: getChainName(selectedOption.externalChain),
})}
</CardDescription>
</CardHeader>
<CardContent className="space-y-3">
Expand Down Expand Up @@ -379,7 +393,10 @@ export function RedemptionForm({ config, onSuccess }: RedemptionFormProps) {
<CardContent className="py-6 text-center space-y-4">
<div>
<CardDescription className="mb-1">
{t('redemption.from')} ({getChainName(selectedOption.internalChain)})
{t('common.chainLabel', {
label: t('redemption.from'),
chain: getChainName(selectedOption.internalChain),
})}
</CardDescription>
<div className="text-3xl font-bold flex items-center justify-center gap-2">
<TokenAvatar symbol={selectedOption.internalAsset} size="sm" />
Expand All @@ -395,7 +412,10 @@ export function RedemptionForm({ config, onSuccess }: RedemptionFormProps) {
</div>
<div>
<CardDescription className="mb-1">
{t('redemption.to')} ({getChainName(selectedOption.externalChain)})
{t('common.chainLabel', {
label: t('redemption.to'),
chain: getChainName(selectedOption.externalChain),
})}
</CardDescription>
<div className="text-3xl font-bold text-blue-500 flex items-center justify-center gap-2">
<TokenAvatar symbol={selectedOption.externalAsset} size="sm" />
Expand Down Expand Up @@ -471,7 +491,9 @@ export function RedemptionForm({ config, onSuccess }: RedemptionFormProps) {
</p>
{redemption.orderId && (
<p className="text-xs text-muted-foreground font-mono">
{t('success.orderId')}: {redemption.orderId.slice(0, 16)}...
{t('success.orderIdLabel', {
id: `${redemption.orderId.slice(0, 16)}...`,
})}
</p>
)}
</div>
Expand Down
1 change: 0 additions & 1 deletion miniapps/forge/src/hooks/useForge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ export function useForge() {

setState({ step: 'success', orderId: res.orderId, error: null })
} catch (err) {
console.error('[forge] submit failed', err)
setState({
step: 'error',
orderId: null,
Expand Down
Loading