repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
lawb terminal
stars
latest
clone command
git clone gitlawb://did:key:z6MkvfHn...poLu/lawb-terminalgit clone gitlawb://did:key:z6MkvfHn.../lawb-terminald17cf07esync from playground1d ago| #1 | import { useState } from "react"; |
| #2 | import { useAccount, useConnect, useWriteContract, useWaitForTransactionReceipt } from "wagmi"; |
| #3 | import { parseUnits, type Hex } from "viem"; |
| #4 | import { RECIPIENT_ADDRESS, USDC_ADDRESS } from "./wagmi"; |
| #5 | |
| #6 | const SUGGESTED_AMOUNTS = [1, 5, 20] as const; |
| #7 | |
| #8 | // Minimal ERC-20 ABI for transfer |
| #9 | const ERC20_ABI = [ |
| #10 | { |
| #11 | name: "transfer", |
| #12 | type: "function", |
| #13 | stateMutability: "nonpayable", |
| #14 | inputs: [ |
| #15 | { name: "to", type: "address" }, |
| #16 | { name: "amount", type: "uint256" }, |
| #17 | ], |
| #18 | outputs: [{ name: "", type: "bool" }], |
| #19 | }, |
| #20 | ] as const; |
| #21 | |
| #22 | export default function TipButton() { |
| #23 | const [showModal, setShowModal] = useState(false); |
| #24 | const [selectedAmount, setSelectedAmount] = useState<number | null>(5); |
| #25 | const [customAmount, setCustomAmount] = useState(""); |
| #26 | const [txHash, setTxHash] = useState<Hex | undefined>(); |
| #27 | |
| #28 | const { address, isConnected } = useAccount(); |
| #29 | const { connect, connectors, isPending: isConnecting, error: connectError } = useConnect(); |
| #30 | const { writeContract, isPending: isSending, error: sendError, reset } = useWriteContract(); |
| #31 | |
| #32 | const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ |
| #33 | hash: txHash, |
| #34 | }); |
| #35 | |
| #36 | const finalAmount = customAmount ? parseFloat(customAmount) : selectedAmount; |
| #37 | |
| #38 | const handleSend = () => { |
| #39 | if (!finalAmount || finalAmount <= 0) return; |
| #40 | |
| #41 | const amount = parseUnits(String(finalAmount), 6); // USDC has 6 decimals |
| #42 | |
| #43 | writeContract( |
| #44 | { |
| #45 | address: USDC_ADDRESS, |
| #46 | abi: ERC20_ABI, |
| #47 | functionName: "transfer", |
| #48 | args: [RECIPIENT_ADDRESS, amount], |
| #49 | }, |
| #50 | { |
| #51 | onSuccess: (hash) => { |
| #52 | setTxHash(hash); |
| #53 | }, |
| #54 | } |
| #55 | ); |
| #56 | }; |
| #57 | |
| #58 | const handleConnectAndSend = () => { |
| #59 | if (!isConnected) { |
| #60 | const injectedConnector = connectors.find((c) => c.id === "injected"); |
| #61 | if (injectedConnector) { |
| #62 | connect({ connector: injectedConnector }); |
| #63 | } |
| #64 | return; |
| #65 | } |
| #66 | handleSend(); |
| #67 | }; |
| #68 | |
| #69 | const handleClose = () => { |
| #70 | setShowModal(false); |
| #71 | setSelectedAmount(5); |
| #72 | setCustomAmount(""); |
| #73 | setTxHash(undefined); |
| #74 | reset(); |
| #75 | }; |
| #76 | |
| #77 | const noWallet = typeof window !== "undefined" && !window.ethereum; |
| #78 | |
| #79 | return ( |
| #80 | <> |
| #81 | <button className="tip-trigger" onClick={() => setShowModal(true)}> |
| #82 | ☕ Support this project |
| #83 | </button> |
| #84 | |
| #85 | {showModal && ( |
| #86 | <div className="tip-overlay" onClick={handleClose}> |
| #87 | <div className="tip-modal" onClick={(e) => e.stopPropagation()}> |
| #88 | <button className="tip-close" onClick={handleClose}> |
| #89 | ✕ |
| #90 | </button> |
| #91 | |
| #92 | {isConfirmed ? ( |
| #93 | <div className="tip-success"> |
| #94 | <div className="tip-success-icon">✨</div> |
| #95 | <h3>Thank you!</h3> |
| #96 | <p>Your support means a lot.</p> |
| #97 | {txHash && ( |
| #98 | <a |
| #99 | className="tip-tx-link" |
| #100 | href={`https://basescan.org/tx/${txHash}`} |
| #101 | target="_blank" |
| #102 | rel="noopener noreferrer" |
| #103 | > |
| #104 | View on BaseScan → |
| #105 | </a> |
| #106 | )} |
| #107 | <button className="tip-btn tip-btn-secondary" onClick={handleClose}> |
| #108 | Close |
| #109 | </button> |
| #110 | </div> |
| #111 | ) : ( |
| #112 | <> |
| #113 | <h3 className="tip-title">Support this project</h3> |
| #114 | <p className="tip-subtitle">Send USDC on Base</p> |
| #115 | |
| #116 | <div className="tip-amounts"> |
| #117 | {SUGGESTED_AMOUNTS.map((amt) => ( |
| #118 | <button |
| #119 | key={amt} |
| #120 | className={`tip-amount ${selectedAmount === amt && !customAmount ? "active" : ""}`} |
| #121 | onClick={() => { |
| #122 | setSelectedAmount(amt); |
| #123 | setCustomAmount(""); |
| #124 | }} |
| #125 | > |
| #126 | ${amt} |
| #127 | </button> |
| #128 | ))} |
| #129 | </div> |
| #130 | |
| #131 | <input |
| #132 | className="tip-custom-input" |
| #133 | type="number" |
| #134 | min="0" |
| #135 | step="0.01" |
| #136 | placeholder="Custom amount" |
| #137 | value={customAmount} |
| #138 | onChange={(e) => { |
| #139 | setCustomAmount(e.target.value); |
| #140 | setSelectedAmount(null); |
| #141 | }} |
| #142 | /> |
| #143 | |
| #144 | <div className="tip-recipient"> |
| #145 | To: <code>{RECIPIENT_ADDRESS.slice(0, 6)}...{RECIPIENT_ADDRESS.slice(-4)}</code> |
| #146 | </div> |
| #147 | |
| #148 | {connectError && <p className="tip-error">{connectError.message}</p>} |
| #149 | {sendError && <p className="tip-error">{sendError.message}</p>} |
| #150 | |
| #151 | {noWallet ? ( |
| #152 | <div className="tip-no-wallet"> |
| #153 | <p>No wallet detected.</p> |
| #154 | <a |
| #155 | href="https://metamask.io/download/" |
| #156 | target="_blank" |
| #157 | rel="noopener noreferrer" |
| #158 | className="tip-btn tip-btn-secondary" |
| #159 | > |
| #160 | Install MetaMask → |
| #161 | </a> |
| #162 | </div> |
| #163 | ) : isSending || isConfirming ? ( |
| #164 | <button className="tip-btn tip-btn-primary" disabled> |
| #165 | {isSending ? "Confirm in wallet..." : "Sending..."} |
| #166 | </button> |
| #167 | ) : ( |
| #168 | <button |
| #169 | className="tip-btn tip-btn-primary" |
| #170 | onClick={handleConnectAndSend} |
| #171 | disabled={!finalAmount || finalAmount <= 0} |
| #172 | > |
| #173 | {isConnected |
| #174 | ? `Send $${finalAmount ?? 0} USDC` |
| #175 | : "Connect Wallet"} |
| #176 | </button> |
| #177 | )} |
| #178 | </> |
| #179 | )} |
| #180 | </div> |
| #181 | </div> |
| #182 | )} |
| #183 | </> |
| #184 | ); |
| #185 | } |
| #186 |