|
| 1 | +<script lang="ts"> |
| 2 | + import { useQueryClient } from "@tanstack/svelte-query"; |
| 3 | + import { SuiConnector, WalletState } from "nimbus-sui-kit"; |
| 4 | + import { isDarkMode, user, suiWalletInstance } from "~/store"; |
| 5 | + import { nimbus } from "~/lib/network"; |
| 6 | + import { Toast } from "flowbite-svelte"; |
| 7 | + import { blur } from "svelte/transition"; |
| 8 | +
|
| 9 | + import ReactAdapter from "~/components/ReactAdapter.svelte"; |
| 10 | +
|
| 11 | + import User from "~/assets/user.png"; |
| 12 | + import SUI from "~/assets/chains/sui.png"; |
| 13 | +
|
| 14 | + export let data; |
| 15 | + export let reCallAPI = () => {}; |
| 16 | +
|
| 17 | + const queryClient = useQueryClient(); |
| 18 | + const chains = [ |
| 19 | + { |
| 20 | + id: "sui:mainnet", |
| 21 | + name: "Mainnet", |
| 22 | + rpcUrl: "https://fullnode.mainnet.sui.io", |
| 23 | + }, |
| 24 | + ]; |
| 25 | +
|
| 26 | + const onConnectSuccess = (msg) => { |
| 27 | + console.log("Success connect: ", msg); |
| 28 | + if ($suiWalletInstance) { |
| 29 | + ($suiWalletInstance as WalletState).toggleSelect(); |
| 30 | + } |
| 31 | + }; |
| 32 | +
|
| 33 | + const onConnectError = (msg) => { |
| 34 | + console.error("Error connect", msg); |
| 35 | + if ($suiWalletInstance) { |
| 36 | + ($suiWalletInstance as WalletState).toggleSelect(); |
| 37 | + } |
| 38 | + }; |
| 39 | +
|
| 40 | + const widgetConfig = { |
| 41 | + walletFn: (wallet) => { |
| 42 | + suiWalletInstance.update((n) => (n = wallet)); |
| 43 | + }, |
| 44 | + onConnectSuccess, |
| 45 | + onConnectError, |
| 46 | + }; |
| 47 | +
|
| 48 | + let toastMsg = ""; |
| 49 | + let isSuccessToast = false; |
| 50 | + let counter = 3; |
| 51 | + let showToast = false; |
| 52 | +
|
| 53 | + const trigger = () => { |
| 54 | + showToast = true; |
| 55 | + counter = 3; |
| 56 | + timeout(); |
| 57 | + }; |
| 58 | +
|
| 59 | + const timeout = () => { |
| 60 | + if (--counter > 0) return setTimeout(timeout, 1000); |
| 61 | + showToast = false; |
| 62 | + toastMsg = ""; |
| 63 | + isSuccessToast = false; |
| 64 | + }; |
| 65 | +
|
| 66 | + const handleSUIAuth = async () => { |
| 67 | + try { |
| 68 | + if ($suiWalletInstance) { |
| 69 | + ($suiWalletInstance as WalletState).toggleSelect(); |
| 70 | + } |
| 71 | + } catch (e) { |
| 72 | + console.log("error: ", e); |
| 73 | + } |
| 74 | + }; |
| 75 | +
|
| 76 | + $: { |
| 77 | + if ( |
| 78 | + ($suiWalletInstance as WalletState) && |
| 79 | + ($suiWalletInstance as WalletState).connected |
| 80 | + ) { |
| 81 | + handleGetNonce(($suiWalletInstance as WalletState)?.account?.address); |
| 82 | + } |
| 83 | + } |
| 84 | +
|
| 85 | + const handleGetNonce = async (address: string) => { |
| 86 | + try { |
| 87 | + const res = await nimbus.post("/users/nonce", { |
| 88 | + publicAddress: address, |
| 89 | + referrer: undefined, |
| 90 | + }); |
| 91 | + if (res && res.data) { |
| 92 | + const signature = await handleSignAddressMessage( |
| 93 | + res.data.nonce as string |
| 94 | + ); |
| 95 | + if (signature) { |
| 96 | + const payload = { |
| 97 | + signature: signature.signature, |
| 98 | + bytes: signature.bytes, |
| 99 | + publicAddress: address?.toLowerCase(), |
| 100 | + }; |
| 101 | + handleUpdatePublicAddress(payload, address); |
| 102 | + } |
| 103 | + } |
| 104 | + } catch (e) { |
| 105 | + console.error("error: ", e); |
| 106 | + } |
| 107 | + }; |
| 108 | +
|
| 109 | + const handleSignAddressMessage = async (nonce: string) => { |
| 110 | + const msg = await ($suiWalletInstance as WalletState).signPersonalMessage({ |
| 111 | + message: new TextEncoder().encode( |
| 112 | + `I am signing my one-time nonce: ${nonce}` |
| 113 | + ), |
| 114 | + }); |
| 115 | + return msg; |
| 116 | + }; |
| 117 | +
|
| 118 | + const handleGetSUIToken = async (data) => { |
| 119 | + try { |
| 120 | + const res = await nimbus.post("/auth/sui", data); |
| 121 | + if (res?.data?.result) { |
| 122 | + localStorage.setItem("sui_token", res?.data?.result); |
| 123 | + user.update( |
| 124 | + (n) => |
| 125 | + (n = { |
| 126 | + picture: User, |
| 127 | + }) |
| 128 | + ); |
| 129 | + queryClient?.invalidateQueries(["users-me"]); |
| 130 | + queryClient.invalidateQueries(["list-address"]); |
| 131 | + queryClient.invalidateQueries(["list-bundle"]); |
| 132 | + queryClient.invalidateQueries(["link-socials"]); |
| 133 | + reCallAPI(); |
| 134 | + toastMsg = "Link your wallet successfully!"; |
| 135 | + isSuccessToast = false; |
| 136 | + trigger(); |
| 137 | + } |
| 138 | + } catch (e) { |
| 139 | + console.error("error: ", e); |
| 140 | + } |
| 141 | + }; |
| 142 | +
|
| 143 | + const handleUpdatePublicAddress = async (payload, address) => { |
| 144 | + try { |
| 145 | + let params: any = { |
| 146 | + kind: "wallet", |
| 147 | + type: null, |
| 148 | + userPublicAddress: address, |
| 149 | + id: data?.uid, |
| 150 | + info: data?.info, |
| 151 | + displayName: data?.name, |
| 152 | + }; |
| 153 | + const res = await nimbus.post("/accounts/link", params); |
| 154 | + if (res && res?.error) { |
| 155 | + toastMsg = |
| 156 | + "Your wallet already Nimbus user. Please try again with another wallet!"; |
| 157 | + isSuccessToast = false; |
| 158 | + trigger(); |
| 159 | + return; |
| 160 | + } |
| 161 | + localStorage.removeItem("auth_token"); |
| 162 | + handleGetSUIToken(payload); |
| 163 | + } catch (e) { |
| 164 | + console.log(e); |
| 165 | + } |
| 166 | + }; |
| 167 | +</script> |
| 168 | + |
| 169 | +<div |
| 170 | + class={`flex items-center justify-center gap-2 text-white border cursor-pointer py-3 px-6 rounded-[12px] min-w-[250px] ${ |
| 171 | + $isDarkMode ? "border-white text-white" : "border-[#27326f] text-[#27326f]" |
| 172 | + }`} |
| 173 | + on:click={handleSUIAuth} |
| 174 | +> |
| 175 | + <img src={SUI} class="h-[24px] w-auto" /> |
| 176 | + <div class="font-semibold text-[15px]">Login with Sui</div> |
| 177 | +</div> |
| 178 | + |
| 179 | +<ReactAdapter |
| 180 | + element={SuiConnector} |
| 181 | + config={widgetConfig} |
| 182 | + autoConnect={false} |
| 183 | + {chains} |
| 184 | + integrator="svelte-example" |
| 185 | +/> |
| 186 | + |
| 187 | +{#if showToast} |
| 188 | + <div class="fixed top-3 right-3 w-full" style="z-index: 2147483648;"> |
| 189 | + <Toast |
| 190 | + transition={blur} |
| 191 | + params={{ amount: 10 }} |
| 192 | + position="top-right" |
| 193 | + color={isSuccessToast ? "green" : "red"} |
| 194 | + bind:open={showToast} |
| 195 | + > |
| 196 | + <svelte:fragment slot="icon"> |
| 197 | + {#if isSuccessToast} |
| 198 | + <svg |
| 199 | + aria-hidden="true" |
| 200 | + class="w-5 h-5" |
| 201 | + fill="currentColor" |
| 202 | + viewBox="0 0 20 20" |
| 203 | + xmlns="http://www.w3.org/2000/svg" |
| 204 | + ><path |
| 205 | + fill-rule="evenodd" |
| 206 | + d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" |
| 207 | + clip-rule="evenodd" |
| 208 | + /></svg |
| 209 | + > |
| 210 | + <span class="sr-only">Check icon</span> |
| 211 | + {:else} |
| 212 | + <svg |
| 213 | + aria-hidden="true" |
| 214 | + class="w-5 h-5" |
| 215 | + fill="currentColor" |
| 216 | + viewBox="0 0 20 20" |
| 217 | + xmlns="http://www.w3.org/2000/svg" |
| 218 | + ><path |
| 219 | + fill-rule="evenodd" |
| 220 | + d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" |
| 221 | + clip-rule="evenodd" |
| 222 | + /></svg |
| 223 | + > |
| 224 | + <span class="sr-only">Error icon</span> |
| 225 | + {/if} |
| 226 | + </svelte:fragment> |
| 227 | + {toastMsg} |
| 228 | + </Toast> |
| 229 | + </div> |
| 230 | +{/if} |
| 231 | + |
| 232 | +<style windi:preflights:global windi:safelist:global> |
| 233 | +</style> |
0 commit comments