|
1 |
| -import { useEffect, useState } from "react"; |
2 |
| -import { useLaserEyes, MAGIC_EDEN, ProviderType } from "@omnisat/lasereyes"; |
3 |
| -import init, { verify } from "@/bip322.js"; |
| 1 | +import { useEffect } from "react"; |
4 | 2 | import VerifyForm from "@/components/VerifyForm";
|
5 |
| -import ConnectWalletForm from "@/components/ConnectWallet"; |
6 | 3 | import SignMessageForm from "@/components/SignMessage";
|
7 |
| -import "@/index.css"; |
8 |
| -import { Button } from "@/components/ui/button"; |
9 |
| -import AnimatedContainer from "./components/AnimatedContainer"; |
10 |
| - |
11 |
| -export interface SignMessageState { |
12 |
| - message: string; |
13 |
| - signedData: { |
14 |
| - address: string; |
15 |
| - message: string; |
16 |
| - signature: string; |
17 |
| - } | null; |
18 |
| -} |
19 |
| - |
20 |
| -export interface VerifyFormState { |
21 |
| - address: string; |
22 |
| - message: string; |
23 |
| - signature: string; |
24 |
| - verificationResult: string | null; |
25 |
| -} |
| 4 | +import { BaseButton } from "@/components/ui/base-button"; |
| 5 | +import { useWasmInit } from "@/hooks/useWasmInit"; |
| 6 | +import { useWalletConnection } from "@/hooks/useWalletConnection"; |
| 7 | +import { useSignMessage } from "@/hooks/useSignMessage"; |
| 8 | +import { useVerifyMessage } from "@/hooks/useVerifyMessage"; |
26 | 9 |
|
27 | 10 | function App() {
|
28 |
| - const [isWasmInitialized, setWasmInitialized] = useState(false); |
29 |
| - |
30 |
| - const [isSignFormVisible, setIsSignFormVisible] = useState(false); |
31 |
| - const [isVerifyFormVisible, setIsVerifyFormVisible] = useState(false); |
32 |
| - |
33 |
| - const [signMessageState, setSignMessageState] = useState<SignMessageState>({ |
34 |
| - message: "", |
35 |
| - signedData: null, |
36 |
| - }); |
37 |
| - |
38 |
| - const [verifyFormState, setVerifyFormState] = useState<VerifyFormState>({ |
39 |
| - address: "", |
40 |
| - message: "", |
41 |
| - signature: "", |
42 |
| - verificationResult: null, |
43 |
| - }); |
44 |
| - |
45 |
| - const { |
46 |
| - connect, |
47 |
| - disconnect, |
48 |
| - address, |
49 |
| - provider, |
50 |
| - hasUnisat, |
51 |
| - hasXverse, |
52 |
| - hasOyl, |
53 |
| - hasMagicEden, |
54 |
| - hasOkx, |
55 |
| - hasLeather, |
56 |
| - hasPhantom, |
57 |
| - connected, |
58 |
| - signMessage, |
59 |
| - } = useLaserEyes(); |
60 |
| - |
61 |
| - const hasWallet = { |
62 |
| - unisat: hasUnisat, |
63 |
| - xverse: hasXverse, |
64 |
| - oyl: hasOyl, |
65 |
| - [MAGIC_EDEN]: hasMagicEden, |
66 |
| - okx: hasOkx, |
67 |
| - leather: hasLeather, |
68 |
| - phantom: hasPhantom, |
69 |
| - }; |
70 |
| - |
71 |
| - const handleConnect = async (walletName: ProviderType) => { |
72 |
| - if (provider === walletName) { |
73 |
| - disconnect(); |
74 |
| - } else { |
75 |
| - await connect(walletName as never); |
76 |
| - } |
77 |
| - }; |
78 |
| - |
79 |
| - const handleDisconnect = async () => { |
80 |
| - try { |
81 |
| - disconnect(); |
82 |
| - resetSignMessageForm(); |
83 |
| - } catch (error) { |
84 |
| - console.error("Failed to disconnect wallet:", error); |
85 |
| - } |
86 |
| - }; |
87 |
| - |
88 |
| - const handleMessageSign = async () => { |
89 |
| - if (!connected || !signMessageState.message) return; |
90 |
| - |
91 |
| - try { |
92 |
| - const signature = await signMessage(signMessageState.message, address); |
93 |
| - const newSignedData = { |
94 |
| - address: address, |
95 |
| - message: signMessageState.message, |
96 |
| - signature, |
97 |
| - }; |
98 |
| - |
99 |
| - setSignMessageState((prev) => ({ |
100 |
| - ...prev, |
101 |
| - signedData: newSignedData, |
102 |
| - })); |
103 |
| - } catch (error) { |
104 |
| - console.error("Failed to sign message:", error); |
105 |
| - } |
106 |
| - }; |
107 |
| - |
108 |
| - const handleVerification = async (e: React.FormEvent) => { |
109 |
| - e.preventDefault(); |
110 |
| - if (!isWasmInitialized) { |
111 |
| - console.error("WASM not initialized yet"); |
112 |
| - return; |
113 |
| - } |
114 |
| - |
115 |
| - try { |
116 |
| - const result = verify( |
117 |
| - verifyFormState.address, |
118 |
| - verifyFormState.message, |
119 |
| - verifyFormState.signature |
120 |
| - ); |
121 |
| - setVerifyFormState((prev) => ({ |
122 |
| - ...prev, |
123 |
| - verificationResult: result.toString(), |
124 |
| - })); |
125 |
| - } catch (error) { |
126 |
| - console.error("Verification failed:", error); |
127 |
| - setVerifyFormState((prev) => ({ |
128 |
| - ...prev, |
129 |
| - verificationResult: "Verification failed", |
130 |
| - })); |
131 |
| - } |
132 |
| - }; |
133 |
| - |
134 |
| - const handleVerifyFormChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
135 |
| - setVerifyFormState((prev) => ({ |
136 |
| - ...prev, |
137 |
| - [e.target.id]: e.target.value, |
138 |
| - })); |
139 |
| - }; |
140 |
| - |
141 |
| - const resetSignMessageForm = () => { |
142 |
| - setSignMessageState({ |
143 |
| - message: "", |
144 |
| - signedData: null, |
145 |
| - }); |
146 |
| - }; |
147 |
| - |
148 |
| - const resetVerifyForm = () => { |
149 |
| - setVerifyFormState({ |
150 |
| - address: "", |
151 |
| - message: "", |
152 |
| - signature: "", |
153 |
| - verificationResult: null, |
154 |
| - }); |
155 |
| - }; |
156 |
| - |
157 |
| - useEffect(() => { |
158 |
| - init() |
159 |
| - .then(() => setWasmInitialized(true)) |
160 |
| - .catch((error) => console.error("Failed to initialize WASM:", error)); |
161 |
| - }, []); |
| 11 | + const { wasmError } = useWasmInit(); |
| 12 | + const [walletState, walletActions] = useWalletConnection(); |
| 13 | + const [signState, signActions] = useSignMessage(); |
| 14 | + const [verifyState, verifyActions] = useVerifyMessage(); |
162 | 15 |
|
163 | 16 | useEffect(() => {
|
164 |
| - const handleBeforeUnload = async () => { |
165 |
| - if (connected) { |
166 |
| - disconnect(); |
| 17 | + const handleBeforeUnload = () => { |
| 18 | + if (walletState.isConnected) { |
| 19 | + walletActions.handleDisconnect(); |
167 | 20 | }
|
168 | 21 | };
|
169 | 22 |
|
170 | 23 | window.addEventListener("beforeunload", handleBeforeUnload);
|
171 | 24 | return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
172 |
| - }, [connected, disconnect]); |
| 25 | + }, [walletState.isConnected, walletActions]); |
173 | 26 |
|
174 |
| - if (!isWasmInitialized) { |
175 |
| - return <div>Loading WASM...</div>; |
| 27 | + if (wasmError) { |
| 28 | + return <div>Failed to initialize WASM: {wasmError.message}</div>; |
176 | 29 | }
|
177 | 30 |
|
178 | 31 | return (
|
179 |
| - <div className="app-container"> |
180 |
| - <header className="hero"> |
181 |
| - <h1 onClick={() => window.location.reload()}>bip322</h1> |
| 32 | + <div className="min-h-screen w-full flex flex-col justify-center"> |
| 33 | + <header className="hero h-[calc(var(--size)*0.50)] flex justify-center w-full"> |
| 34 | + <div className="w-[95%] md:w-[80vw] mx-auto"> |
| 35 | + <h1 onClick={() => window.location.reload()}>bip322</h1> |
| 36 | + </div> |
182 | 37 | </header>
|
183 | 38 |
|
184 |
| - <section className="grid grid-cols-2 gap-12 items-center"> |
185 |
| - <AnimatedContainer isExpanded={isSignFormVisible}> |
186 |
| - <div |
187 |
| - className={`absolute inset-0 w-full h-full transition-opacity duration-300 ${ |
188 |
| - !isSignFormVisible |
189 |
| - ? "opacity-0 pointer-events-none" |
190 |
| - : "opacity-100" |
191 |
| - }`} |
192 |
| - > |
193 |
| - {connected && address ? ( |
| 39 | + <main className="w-full"> |
| 40 | + <div className="w-[95%] md:w-[65vw] mx-auto"> |
| 41 | + <div className="flex flex-col lg:flex-row gap-[calc(var(--size)*0.2)] lg:gap-[calc(var(--size)*0.1)] min-h-[50vh] items-center"> |
| 42 | + <div className="flex-1 w-full flex justify-center items-end lg:items-center"> |
194 | 43 | <SignMessageForm
|
195 |
| - address={address} |
196 |
| - message={signMessageState.message} |
197 |
| - signedData={signMessageState.signedData} |
198 |
| - onMessageChange={(message) => |
199 |
| - setSignMessageState((prev) => ({ ...prev, message })) |
| 44 | + message={signState.message} |
| 45 | + signedData={signState.signedData} |
| 46 | + onMessageChange={signActions.setMessage} |
| 47 | + onSign={() => |
| 48 | + signActions.handleSign( |
| 49 | + walletState.address!, |
| 50 | + walletActions.signMessage |
| 51 | + ) |
200 | 52 | }
|
201 |
| - onSign={handleMessageSign} |
202 |
| - onReset={resetSignMessageForm} |
203 |
| - onBack={handleDisconnect} |
| 53 | + onReset={signActions.reset} |
204 | 54 | />
|
205 |
| - ) : ( |
206 |
| - <ConnectWalletForm |
207 |
| - provider={provider} |
208 |
| - hasWallet={hasWallet} |
209 |
| - onConnect={handleConnect} |
210 |
| - onDisconnect={() => { |
211 |
| - handleDisconnect(); |
212 |
| - setIsSignFormVisible(false); |
213 |
| - }} |
| 55 | + </div> |
| 56 | + |
| 57 | + <div className="flex-1 w-full flex justify-center items-start lg:items-center"> |
| 58 | + <VerifyForm |
| 59 | + formData={verifyState} |
| 60 | + verificationResult={verifyState.verificationResult} |
| 61 | + onSubmit={verifyActions.handleVerify} |
| 62 | + onInputChange={verifyActions.handleChange} |
| 63 | + onReset={verifyActions.reset} |
214 | 64 | />
|
215 |
| - )} |
| 65 | + </div> |
216 | 66 | </div>
|
217 |
| - <Button |
218 |
| - className={` |
219 |
| - h-[calc(var(--font-large)+3rem)] w-full |
220 |
| - text-[length:var(--font-md)] |
221 |
| - md:text-[length:var(--font-md)] |
222 |
| - bg-[hsl(var(--light-1))] text-[hsl(var(--dark-1))] |
223 |
| - [box-shadow:0_0_7px_#fff] |
224 |
| - hover:bg-[hsl(var(--light-2))] |
225 |
| - hover:text-[hsl(var(--dark-1))] |
226 |
| - hover:[box-shadow:0_0_15px_3px_#fff] |
227 |
| - rounded-xl |
228 |
| - transition-all duration-300 |
229 |
| - [text-shadow:0_0_4px_rgba(0,0,0,0.3),0_0_8px_rgba(0,0,0,0.2),0_0_12px_rgba(0,0,0,0.1)] |
230 |
| - hover:[text-shadow:0_0_6px_rgba(0,0,0,0.4),0_0_12px_rgba(0,0,0,0.3),0_0_18px_rgba(0,0,0,0.2)] |
231 |
| - ${ |
232 |
| - isSignFormVisible |
233 |
| - ? "opacity-0 pointer-events-none" |
234 |
| - : "opacity-100" |
235 |
| - } |
236 |
| - `} |
237 |
| - variant="ghost" |
238 |
| - onClick={() => setIsSignFormVisible(true)} |
239 |
| - > |
240 |
| - sign |
241 |
| - </Button> |
242 |
| - </AnimatedContainer> |
243 |
| - |
244 |
| - <AnimatedContainer isExpanded={isVerifyFormVisible}> |
245 |
| - <div |
246 |
| - className={`absolute inset-0 w-full h-full transition-opacity duration-300 ${ |
247 |
| - !isVerifyFormVisible |
248 |
| - ? "opacity-0 pointer-events-none" |
249 |
| - : "opacity-100" |
250 |
| - }`} |
251 |
| - > |
252 |
| - <VerifyForm |
253 |
| - formData={verifyFormState} |
254 |
| - verificationResult={verifyFormState.verificationResult} |
255 |
| - onSubmit={handleVerification} |
256 |
| - onInputChange={handleVerifyFormChange} |
257 |
| - onReset={resetVerifyForm} |
258 |
| - onBack={() => setIsVerifyFormVisible(false)} |
259 |
| - /> |
| 67 | + </div> |
| 68 | + </main> |
| 69 | + |
| 70 | + <footer className="flex h-[calc(var(--size)*0.40)] items-center w-full"> |
| 71 | + <nav className="w-[95%] md:w-[80%] mx-auto"> |
| 72 | + <div className="flex justify-between gap-4 lg:gap-8"> |
| 73 | + <BaseButton variant="nav" asChild> |
| 74 | + <a href="https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki"> |
| 75 | + bip |
| 76 | + </a> |
| 77 | + </BaseButton> |
| 78 | + <BaseButton variant="nav" asChild> |
| 79 | + <a href="https://github.com/rust-bitcoin/bip322">github</a> |
| 80 | + </BaseButton> |
| 81 | + <BaseButton variant="nav" asChild> |
| 82 | + <a href="https://crates.io/crates/bip322">crate</a> |
| 83 | + </BaseButton> |
260 | 84 | </div>
|
261 |
| - <Button |
262 |
| - className={` |
263 |
| - h-[calc(var(--font-large)+3rem)] w-full |
264 |
| - text-[length:var(--font-md)] |
265 |
| - md:text-[length:var(--font-md)] |
266 |
| - bg-[hsl(var(--light-1))] text-[hsl(var(--dark-1))] |
267 |
| - [box-shadow:0_0_10px_#fff] |
268 |
| - hover:bg-[hsl(var(--light-2))] |
269 |
| - hover:text-[hsl(var(--dark-1))] |
270 |
| - hover:[box-shadow:0_0_20px_3px_#fff] |
271 |
| - rounded-xl |
272 |
| - transition-all duration-300 |
273 |
| - [text-shadow:0_0_4px_rgba(0,0,0,0.3),0_0_8px_rgba(0,0,0,0.2),0_0_12px_rgba(0,0,0,0.1)] |
274 |
| - hover:[text-shadow:0_0_6px_rgba(0,0,0,0.4),0_0_12px_rgba(0,0,0,0.3),0_0_18px_rgba(0,0,0,0.2)] |
275 |
| - ${ |
276 |
| - isVerifyFormVisible |
277 |
| - ? "opacity-0 pointer-events-none" |
278 |
| - : "opacity-100" |
279 |
| - } |
280 |
| - `} |
281 |
| - variant="ghost" |
282 |
| - onClick={() => setIsVerifyFormVisible(true)} |
283 |
| - > |
284 |
| - verify |
285 |
| - </Button> |
286 |
| - </AnimatedContainer> |
287 |
| - </section> |
288 |
| - |
289 |
| - <nav className="flex justify-between items-center absolute inset-x-0 bottom-28 py-18 mb-20"> |
290 |
| - <Button |
291 |
| - asChild |
292 |
| - variant="link" |
293 |
| - className="text-[length:var(--font-small)]" |
294 |
| - > |
295 |
| - <a href="https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki"> |
296 |
| - bip |
297 |
| - </a> |
298 |
| - </Button> |
299 |
| - <Button |
300 |
| - asChild |
301 |
| - variant="link" |
302 |
| - className="text-[length:var(--font-small)]" |
303 |
| - > |
304 |
| - <a href="https://github.com/rust-bitcoin/bip322">github</a> |
305 |
| - </Button> |
306 |
| - <Button |
307 |
| - asChild |
308 |
| - variant="link" |
309 |
| - className="text-[length:var(--font-small)]" |
310 |
| - > |
311 |
| - <a href="https://crates.io/crates/bip322">crate</a> |
312 |
| - </Button> |
313 |
| - </nav> |
| 85 | + </nav> |
| 86 | + </footer> |
314 | 87 | </div>
|
315 | 88 | );
|
316 | 89 | }
|
|
0 commit comments