Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEAT: Improve UI #47

Merged
merged 17 commits into from
Apr 9, 2024
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
Loading