Skip to content
Open
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
22 changes: 22 additions & 0 deletions src/app/[locale]/@home/(home)/(bottomsheet)/[widgetRoute]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { notFound } from "next/navigation"

import { AuthenticatedWidgetRoute } from "@/widgets/AuthenticatedWidgetRoute"
import { getWidgetByRoute, WIDGETS } from "@/widgets/registry"

export const dynamicParams = false

export function generateStaticParams() {
return WIDGETS.map((widget) => ({ widgetRoute: widget.routeSlug }))
}

export default async function WidgetRoutePage({
params,
}: {
params: Promise<{ widgetRoute: string }>
}) {
const { widgetRoute } = await params
const widget = getWidgetByRoute(widgetRoute)
if (!widget) notFound()

return <AuthenticatedWidgetRoute widgetId={widget.widgetId} />
}
37 changes: 10 additions & 27 deletions src/components/Form/RoundButton/RoundButton.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,23 @@
"use client"

import type { ReactNode } from "react"
import { BsLink45Deg, BsPlusCircle } from "react-icons/bs"
import { Icon, type IconName } from "ui"

export type RoundButtonProps = {
onClick?: () => void
buttonType: RoundButtonType
icon?: IconName
iconElement?: ReactNode
text?: string
fill?: boolean
}

export const enum RoundButtonType {
Fund,
Send,
Receive,
GoodDollar,
Swap,
WalletConnect,
Predictions,
}

const buttonIcons: Record<RoundButtonType, IconName> = {
[RoundButtonType.Fund]: "Cash",
[RoundButtonType.Send]: "ArrowUpAlt",
[RoundButtonType.Receive]: "ArrowDownAlt",
[RoundButtonType.GoodDollar]: "goodDollarLogo",
[RoundButtonType.Swap]: "Swap",
[RoundButtonType.WalletConnect]: "walletConnectLogo",
[RoundButtonType.Predictions]: "Predictions",
indicator?: "connected" | "available"
}

export const RoundButton = ({
buttonType,
icon,
iconElement,
onClick,
text,
fill = false,
indicator,
}: RoundButtonProps) => {
return (
<div
Expand All @@ -43,9 +26,9 @@ export const RoundButton = ({
>
<div className="rounded-full p-px gradient-background">
<div className="flex aspect-square w-14 h-14 p-4 rounded-full justify-center items-center bg-[var(--token-bg)] btn-circle relative">
<Icon name={buttonIcons[buttonType]} size="big" color="white" />
{icon ? <Icon name={icon} size="big" color="white" /> : iconElement}

{fill && buttonType === RoundButtonType.WalletConnect ? (
{indicator === "connected" ? (
<BsLink45Deg
className="absolute bottom-0 right-0 text-white bg-[#1884FF] rounded"
stroke="white"
Expand All @@ -54,7 +37,7 @@ export const RoundButton = ({
style={{ borderRadius: "50%", padding: "3px" }}
/>
) : null}
{fill && buttonType === RoundButtonType.GoodDollar ? (
{indicator === "available" ? (
<BsPlusCircle
className="absolute bottom-0 right-0 text-white bg-[#1884FF] rounded"
stroke="white"
Expand Down
2 changes: 2 additions & 0 deletions src/login/context/SessionContext/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { proxy, ref } from "valtio"

import { getPrivateKeySession } from "@/login/adapters/privatekey"
import type { Addresses, ISigner, ISignerSession } from "@/login/types"
import { resetWalletConnectDialogs } from "@/sections/WalletConnect/store/walletConnectDialogStore"

const SIGNER_SESSION_KEY = "SIGNER_SESSION"

Expand Down Expand Up @@ -77,6 +78,7 @@ export const setSession = (session: ISignerSession | null) => {
}

export const logout = () => {
resetWalletConnectDialogs()
setSession(null)
}

Expand Down
107 changes: 42 additions & 65 deletions src/sections/Home/components/WalletSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ import { useSnapshot } from "valtio"
import { useTranslation } from "translations"
import { AnalyticsEventTypes } from "@/analytics/types"
import { useAnalytics } from "@/analytics/useAnalytics"
import {
RoundButton,
RoundButtonType,
} from "@/components/Form/RoundButton/RoundButton"
import { RoundButton } from "@/components/Form/RoundButton/RoundButton"
import { BottomSheet } from "@/components/Snippet/BottomSheet/BottomSheet"
import { useBottomSheetSnapshot } from "@/components/Snippet/BottomSheet/bottomSheetStore"
import { config } from "@/config"
Expand All @@ -31,6 +28,10 @@ import { pwaVersionStore } from "@/stores/versioningStore"
import { isDeltaMobile, isPasskeyEnabled } from "@/utils/getClientEnvironment"
import { postMessageToReactNative } from "@/utils/messageReactNative"
import { isPwa } from "@/utils/pwa"
import {
coreDashboardActions,
widgetDashboardActions,
} from "@/widgets/registry"

import { Menu } from "./Menu"
import { ProfileCard } from "./ProfileCard"
Expand Down Expand Up @@ -187,61 +188,15 @@ export default function WalletSection({
}
}, [hasMultipleActionRows])

const sendLink = (
<Link href={`/${locale}/send`} scroll={false} prefetch={true}>
<RoundButton
buttonType={RoundButtonType.Send}
text={homeTranslations.send}
/>
</Link>
)

const receiveLink = (
<Link href={`/${locale}/receive`} scroll={false} prefetch={true}>
<RoundButton
buttonType={RoundButtonType.Receive}
text={homeTranslations.receive}
/>
</Link>
)

const swapLink = (
<Link href={`/${locale}/swap`} scroll={false} prefetch={true}>
<RoundButton
buttonType={RoundButtonType.Swap}
text={homeTranslations.swap}
/>
</Link>
)

const predictionsLink = (
<Link href={`/${locale}/predictions`} scroll={false} prefetch={true}>
<RoundButton
buttonType={RoundButtonType.Predictions}
text={homeTranslations.predictions}
/>
</Link>
)

const goodDollarLink = (
<Link href={`/${locale}/gooddollar`} scroll={false} prefetch={true}>
<RoundButton
buttonType={RoundButtonType.GoodDollar}
text={homeTranslations.gooddollar}
fill={canClaim}
/>
</Link>
)

const walletConnectLink = (
<Link href={`/${locale}/walletconnect`} scroll={false} prefetch={true}>
<RoundButton
buttonType={RoundButtonType.WalletConnect}
text={homeTranslations.walletConnect}
fill={sessions.length > 0}
/>
</Link>
)
const actionLabels: Record<string, string> = {
gooddollar: homeTranslations.gooddollar,
send: homeTranslations.send,
receive: homeTranslations.receive,
swap: homeTranslations.swap,
predictions: homeTranslations.predictions,
walletconnect: homeTranslations.walletConnect,
}
const dashboardActions = [...coreDashboardActions, ...widgetDashboardActions]

return (
<>
Expand Down Expand Up @@ -273,12 +228,34 @@ export default function WalletSection({
}
data-testid="wallet-actions"
>
{goodDollarLink}
{sendLink}
{receiveLink}
{swapLink}
{predictionsLink}
{walletConnectLink}
{dashboardActions.map((action) => {
const icon =
action.icon.kind === "system" ? action.icon.name : undefined
const iconElement =
action.icon.kind === "local" ? action.icon.render() : undefined
const indicator =
action.id === "gooddollar" && canClaim
? "available"
: action.id === "walletconnect" && sessions.length > 0
? "connected"
: undefined

return (
<Link
key={action.id}
href={`/${locale}/${action.routeSlug}`}
scroll={false}
prefetch={true}
>
<RoundButton
icon={icon}
iconElement={iconElement}
text={actionLabels[action.id] ?? action.label}
indicator={indicator}
/>
</Link>
)
})}
</div>
{hasMultipleActionRows ? (
<div
Expand Down
68 changes: 68 additions & 0 deletions src/sections/WalletConnect/store/walletConnectDialogStore.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { afterEach, describe, expect, it, vi } from "vitest"

import {
openWalletConnectDialog,
resetWalletConnectDialogs,
updateWalletConnectDialogStatus,
walletConnectDialogStore,
} from "./walletConnectDialogStore"

const dialog = (title: string) => ({
type: "generic" as const,
title,
bodyText: title,
acceptBtnText: "Approve",
})

describe("openWalletConnectDialog", () => {
afterEach(() => {
resetWalletConnectDialogs()
vi.useRealTimers()
})

it("keeps concurrent approvals bound to their own dialogs", async () => {
const first = openWalletConnectDialog(dialog("First"))
const second = openWalletConnectDialog(dialog("Second"))

expect(walletConnectDialogStore.dialog).toMatchObject({ title: "First" })

walletConnectDialogStore.status = "accepted"
await expect(first).resolves.toBe("accepted")
expect(walletConnectDialogStore.dialog).toMatchObject({ title: "Second" })

walletConnectDialogStore.status = "rejected"
await expect(second).resolves.toBe("rejected")
})

it("rejects active and queued dialogs when reset", async () => {
const first = openWalletConnectDialog(dialog("First"))
const second = openWalletConnectDialog(dialog("Second"))

resetWalletConnectDialogs()

await expect(first).resolves.toBe("rejected")
await expect(second).resolves.toBe("rejected")

const next = openWalletConnectDialog(dialog("Next"))
expect(walletConnectDialogStore.dialog).toMatchObject({ title: "Next" })
walletConnectDialogStore.status = "rejected"
await expect(next).resolves.toBe("rejected")
})

it("ignores a delayed update after reset", async () => {
vi.useFakeTimers()
const first = openWalletConnectDialog(dialog("First"))
const update = updateWalletConnectDialogStatus("accepted")

resetWalletConnectDialogs()
await expect(first).resolves.toBe("rejected")

const second = openWalletConnectDialog(dialog("Second"))
await vi.advanceTimersByTimeAsync(200)
await update

expect(walletConnectDialogStore.status).toBe("pending")
resetWalletConnectDialogs()
await expect(second).resolves.toBe("rejected")
})
})
Loading
Loading