Fix wallet modal, add Lute wallet, dark mode, add comments
This commit is contained in:
@ -1,9 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>AlgoKit React Template</title>
|
<title>Tokenization Template</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
"@txnlab/use-wallet-react": "^4.0.0",
|
"@txnlab/use-wallet-react": "^4.0.0",
|
||||||
"algosdk": "^3.0.0",
|
"algosdk": "^3.0.0",
|
||||||
"daisyui": "^4.0.0",
|
"daisyui": "^4.0.0",
|
||||||
|
"lute-connect": "^1.6.3",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
@ -8785,6 +8786,12 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lute-connect": {
|
||||||
|
"version": "1.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lute-connect/-/lute-connect-1.6.3.tgz",
|
||||||
|
"integrity": "sha512-QSBHj1fG9QhkgzezcRrG1piYCxTt0Tlf13ZOTLuXsJfagEC+IERKBKORo0CgzBMOlo47nr4w8n19vcFpfCvJNQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
|
|||||||
@ -42,6 +42,7 @@
|
|||||||
"@txnlab/use-wallet-react": "^4.0.0",
|
"@txnlab/use-wallet-react": "^4.0.0",
|
||||||
"algosdk": "^3.0.0",
|
"algosdk": "^3.0.0",
|
||||||
"daisyui": "^4.0.0",
|
"daisyui": "^4.0.0",
|
||||||
|
"lute-connect": "^1.6.3",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
import { SupportedWallet, WalletId, WalletManager, WalletProvider } from '@txnlab/use-wallet-react'
|
import { SupportedWallet, WalletId, WalletManager, WalletProvider } from '@txnlab/use-wallet-react'
|
||||||
import { SnackbarProvider } from 'notistack'
|
import { SnackbarProvider } from 'notistack'
|
||||||
import { BrowserRouter, Routes, Route } from 'react-router-dom'
|
import { BrowserRouter, Route, Routes } from 'react-router-dom'
|
||||||
import Home from './Home'
|
import Home from './Home'
|
||||||
import Layout from './Layout'
|
import Layout from './Layout'
|
||||||
import TokenizePage from './TokenizePage'
|
import TokenizePage from './TokenizePage'
|
||||||
import { getAlgodConfigFromViteEnvironment, getKmdConfigFromViteEnvironment } from './utils/network/getAlgoClientConfigs'
|
import { getAlgodConfigFromViteEnvironment, getKmdConfigFromViteEnvironment } from './utils/network/getAlgoClientConfigs'
|
||||||
|
|
||||||
|
// Configure supported wallets based on network environment
|
||||||
let supportedWallets: SupportedWallet[]
|
let supportedWallets: SupportedWallet[]
|
||||||
if (import.meta.env.VITE_ALGOD_NETWORK === 'localnet') {
|
if (import.meta.env.VITE_ALGOD_NETWORK === 'localnet') {
|
||||||
|
// LocalNet: KMD wallet for local development
|
||||||
const kmdConfig = getKmdConfigFromViteEnvironment()
|
const kmdConfig = getKmdConfigFromViteEnvironment()
|
||||||
supportedWallets = [
|
supportedWallets = [
|
||||||
{
|
{
|
||||||
@ -18,11 +20,17 @@ if (import.meta.env.VITE_ALGOD_NETWORK === 'localnet') {
|
|||||||
port: String(kmdConfig.port),
|
port: String(kmdConfig.port),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{ id: WalletId.LUTE },
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
supportedWallets = [{ id: WalletId.DEFLY }, { id: WalletId.PERA }, { id: WalletId.EXODUS }]
|
// TestNet/MainNet: Browser extension wallets (Pera, Defly, Exodus, Lute)
|
||||||
|
supportedWallets = [{ id: WalletId.DEFLY }, { id: WalletId.PERA }, { id: WalletId.EXODUS }, { id: WalletId.LUTE }]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main App Component
|
||||||
|
* Sets up wallet provider and routing for the Tokenization dApp
|
||||||
|
*/
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const algodConfig = getAlgodConfigFromViteEnvironment()
|
const algodConfig = getAlgodConfigFromViteEnvironment()
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import { useWallet } from '@txnlab/use-wallet-react'
|
import { useWallet } from '@txnlab/use-wallet-react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Home Page
|
||||||
|
* Landing page showcasing the RWA tokenization platform
|
||||||
|
* Displays features, how it works, and CTAs to connect wallet and create assets
|
||||||
|
*/
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const { activeAddress } = useWallet()
|
const { activeAddress } = useWallet()
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,10 @@ import { NavLink, Outlet } from 'react-router-dom'
|
|||||||
import ConnectWallet from './components/ConnectWallet'
|
import ConnectWallet from './components/ConnectWallet'
|
||||||
import ThemeToggle from './components/ThemeToggle'
|
import ThemeToggle from './components/ThemeToggle'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main Layout Component
|
||||||
|
* Wraps the entire app with navigation, footer, and wallet connection modal
|
||||||
|
*/
|
||||||
export default function Layout() {
|
export default function Layout() {
|
||||||
const [openWalletModal, setOpenWalletModal] = useState(false)
|
const [openWalletModal, setOpenWalletModal] = useState(false)
|
||||||
const { activeAddress } = useWallet()
|
const { activeAddress } = useWallet()
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import TokenizeAsset from './components/TokenizeAsset'
|
import TokenizeAsset from './components/TokenizeAsset'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokenize Page
|
||||||
|
* Main page for creating new Algorand Standard Assets (ASAs)
|
||||||
|
*/
|
||||||
export default function TokenizePage() {
|
export default function TokenizePage() {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white dark:bg-slate-950 min-h-screen py-12">
|
<div className="bg-white dark:bg-slate-950 min-h-screen py-12">
|
||||||
|
|||||||
@ -3,6 +3,11 @@ import { useMemo } from 'react'
|
|||||||
import { ellipseAddress } from '../utils/ellipseAddress'
|
import { ellipseAddress } from '../utils/ellipseAddress'
|
||||||
import { getAlgodConfigFromViteEnvironment } from '../utils/network/getAlgoClientConfigs'
|
import { getAlgodConfigFromViteEnvironment } from '../utils/network/getAlgoClientConfigs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Account Component
|
||||||
|
* Displays the connected wallet address (shortened) and current network
|
||||||
|
* Address links to Lora explorer for easy account tracking
|
||||||
|
*/
|
||||||
const Account = () => {
|
const Account = () => {
|
||||||
const { activeAddress } = useWallet()
|
const { activeAddress } = useWallet()
|
||||||
const algoConfig = getAlgodConfigFromViteEnvironment()
|
const algoConfig = getAlgodConfigFromViteEnvironment()
|
||||||
@ -13,10 +18,14 @@ const Account = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<a className="text-xl" target="_blank" href={`https://lora.algokit.io/${networkName}/account/${activeAddress}/`}>
|
<a
|
||||||
|
className="text-xl text-gray-900 dark:text-slate-100 hover:text-teal-600 dark:hover:text-teal-400 transition"
|
||||||
|
target="_blank"
|
||||||
|
href={`https://lora.algokit.io/${networkName}/account/${activeAddress}/`}
|
||||||
|
>
|
||||||
Address: {ellipseAddress(activeAddress)}
|
Address: {ellipseAddress(activeAddress)}
|
||||||
</a>
|
</a>
|
||||||
<div className="text-xl">Network: {networkName}</div>
|
<div className="text-xl text-gray-900 dark:text-slate-100 mt-2">Network: {networkName}</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useWallet, Wallet, WalletId } from '@txnlab/use-wallet-react'
|
import { useWallet, Wallet, WalletId } from '@txnlab/use-wallet-react'
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
import Account from './Account'
|
import Account from './Account'
|
||||||
|
|
||||||
interface ConnectWalletInterface {
|
interface ConnectWalletInterface {
|
||||||
@ -6,21 +7,87 @@ interface ConnectWalletInterface {
|
|||||||
closeModal: () => void
|
closeModal: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConnectWallet Modal Component
|
||||||
|
* Displays wallet connection options (Pera, Defly, Lute, KMD for LocalNet)
|
||||||
|
* Also shows connected wallet details and network information when logged in
|
||||||
|
*/
|
||||||
const ConnectWallet = ({ openModal, closeModal }: ConnectWalletInterface) => {
|
const ConnectWallet = ({ openModal, closeModal }: ConnectWalletInterface) => {
|
||||||
const { wallets, activeAddress } = useWallet()
|
const { wallets, activeAddress } = useWallet()
|
||||||
|
const dialogRef = useRef<HTMLDialogElement>(null)
|
||||||
|
|
||||||
|
// Manage native dialog element's open/close state
|
||||||
|
useEffect(() => {
|
||||||
|
const dialog = dialogRef.current
|
||||||
|
if (!dialog) return
|
||||||
|
|
||||||
|
if (openModal) {
|
||||||
|
dialog.showModal()
|
||||||
|
} else {
|
||||||
|
dialog.close()
|
||||||
|
}
|
||||||
|
}, [openModal])
|
||||||
|
|
||||||
|
const getActiveWallet = () => {
|
||||||
|
if (!wallets) return null
|
||||||
|
return wallets.find((w) => w.isActive)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getWalletDisplayName = (wallet: Wallet) => {
|
||||||
|
if (wallet.id === WalletId.KMD) return 'LocalNet Wallet'
|
||||||
|
return wallet.metadata.name
|
||||||
|
}
|
||||||
|
|
||||||
const isKmd = (wallet: Wallet) => wallet.id === WalletId.KMD
|
const isKmd = (wallet: Wallet) => wallet.id === WalletId.KMD
|
||||||
|
|
||||||
return (
|
const activeWallet = getActiveWallet()
|
||||||
<dialog id="connect_wallet_modal" className={`modal ${openModal ? 'modal-open' : ''}`}>
|
|
||||||
<form method="dialog" className="modal-box">
|
|
||||||
<h3 className="font-bold text-2xl">Select wallet provider</h3>
|
|
||||||
|
|
||||||
<div className="grid m-2 pt-5">
|
return (
|
||||||
|
<dialog
|
||||||
|
ref={dialogRef}
|
||||||
|
id="connect_wallet_modal"
|
||||||
|
className="fixed inset-0 w-full max-w-lg mx-auto my-auto rounded-2xl bg-white dark:bg-slate-800 shadow-2xl border border-gray-200 dark:border-slate-700 overflow-hidden"
|
||||||
|
onClick={(e) => {
|
||||||
|
// Close when clicking the backdrop
|
||||||
|
if (e.target === dialogRef.current) {
|
||||||
|
closeModal()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="p-6 sm:p-7">
|
||||||
|
<div className="flex items-center justify-between mb-5">
|
||||||
|
<h3 className="text-xl font-semibold text-gray-900 dark:text-slate-100">Select wallet provider</h3>
|
||||||
|
<button
|
||||||
|
className="text-gray-400 dark:text-slate-500 hover:text-gray-600 dark:hover:text-slate-300 transition text-sm"
|
||||||
|
onClick={closeModal}
|
||||||
|
aria-label="Close wallet modal"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-sm text-gray-500 dark:text-slate-400 mb-4">
|
||||||
|
Choose the wallet you want to connect. Supported: Pera, Defly, LocalNet (KMD), and others.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
{activeAddress && (
|
{activeAddress && (
|
||||||
<>
|
<>
|
||||||
<Account />
|
<div className="rounded-xl border border-gray-100 dark:border-slate-700 bg-gray-50 dark:bg-slate-700 p-4">
|
||||||
<div className="divider" />
|
<Account />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Wallet Info */}
|
||||||
|
<div className="space-y-3 bg-white dark:bg-slate-700 border border-gray-200 dark:border-slate-600 rounded-xl p-4">
|
||||||
|
{activeWallet && (
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-gray-500 dark:text-slate-400 font-medium mb-1">Connected Wallet</p>
|
||||||
|
<p className="text-sm font-semibold text-gray-900 dark:text-slate-100">{getWalletDisplayName(activeWallet)}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="h-px bg-gray-200 dark:bg-slate-600" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -28,47 +95,54 @@ const ConnectWallet = ({ openModal, closeModal }: ConnectWalletInterface) => {
|
|||||||
wallets?.map((wallet) => (
|
wallets?.map((wallet) => (
|
||||||
<button
|
<button
|
||||||
data-test-id={`${wallet.id}-connect`}
|
data-test-id={`${wallet.id}-connect`}
|
||||||
className="btn border-teal-800 border-1 m-2"
|
className={`
|
||||||
|
w-full flex items-center gap-4 px-4 py-3 rounded-xl bg-white dark:bg-slate-700 border border-gray-200 dark:border-slate-600
|
||||||
|
hover:border-indigo-200 dark:hover:border-indigo-500 hover:bg-indigo-50/50 dark:hover:bg-slate-600 transition
|
||||||
|
focus:outline-none focus:ring-2 focus:ring-indigo-200 dark:focus:ring-indigo-500
|
||||||
|
`}
|
||||||
key={`provider-${wallet.id}`}
|
key={`provider-${wallet.id}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
return wallet.connect()
|
// Close modal before initiating wallet connection
|
||||||
|
closeModal()
|
||||||
|
wallet.connect()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!isKmd(wallet) && (
|
{!isKmd(wallet) && (
|
||||||
<img
|
<img
|
||||||
alt={`wallet_icon_${wallet.id}`}
|
alt={`wallet_icon_${wallet.id}`}
|
||||||
src={wallet.metadata.icon}
|
src={wallet.metadata.icon}
|
||||||
style={{ objectFit: 'contain', width: '30px', height: 'auto' }}
|
className="w-9 h-9 object-contain rounded-md border border-gray-100 dark:border-slate-600 bg-white dark:bg-slate-700"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span>{isKmd(wallet) ? 'LocalNet Wallet' : wallet.metadata.name}</span>
|
<span className="font-medium text-sm text-left flex-1 text-gray-900 dark:text-slate-100">
|
||||||
|
{isKmd(wallet) ? 'LocalNet Wallet' : wallet.metadata.name}
|
||||||
|
</span>
|
||||||
|
{wallet.isActive && <span className="text-sm text-emerald-500">✓</span>}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="modal-action ">
|
<div className="mt-6 flex gap-3">
|
||||||
<button
|
<button
|
||||||
data-test-id="close-wallet-modal"
|
data-test-id="close-wallet-modal"
|
||||||
className="btn"
|
className="w-full sm:w-auto px-4 py-2.5 rounded-lg border border-gray-200 dark:border-slate-600 bg-gray-50 dark:bg-slate-700 text-gray-700 dark:text-slate-300 text-sm hover:bg-gray-100 dark:hover:bg-slate-600 transition"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
closeModal()
|
closeModal()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{activeAddress && (
|
{activeAddress && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-warning"
|
className="w-full sm:w-auto px-4 py-2.5 rounded-lg bg-red-500 hover:bg-red-600 dark:bg-red-600 dark:hover:bg-red-700 text-white text-sm transition"
|
||||||
data-test-id="logout"
|
data-test-id="logout"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (wallets) {
|
if (wallets) {
|
||||||
const activeWallet = wallets.find((w) => w.isActive)
|
const wallet = wallets.find((w) => w.isActive)
|
||||||
if (activeWallet) {
|
if (wallet) {
|
||||||
await activeWallet.disconnect()
|
await wallet.disconnect()
|
||||||
} else {
|
} else {
|
||||||
// Required for logout/cleanup of inactive providers
|
|
||||||
// For instance, when you login to localnet wallet and switch network
|
|
||||||
// to testnet/mainnet or vice verse.
|
|
||||||
localStorage.removeItem('@txnlab/use-wallet:v3')
|
localStorage.removeItem('@txnlab/use-wallet:v3')
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
}
|
||||||
@ -79,7 +153,7 @@ const ConnectWallet = ({ openModal, closeModal }: ConnectWalletInterface) => {
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</dialog>
|
</dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,9 @@ import { useEffect, useState } from 'react'
|
|||||||
const THEME_KEY = 'tokenize_theme'
|
const THEME_KEY = 'tokenize_theme'
|
||||||
type Theme = 'light' | 'dark'
|
type Theme = 'light' | 'dark'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get initial theme preference from localStorage or system preference
|
||||||
|
*/
|
||||||
function getInitialTheme(): Theme {
|
function getInitialTheme(): Theme {
|
||||||
const saved = localStorage.getItem(THEME_KEY)
|
const saved = localStorage.getItem(THEME_KEY)
|
||||||
if (saved === 'light' || saved === 'dark') return saved
|
if (saved === 'light' || saved === 'dark') return saved
|
||||||
@ -10,6 +13,11 @@ function getInitialTheme(): Theme {
|
|||||||
return prefersDark ? 'dark' : 'light'
|
return prefersDark ? 'dark' : 'light'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ThemeToggle Component
|
||||||
|
* Allows users to toggle between light and dark modes
|
||||||
|
* Persists theme preference to localStorage and applies Tailwind's dark class
|
||||||
|
*/
|
||||||
export default function ThemeToggle() {
|
export default function ThemeToggle() {
|
||||||
const [theme, setTheme] = useState<Theme>(() => getInitialTheme())
|
const [theme, setTheme] = useState<Theme>(() => getInitialTheme())
|
||||||
const [mounted, setMounted] = useState(false)
|
const [mounted, setMounted] = useState(false)
|
||||||
|
|||||||
@ -6,6 +6,10 @@ import { AiOutlineInfoCircle, AiOutlineLoading3Quarters } from 'react-icons/ai'
|
|||||||
import { BsCoin } from 'react-icons/bs'
|
import { BsCoin } from 'react-icons/bs'
|
||||||
import { getAlgodConfigFromViteEnvironment } from '../utils/network/getAlgoClientConfigs'
|
import { getAlgodConfigFromViteEnvironment } from '../utils/network/getAlgoClientConfigs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type for created assets stored in browser localStorage
|
||||||
|
* Captures all ASA configuration including compliance fields
|
||||||
|
*/
|
||||||
type CreatedAsset = {
|
type CreatedAsset = {
|
||||||
assetId: number
|
assetId: number
|
||||||
assetName: string
|
assetName: string
|
||||||
@ -23,6 +27,9 @@ type CreatedAsset = {
|
|||||||
const STORAGE_KEY = 'tokenize_assets'
|
const STORAGE_KEY = 'tokenize_assets'
|
||||||
const LORA_BASE = 'https://lora.algokit.io/testnet'
|
const LORA_BASE = 'https://lora.algokit.io/testnet'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load created assets from browser localStorage
|
||||||
|
*/
|
||||||
function loadAssets(): CreatedAsset[] {
|
function loadAssets(): CreatedAsset[] {
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(STORAGE_KEY)
|
const raw = localStorage.getItem(STORAGE_KEY)
|
||||||
@ -32,6 +39,10 @@ function loadAssets(): CreatedAsset[] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a newly created asset to localStorage
|
||||||
|
* Returns updated asset list with new asset at the top
|
||||||
|
*/
|
||||||
function persistAsset(asset: CreatedAsset): CreatedAsset[] {
|
function persistAsset(asset: CreatedAsset): CreatedAsset[] {
|
||||||
const existing = loadAssets()
|
const existing = loadAssets()
|
||||||
const next = [asset, ...existing]
|
const next = [asset, ...existing]
|
||||||
@ -39,6 +50,12 @@ function persistAsset(asset: CreatedAsset): CreatedAsset[] {
|
|||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TokenizeAsset Component
|
||||||
|
* Main form for creating Algorand Standard Assets (ASAs)
|
||||||
|
* Collects basic and advanced compliance metadata
|
||||||
|
* Persists created assets to localStorage for tracking
|
||||||
|
*/
|
||||||
export default function TokenizeAsset() {
|
export default function TokenizeAsset() {
|
||||||
const [assetName, setAssetName] = useState<string>('Tokenized Coffee Membership')
|
const [assetName, setAssetName] = useState<string>('Tokenized Coffee Membership')
|
||||||
const [unitName, setUnitName] = useState<string>('COFFEE')
|
const [unitName, setUnitName] = useState<string>('COFFEE')
|
||||||
@ -84,6 +101,10 @@ export default function TokenizeAsset() {
|
|||||||
|
|
||||||
const isWholeNumber = (v: string) => /^\d+$/.test(v)
|
const isWholeNumber = (v: string) => /^\d+$/.test(v)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle ASA creation with validation and on-chain transaction
|
||||||
|
* Adjusts total supply by decimals and saves asset to localStorage
|
||||||
|
*/
|
||||||
const handleTokenize = async () => {
|
const handleTokenize = async () => {
|
||||||
if (!transactionSigner || !activeAddress) {
|
if (!transactionSigner || !activeAddress) {
|
||||||
enqueueSnackbar('Please connect your wallet first.', { variant: 'warning' })
|
enqueueSnackbar('Please connect your wallet first.', { variant: 'warning' })
|
||||||
|
|||||||
Reference in New Issue
Block a user