Build Components
Now let’s build the UI components that use our hooks.1. USDC Section
Createsrc/components/usdc-section.tsx:
Copy
import { useCurrentAccount } from "@mysten/dapp-kit-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { useUsdcBalance } from "@/hooks/use-usdc-balance";
import { useMintUsdc } from "@/hooks/use-mint-usdc";
import { formatUSDC, USDC_MINT_AMOUNT } from "@/lib/contracts";
import { Loader2, Plus } from "lucide-react";
export function UsdcSection() {
const account = useCurrentAccount();
const { data } = useUsdcBalance();
const { mint, isPending } = useMintUsdc();
if (!account) return null;
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<img
src="https://cryptologos.cc/logos/usd-coin-usdc-logo.png"
alt="USDC"
className="w-5 h-5"
/>
USDC
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="flex justify-between items-center p-3 bg-muted rounded-lg">
<span className="text-sm text-muted-foreground">Balance</span>
<span className="text-lg font-bold">
{formatUSDC(data?.balance ?? 0n)}
</span>
</div>
<Button onClick={mint} disabled={isPending} className="w-full">
{isPending ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<>
<Plus className="w-4 h-4 mr-1" />
Mint {USDC_MINT_AMOUNT}
</>
)}
</Button>
</CardContent>
</Card>
);
}
2. NFT Mint Section
Createsrc/components/nft-mint-section.tsx:
Copy
import { useCurrentAccount } from "@mysten/dapp-kit-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { useUsdcBalance } from "@/hooks/use-usdc-balance";
import { useMintNft } from "@/hooks/use-mint-nft";
import { formatUSDC, NFT_MINT_PRICE } from "@/lib/contracts";
import { Sparkles, Loader2 } from "lucide-react";
export function NftMintSection() {
const account = useCurrentAccount();
const { data: usdcData } = useUsdcBalance();
const { mint, isPending } = useMintNft();
if (!account) return null;
const hasEnoughBalance = (usdcData?.balance ?? 0n) >= NFT_MINT_PRICE;
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Sparkles className="w-4 h-4" />
Mint NFT
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<div className="relative rounded-lg overflow-hidden">
<img
src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQh1cpBR8d2D2P55Z2kQzlr6uGuhRfWzEnoVQ&s"
alt="PSIL NFT"
className="w-full aspect-square object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
<div className="absolute bottom-0 p-3 text-white">
<p className="font-bold">Pria Solo Itu Lagi</p>
<p className="text-xs opacity-80">SAYA AKAN PULANG KE SOLO</p>
</div>
</div>
<div className="flex justify-between items-center p-3 bg-muted rounded-lg">
<span className="text-sm text-muted-foreground">Price</span>
<span className="font-bold">{formatUSDC(NFT_MINT_PRICE)} USDC</span>
</div>
{!hasEnoughBalance && (
<p className="text-sm text-destructive">Insufficient USDC balance</p>
)}
<Button
onClick={mint}
disabled={isPending || !hasEnoughBalance}
className="w-full"
>
{isPending ? (
<>
<Loader2 className="w-4 h-4 mr-1 animate-spin" />
Minting...
</>
) : (
<>
<Sparkles className="w-4 h-4 mr-1" />
Mint
</>
)}
</Button>
</CardContent>
</Card>
);
}
3. NFT Gallery
Createsrc/components/nft-gallery.tsx:
Copy
import { useCurrentAccount } from "@mysten/dapp-kit-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { useOwnedNfts } from "@/hooks/use-owned-nfts";
import { ImageIcon } from "lucide-react";
export function NftGallery() {
const account = useCurrentAccount();
const { data: nfts } = useOwnedNfts();
if (!account) return null;
const count = nfts?.length ?? 0;
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<ImageIcon className="w-4 h-4" />
Your NFTs
{count > 0 && (
<span className="ml-auto text-sm font-normal text-muted-foreground">
{count}
</span>
)}
</CardTitle>
</CardHeader>
<CardContent>
{count > 0 ? (
<div className="grid grid-cols-2 gap-2">
{nfts!.map((nft) => (
<div
key={nft.id}
className="relative rounded-lg overflow-hidden border"
>
<img
src={nft.url}
alt={nft.name}
className="w-full aspect-square object-cover"
/>
<span className="absolute top-1 right-1 bg-black/60 text-white text-xs px-1.5 py-0.5 rounded">
#{nft.edition}
</span>
</div>
))}
</div>
) : (
<p className="text-center py-6 text-sm text-muted-foreground">
No NFTs yet
</p>
)}
</CardContent>
</Card>
);
}
4. Update App
Updatesrc/app.tsx to use all components:
Copy
import { ConnectButton, useCurrentAccount } from "@mysten/dapp-kit-react";
import { UsdcSection } from "./components/usdc-section";
import { NftMintSection } from "./components/nft-mint-section";
import { NftGallery } from "./components/nft-gallery";
export function App() {
const account = useCurrentAccount();
return (
<div className="min-h-screen bg-background bg-grid">
<header className="sticky top-0 z-50 border-b bg-background/80 backdrop-blur">
<div className="max-w-4xl mx-auto px-4 py-3 flex items-center justify-between">
<h1 className="font-bold">Sui Workshop</h1>
<ConnectButton />
</div>
</header>
<main className="max-w-4xl mx-auto px-4 py-6">
{account ? (
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-4">
<UsdcSection />
<NftGallery />
</div>
<NftMintSection />
</div>
) : (
<div className="text-center py-20">
<h2 className="text-xl font-bold mb-2">Welcome</h2>
<p className="text-muted-foreground mb-4">Connect wallet to start</p>
<ConnectButton />
</div>
)}
</main>
</div>
);
}
Install Lucide Icons
Copy
npm install lucide-react
Final Project Structure
Copy
src/
├── app.tsx
├── main.tsx
├── index.css
├── components/
│ ├── providers.tsx
│ ├── usdc-section.tsx
│ ├── nft-mint-section.tsx
│ ├── nft-gallery.tsx
│ └── ui/
├── hooks/
│ ├── use-usdc-balance.ts
│ ├── use-mint-usdc.ts
│ ├── use-owned-nfts.ts
│ └── use-mint-nft.ts
└── lib/
├── contracts.ts
├── dapp-kit.ts
└── utils.ts
Test the App
- Run
npm run dev - Connect your wallet
- Mint some USDC tokens
- Mint an NFT with USDC
- See your NFT in the gallery