Skip to content

Commit

Permalink
FEAT: Improve UI (#47)
Browse files Browse the repository at this point in the history
# Increase toast visibility
# Omit cluster selector
# Use env variables for token addresses
# Remove the inability to interact with the wallet modal
# Improve the amount field
  • Loading branch information
rogaldh authored Apr 9, 2024
1 parent c621ec2 commit 04c761d
Show file tree
Hide file tree
Showing 19 changed files with 137 additions and 192 deletions.
3 changes: 2 additions & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ It demonstrates how to integrate the [`TokenUpgrade`](https://github.com/hoodies

Use the button down here to launch it on [Vercel](https://vercel.com).

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fhoodieshq%2Ftoken-upgrade-ui&env=NEXT_PUBLIC_TOKEN_UPGRADE_PROGRAM_ID&envDescription=Upgrade%20Program%20Address&project-name=solana-token-upgrade-app&repository-name=solana-token-upgrade-app&demo-title=Token%20Upgrade%20UI&demo-description=App%20to%20Upgrade%20Token%20on%20Solana%20Blockchain)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fhoodieshq%2Ftoken-upgrade-ui&env=NEXT_PUBLIC_CLUSTER_URL&env=NEXT_PUBLIC_ESCROW_AUTHY_ADDRESS&env=NEXT_PUBLIC_ORIGIN_TOKEN_ADDRESS&env=NEXT_PUBLIC_TARGET_TOKEN_ADDRESS&env=NEXT_PUBLIC_TOKEN_UPGRADE_PROGRAM_ID&envDescription=Upgrade%20Program%20Address%20Variables&root-directory=packages%2Fapp&project-name=solana-token-upgrade-app&repository-name=solana-token-upgrade-app&demo-title=Token%20Upgrade%20UI&demo-description=App%20to%20Upgrade%20Token%20on%20Solana%20Blockchain)

Bear in mind it will require some configuration:

- Set `NEXT_PUBLIC_TOKEN_UPGRADE_PROGRAM_ID` environment variable and provide the address of the deployed [`token-upgrade`](https://github.com/solana-labs/solana-program-library/tree/master/token-upgrade) program.
- Set all [other env variables](/packages/app/.env) to the proper values.
- The [root directory](https://vercel.com/docs/deployments/configure-a-build#root-directory) should lead to the `packages/app` directory.
- `[email protected]`

Expand Down
4 changes: 4 additions & 0 deletions packages/app/.env
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
NEXT_PUBLIC_CLUSTER_URL=
NEXT_PUBLIC_ESCROW_AUTHY_ADDRESS=
NEXT_PUBLIC_ORIGIN_TOKEN_ADDRESS=
NEXT_PUBLIC_TARGET_TOKEN_ADDRESS=
NEXT_PUBLIC_TOKEN_UPGRADE_PROGRAM_ID=
28 changes: 28 additions & 0 deletions packages/app/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
import dynamic from "next/dynamic"
import * as web3 from "@solana/web3.js"
import {
CLUSTER_URL,
ORIGIN_TOKEN_ADDRESS,
TARGET_TOKEN_ADDRESS,
ESCROW_AUTHY_ADDRESS,
TOKEN_UPGRADE_PROGRAM_ID,
} from "../env"

function guardConfiguration() {
const isValidAddr = (a?: string) => {
return Boolean(a) && a ? Boolean(new web3.PublicKey(a)) : false
}

return (
Boolean(new URL(CLUSTER_URL ?? "")) &&
isValidAddr(ORIGIN_TOKEN_ADDRESS) &&
isValidAddr(TARGET_TOKEN_ADDRESS) &&
isValidAddr(ESCROW_AUTHY_ADDRESS) &&
isValidAddr(TOKEN_UPGRADE_PROGRAM_ID)
)
}

const NoSSRIndexEntrie = dynamic(() => import("../entries/index"), {
ssr: false,
})

export default function Home() {
if (!guardConfiguration()) {
throw new Error(
"Application is not configured correctly. Please provide valid token addresses. See the project README for more information.",
)
}

return <NoSSRIndexEntrie />
}
8 changes: 8 additions & 0 deletions packages/app/src/env.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
export const CLUSTER_URL = process.env.NEXT_PUBLIC_CLUSTER_URL

export const TOKEN_UPGRADE_PROGRAM_ID =
process.env.NEXT_PUBLIC_TOKEN_UPGRADE_PROGRAM_ID

export const ORIGIN_TOKEN_ADDRESS = process.env.NEXT_PUBLIC_ORIGIN_TOKEN_ADDRESS

export const TARGET_TOKEN_ADDRESS = process.env.NEXT_PUBLIC_TARGET_TOKEN_ADDRESS

export const ESCROW_AUTHY_ADDRESS = process.env.NEXT_PUBLIC_ESCROW_AUTHY_ADDRESS
17 changes: 13 additions & 4 deletions packages/app/src/features/connect-providers.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"use client"
import "@solana/wallet-adapter-react-ui/styles.css"
import * as web3 from "@solana/web3.js"
import React from "react"
import React, { useCallback, useEffect, useState } from "react"
import { CLUSTER_URL } from "../env"
import {
ConnectionProvider,
WalletProvider,
Expand All @@ -25,11 +26,19 @@ const queryClient = new QueryClient()
export const AllProviders: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const [endpoint, setEndpoint] = React.useState(
web3.clusterApiUrl(WalletAdapterNetwork.Devnet),
const [endpoint, setEndpoint] = useState(
CLUSTER_URL ?? web3.clusterApiUrl(WalletAdapterNetwork.Devnet),
)

const onInspect = React.useCallback(({ link }: OnInspectArgs) => {
useEffect(() => {
try {
new URL(CLUSTER_URL ?? "")
} catch (err: unknown) {
console.error(`Invalid cluster URL. ${endpoint} will be used`)
}
}, [endpoint])

const onInspect = useCallback(({ link }: OnInspectArgs) => {
globalThis.window.open(link, "_blank")
}, [])

Expand Down
4 changes: 1 addition & 3 deletions packages/app/src/features/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ export const Header = forwardRef<
/>
<div className="flex items-center gap-5">
<div className="flex gap-4">{}</div>
<div className="hidden min-[320px]:contents">
<WalletMultiButton />
</div>
<WalletMultiButton />
</div>
</motion.div>
)
Expand Down
3 changes: 1 addition & 2 deletions packages/app/src/features/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ export function Layout({ children }: React.PropsWithChildren) {
<div className="h-full">
<motion.header
layoutScroll
className="contents lg:pointer-events-none lg:fixed lg:inset-0 lg:z-40 lg:flex"
className="contents lg:inset-0 lg:z-40 lg:flex"
>
<Header />
</motion.header>
<div className="relative flex h-full flex-col px-4 pt-14 sm:px-6 lg:px-8">
<main className="flex-auto">{children}</main>
{/* Footer */}
</div>
</div>
</AllProviders>
Expand Down
58 changes: 0 additions & 58 deletions packages/app/src/shared/input.tsx

This file was deleted.

96 changes: 10 additions & 86 deletions packages/app/src/widgets/index-page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import * as web3 from "@solana/web3.js"
import ChangeCluster from "../features/change-cluster"
import Input from "../shared/input"
import {
ESCROW_AUTHY_ADDRESS,
ORIGIN_TOKEN_ADDRESS,
TARGET_TOKEN_ADDRESS,
TOKEN_UPGRADE_PROGRAM_ID,
} from "../env"
import { Pattern } from "../shared/pattern"
import { TOKEN_UPGRADE_PROGRAM_ID } from "../env"
import { TokenUpgrade, useNotificationContext } from "@solana/token-upgrade-ui"
import { useCallback, useEffect, useState } from "react"
import { useCallback } from "react"
import { useConnection } from "@solana/wallet-adapter-react"

function getCluster(rpc: string) {
Expand All @@ -17,16 +20,6 @@ function getCluster(rpc: string) {
return getMoniker(rpc)
}

function setPublicKey(publicKey: string) {
let pk
try {
pk = new web3.PublicKey(publicKey)
} catch (e: unknown) {
return null
}
return pk
}

export default function IndexPage() {
const { connection } = useConnection()
const { setNotification } = useNotificationContext()
Expand All @@ -39,28 +32,13 @@ export default function IndexPage() {
[setNotification],
)

const [token, setToken] = useState<web3.PublicKey>()
const [tokenExt, setTokenExt] = useState<web3.PublicKey>()
const [escrow, setEscrow] = useState<web3.PublicKey>()

useEffect(() => {
const url = new URLSearchParams(globalThis.location.search)

const t = url.get("token")
const te = url.get("tokenExt")
const e = url.get("escrow")
if (t) setToken(new web3.PublicKey(t))
if (te) setTokenExt(new web3.PublicKey(te))
if (e) setEscrow(new web3.PublicKey(e))
}, [])

return (
<>
<Pattern />
<div className="prose flex justify-center py-2 dark:prose-invert">
<div className="container max-w-[440px]">
<TokenUpgrade
escrow={escrow?.toString()}
escrow={ESCROW_AUTHY_ADDRESS}
onUpgradeStart={() => _log("Upgrading token")}
onUpgradeEnd={({ signature }) =>
setNotification({
Expand All @@ -71,66 +49,12 @@ export default function IndexPage() {
onUpgradeError={(error) =>
_log(`Error: ${error.message || error.name}`)
}
tokenAddress={token?.toString()}
tokenExtAddress={tokenExt?.toString()}
tokenAddress={ORIGIN_TOKEN_ADDRESS}
tokenExtAddress={TARGET_TOKEN_ADDRESS}
tokenUpgradeProgramId={TOKEN_UPGRADE_PROGRAM_ID}
/>
</div>
</div>

<div className="light:text-black dark:text-white">
<div className="container flex flex-col items-center justify-center py-2">
<div className="min-w-80 pb-1.5 pt-2.5">
<Input
defaultValue={token?.toString()}
name="tokenAddress"
label="Token address"
onChange={(e) => {
const pk = setPublicKey(e.target.value.trim())
if (pk === null) {
setNotification({ message: "Invalid address" })
} else {
setToken(pk)
}
}}
placeholder="Paste here token address to update"
/>
</div>
<div className="min-w-80 pb-1.5 pt-2.5">
<Input
defaultValue={tokenExt?.toString()}
name="token2022Address"
label="Token Extension address"
onChange={(e) => {
const pk = setPublicKey(e.target.value.trim())
if (pk === null) {
setNotification({ message: "Invalid address" })
} else {
setTokenExt(pk)
}
}}
placeholder="Paste here token address to update"
/>
</div>
<div className="min-w-80 pb-1.5 pt-2.5">
<Input
defaultValue={escrow?.toString()}
name="escrow"
label="Escrow address"
onChange={(e) => {
const pk = setPublicKey(e.target.value.trim())
if (pk === null) {
setNotification({ message: "Invalid address" })
} else {
setEscrow(pk)
}
}}
placeholder="Paste here token address to update"
/>
</div>
<ChangeCluster className="min-w-80 pb-1.5 pt-2.5" />
</div>
</div>
</>
)
}
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"lint": "pnpm run lint-pret && pnpm run lint-es",
"lint-es": "eslint ./src",
"lint-fix": "pnpm run lint-pret --w",
"lint-pret": "prettier ./src/** --check",
"lint-pret": "prettier ./src/** ./tests/** --check",
"local:test-e2e": "anchor test",
"local:sb": "pnpm run storybook --no-open --quiet --no-version-updates",
"local:sb-kill": "kill -2 $(lsof -t -i:6006)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const WithAddress: Story = {
const ctx = within(canvasElement)

await step("should show symbol", async () => {
const el = ctx.getByText("Ho..Y")
const el = ctx.getByText("HoK..jqY")
await expect(el).toBeVisible()
})
},
Expand Down
13 changes: 4 additions & 9 deletions packages/ui/src/__stories__/widgets/token-upgrade.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
WalletProvider,
} from "@solana/wallet-adapter-react"
import { clusterApiUrl } from "@solana/web3.js"
import { expect, fn, fireEvent, userEvent, within } from "@storybook/test"
import { expect, fn, within } from "@storybook/test"
import { PhantomWalletAdapter } from "@solana/wallet-adapter-wallets"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { TokenUpgrade } from "../../widgets/token-upgrade"
Expand Down Expand Up @@ -64,11 +64,7 @@ export const Default: StoryObj<ComponentPropsWithTestId<typeof TokenUpgrade>> =

await step("should toggle destination field", async () => {
const cbx = ctx.getByLabelText("toggle-destination")
await expect(cbx).not.toBeDisabled()
await userEvent.click(cbx)
await expect(
ctx.getByRole("textbox", { description: "Destination" }),
).toBeVisible()
await expect(cbx).toBeDisabled()
})
},
}
Expand All @@ -82,11 +78,10 @@ export const WithTokenAddress: StoryObj<
async play({ canvasElement, step }: any) {
const ctx = within(canvasElement)

await step("should allow change amount", async () => {
await step("should not allow to change amount", async () => {
const input = ctx.getByRole("spinbutton", { description: "Amount" })

await expect(input).not.toBeDisabled()
await fireEvent.change(input, { target: { value: "1" } })
await expect(input).toBeDisabled()
await expect(ctx.getByLabelText("Select Wallet")).not.toBeDisabled()
})
},
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/features/notification/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function NotificationProvider({
<NotificationContext.Provider
value={{ message: data?.message, link: data?.link, setNotification }}
>
<Toast.Provider swipeDirection={swipeDirection}>
<Toast.Provider duration={12000} swipeDirection={swipeDirection}>
<div
aria-live="assertive"
className="pointer-events-none fixed inset-0 z-50 flex items-end px-4 py-6 sm:items-start sm:p-6"
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/features/notification/toast.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { Fragment } from "react"
import { Transition, TransitionChildProps } from "@headlessui/react"
import { Transition } from "@headlessui/react"

interface ToastProps {
link?: string
Expand Down
Loading

0 comments on commit 04c761d

Please sign in to comment.