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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ node_modules/

# npm
package-lock.json

# Vite
dist
*.local

lib
6 changes: 6 additions & 0 deletions week-04/dev/my-dapp/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Counter 컨트랙트 배포 주소
# Anvil에서 배포 후 주소를 .env.local에 설정하세요
VITE_COUNTER_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3

# WalletConnect Project ID (https://cloud.walletconnect.com 에서 발급)
VITE_WALLETCONNECT_PROJECT_ID=YOUR_PROJECT_ID
Binary file added week-04/dev/my-dapp/assets/1-init.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added week-04/dev/my-dapp/assets/2-connect.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added week-04/dev/my-dapp/assets/3-increment.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added week-04/dev/my-dapp/assets/4-decrement.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions week-04/dev/my-dapp/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Counter DApp</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
26 changes: 26 additions & 0 deletions week-04/dev/my-dapp/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "counter-dapp",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"@rainbow-me/rainbowkit": "^2.2.10",
"@tanstack/react-query": "^5.62.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"viem": "^2.21.0",
"wagmi": "^2.14.0"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
"typescript": "~5.6.2",
"vite": "^6.0.0"
}
}
153 changes: 153 additions & 0 deletions week-04/dev/my-dapp/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #1a1a2e;
color: #e0e0e0;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}

.container {
max-width: 480px;
width: 100%;
padding: 2rem;
text-align: center;
}

h1 {
font-size: 2rem;
margin-bottom: 2rem;
color: #fff;
}

.card {
background: #16213e;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1rem;
}

.wallet-info {
font-size: 0.875rem;
color: #a0a0a0;
word-break: break-all;
}

.wallet-info .address {
color: #64b5f6;
font-family: monospace;
}

.wallet-info .chain {
color: #81c784;
margin-top: 0.25rem;
}

.count {
font-size: 4rem;
font-weight: bold;
color: #fff;
margin: 0.5rem 0;
}

.actions {
display: flex;
gap: 0.75rem;
justify-content: center;
flex-wrap: wrap;
}

button {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: opacity 0.2s, transform 0.1s;
}

button:hover:not(:disabled) {
opacity: 0.85;
}

button:active:not(:disabled) {
transform: scale(0.97);
}

button:disabled {
opacity: 0.5;
cursor: not-allowed;
}

.btn-connect {
background: #4361ee;
color: #fff;
width: 100%;
}

.btn-disconnect {
background: #e63946;
color: #fff;
width: 100%;
margin-top: 0.75rem;
}

.btn-increment {
background: #2ec4b6;
color: #fff;
}

.btn-decrement {
background: #ff6b6b;
color: #fff;
}

.btn-reset {
background: #6c757d;
color: #fff;
}

.tx-status {
margin-top: 0.75rem;
padding: 0.75rem;
border-radius: 8px;
font-size: 0.875rem;
}

.tx-status.pending {
background: #fff3cd;
color: #856404;
}

.tx-status.confirming {
background: #cce5ff;
color: #004085;
}

.tx-status.success {
background: #d4edda;
color: #155724;
}

.tx-status.error {
background: #f8d7da;
color: #721c24;
}

.loading {
color: #a0a0a0;
font-style: italic;
}

.error-text {
color: #e63946;
font-size: 0.875rem;
}
30 changes: 30 additions & 0 deletions week-04/dev/my-dapp/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import '@rainbow-me/rainbowkit/styles.css'

import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ConnectButton } from '@rainbow-me/rainbowkit'
import { config } from './wagmi'
import { CounterDisplay } from './components/CounterDisplay'
import { CounterActions } from './components/CounterActions'

const queryClient = new QueryClient()

function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<div className="container">
<h1>Counter DApp</h1>
<ConnectButton />
<CounterDisplay />
<CounterActions />
</div>
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
)
}

export default App
59 changes: 59 additions & 0 deletions week-04/dev/my-dapp/src/components/CounterActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useAccount, useWriteContract } from 'wagmi'
import { useQueryClient } from '@tanstack/react-query'
import { counterAbi, counterAddress } from '../counter'
import { TransactionStatus } from './TransactionStatus'

export function CounterActions() {
const { isConnected } = useAccount()
const queryClient = useQueryClient()
const { writeContract, data: hash, isPending, error, reset } = useWriteContract()

const handleWrite = (functionName: 'increment' | 'decrement' | 'reset') => {
reset()
writeContract({
address: counterAddress,
abi: counterAbi,
functionName,
})
}

const handleConfirmed = () => {
queryClient.invalidateQueries({ queryKey: ['readContract'] })
}

if (!isConnected) return null

return (
<div className="card">
<div className="actions">
<button
className="btn-decrement"
disabled={isPending}
onClick={() => handleWrite('decrement')}
>
- Decrement
</button>
<button
className="btn-increment"
disabled={isPending}
onClick={() => handleWrite('increment')}
>
+ Increment
</button>
<button
className="btn-reset"
disabled={isPending}
onClick={() => handleWrite('reset')}
>
Reset
</button>
</div>
<TransactionStatus
hash={hash}
isPending={isPending}
error={error}
onConfirmed={handleConfirmed}
/>
</div>
)
}
42 changes: 42 additions & 0 deletions week-04/dev/my-dapp/src/components/CounterDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useReadContract, useAccount } from 'wagmi'
import { counterAbi, counterAddress } from '../counter'

export function CounterDisplay() {
const { isConnected } = useAccount()
const { data, isLoading, isError, error } = useReadContract({
address: counterAddress,
abi: counterAbi,
functionName: 'getCount',
query: { enabled: isConnected },
})

if (!isConnected) {
return (
<div className="card">
<p className="loading">지갑을 연결하세요</p>
</div>
)
}

if (isLoading) {
return (
<div className="card">
<p className="loading">Loading...</p>
</div>
)
}

if (isError) {
return (
<div className="card">
<p className="error-text">Error: {error?.message ?? 'Failed to read count'}</p>
</div>
)
}

return (
<div className="card">
<div className="count">{data?.toString() ?? '—'}</div>
</div>
)
}
Loading