Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #23 from DavidTParks/web3-wallet-connection
Web3 wallet connection
- Loading branch information
Showing
26 changed files
with
19,017 additions
and
11,387 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { DashboardHeader } from "@/components/dashboard/header" | ||
import { DashboardShell } from "@/components/dashboard/shell" | ||
import { Card } from "@/ui/card" | ||
|
||
export default function DashboardSettingsLoading() { | ||
return ( | ||
<DashboardShell> | ||
<div className="grid gap-10"> | ||
<Card.Skeleton /> | ||
</div> | ||
</DashboardShell> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import { DashboardShell } from "@/components/dashboard/shell" | ||
import { AddWalletForm } from "@/components/dashboard/settings/wallet/add-wallet-form" | ||
import { notFound } from "next/navigation" | ||
import { getCurrentUser } from "@/lib/session" | ||
import { db } from "@/lib/db" | ||
import { EmptyPlaceholder } from "@/components/dashboard/empty-placeholder" | ||
import { WalletOperations } from "@/components/dashboard/settings/wallet/wallet-operations" | ||
|
||
export default async function NotificationsPage() { | ||
const user = await getCurrentUser() | ||
|
||
if (!user) { | ||
return notFound() | ||
} | ||
|
||
const dbUser = await db.user.findUnique({ | ||
where: { | ||
id: user.id, | ||
}, | ||
include: { | ||
wallets: true, | ||
}, | ||
}) | ||
|
||
if (!dbUser) return null | ||
|
||
return ( | ||
<DashboardShell> | ||
<div> | ||
<AddWalletForm | ||
user={{ | ||
id: dbUser.id, | ||
}} | ||
/> | ||
<div className="mt-12 sm:flex sm:items-center"> | ||
<div className="sm:flex-auto"> | ||
<h3 className="text-xl font-semibold text-brandtext-500"> | ||
Wallets | ||
</h3> | ||
<p className="mt-2 text-sm text-brandtext-600"> | ||
You can add up to 10 wallets to your account. Set a | ||
default wallet to be used to receive ERC-20 utility | ||
token rewards for platform activity. | ||
</p> | ||
{dbUser.wallets?.length ? ( | ||
<div className="mt-4 flex flex-col"> | ||
<div className="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8"> | ||
<div className=" min-w-full py-2 align-middle md:px-6 lg:px-8"> | ||
<div className="overflow-hidden shadow ring-1 ring-raised ring-opacity-5 md:rounded-lg"> | ||
<table className="min-w-full divide-y divide-raised-border"> | ||
<thead className="bg-raised"> | ||
<tr> | ||
<th | ||
scope="col" | ||
className="whitespace-nowrap py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-brandtext-500 sm:pl-6" | ||
> | ||
Default | ||
</th> | ||
<th | ||
scope="col" | ||
className="whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-brandtext-500" | ||
> | ||
Wallet | ||
</th> | ||
<th | ||
scope="col" | ||
className="whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-brandtext-500" | ||
></th> | ||
</tr> | ||
</thead> | ||
<tbody className="divide-y divide-raised-border bg-raised"> | ||
{dbUser?.wallets?.map( | ||
(wallet) => ( | ||
<tr key={wallet.id}> | ||
<td className="inline-flex items-center gap-2 whitespace-nowrap py-3 pl-4 pr-3 text-sm text-brandtext-600 sm:pl-6"> | ||
{wallet.default | ||
? "True" | ||
: "False"} | ||
</td> | ||
<td className="whitespace-nowrap px-2 py-3 text-sm font-medium text-brandtext-600"> | ||
{ | ||
wallet.address | ||
} | ||
</td> | ||
<td> | ||
<WalletOperations | ||
user={{ | ||
id: user.id, | ||
}} | ||
userWallet={{ | ||
id: wallet.id, | ||
default: | ||
wallet.default, | ||
}} | ||
/> | ||
</td> | ||
</tr> | ||
) | ||
)} | ||
</tbody> | ||
</table> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
) : ( | ||
<> | ||
<EmptyPlaceholder className="mt-4 min-h-[200px]"> | ||
<EmptyPlaceholder.Icon name="eth" /> | ||
<EmptyPlaceholder.Title> | ||
No wallets added | ||
</EmptyPlaceholder.Title> | ||
<EmptyPlaceholder.Description className="mb-0"> | ||
There are no wallets associated with | ||
this account. | ||
</EmptyPlaceholder.Description> | ||
</EmptyPlaceholder> | ||
</> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
</DashboardShell> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
"use client" | ||
|
||
import { chains, wagmiClient } from "@/lib/wagmiClient" | ||
import { RainbowKitProvider } from "@rainbow-me/rainbowkit" | ||
import { PropsWithChildren } from "react" | ||
import { WagmiConfig } from "wagmi" | ||
|
||
export function Web3Providers({ children }: PropsWithChildren) { | ||
return ( | ||
<WagmiConfig client={wagmiClient}> | ||
<RainbowKitProvider chains={chains}>{children}</RainbowKitProvider> | ||
</WagmiConfig> | ||
) | ||
} |
186 changes: 186 additions & 0 deletions
186
components/dashboard/settings/wallet/add-wallet-form.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
"use client" | ||
|
||
import * as React from "react" | ||
import { SiweMessage } from "siwe" | ||
import { trpc } from "@/client/trpcClient" | ||
import { Icons } from "@/components/icons" | ||
import { cn } from "@/lib/utils" | ||
import { userNotificationsSchema } from "@/lib/validations/user" | ||
import { Button } from "@/ui/button" | ||
import { Card } from "@/ui/card" | ||
import { Checkbox } from "@/ui/checkbox" | ||
import { toast } from "@/ui/toast" | ||
import { zodResolver } from "@hookform/resolvers/zod" | ||
import { User } from "@prisma/client" | ||
import { useRouter } from "next/navigation" | ||
import { FormProvider, useForm } from "react-hook-form" | ||
import { z } from "zod" | ||
import { ConnectWallet } from "./conect-wallet" | ||
import { useAccount } from "wagmi" | ||
import { Chip } from "@/ui/chip" | ||
import { useFetchNonce } from "@/hooks/use-fetch-nonce" | ||
import { useNetwork } from "wagmi" | ||
import { useSignMessage } from "wagmi" | ||
|
||
type FormData = z.infer<typeof userNotificationsSchema> | ||
|
||
interface AddWalletFormProps extends React.HTMLAttributes<HTMLFormElement> { | ||
user: Pick<User, "id"> | ||
} | ||
|
||
export function AddWalletForm({ | ||
className, | ||
user, | ||
...props | ||
}: AddWalletFormProps) { | ||
const { isConnected, address } = useAccount() | ||
const { chain } = useNetwork() | ||
const { signMessageAsync } = useSignMessage() | ||
|
||
const saveNotificationPreferences = | ||
trpc.user.saveNotificationPreferences.useMutation() | ||
|
||
const router = useRouter() | ||
|
||
const methods = useForm<FormData>({ | ||
resolver: zodResolver(userNotificationsSchema), | ||
}) | ||
|
||
const [state, setState] = React.useState<{ | ||
loading?: boolean | ||
nonce?: string | ||
}>({}) | ||
|
||
const fetchNonce = async () => { | ||
try { | ||
const nonceRes = await fetch("/api/nonce") | ||
const nonce = await nonceRes.text() | ||
setState((x) => ({ ...x, nonce })) | ||
} catch (error) { | ||
setState((x) => ({ ...x, error: error as Error })) | ||
} | ||
} | ||
|
||
// Pre-fetch random nonce when button is rendered | ||
// to ensure deep linking works for WalletConnect | ||
// users on iOS when signing the SIWE message | ||
React.useEffect(() => { | ||
fetchNonce() | ||
}, []) | ||
|
||
async function onSubmit(data: FormData) { | ||
try { | ||
await saveNotificationPreferences.mutateAsync({ | ||
submissionAccepted: data.submissionAccepted, | ||
newSubmission: data.newSubmission, | ||
}) | ||
toast({ | ||
title: "Notification preferences updated", | ||
message: "", | ||
type: "success", | ||
}) | ||
router.refresh() | ||
} catch (e) { | ||
return toast({ | ||
title: "Something went wrong.", | ||
message: "Please refresh the page and try again.", | ||
type: "error", | ||
}) | ||
} | ||
} | ||
|
||
const signIn = async () => { | ||
try { | ||
const chainId = chain?.id | ||
|
||
if (!address || !chainId) return | ||
|
||
// Create SIWE message with pre-fetched nonce and sign with wallet | ||
const message = new SiweMessage({ | ||
domain: window.location.host, | ||
address, | ||
statement: "Sign in with Ethereum to the app.", | ||
uri: window.location.origin, | ||
version: "1", | ||
chainId, | ||
nonce: state.nonce, | ||
}) | ||
|
||
const signature = await signMessageAsync({ | ||
message: message.prepareMessage(), | ||
}) | ||
|
||
// Verify signature | ||
const verifyRes = await fetch(`/api/verify?userId=${user.id}`, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ message, signature }), | ||
}) | ||
if (!verifyRes.ok) throw new Error("Error verifying message") | ||
|
||
router.refresh() | ||
return toast({ | ||
title: "Wallet linked", | ||
message: | ||
"You can now interact with all the blockchain elements on our platform!", | ||
type: "success", | ||
}) | ||
} catch (error) { | ||
return toast({ | ||
title: "Something went wrong.", | ||
message: "Please refresh the page and try again.", | ||
type: "error", | ||
}) | ||
} | ||
} | ||
|
||
return ( | ||
<FormProvider {...methods}> | ||
<form | ||
className={cn(className)} | ||
onSubmit={methods.handleSubmit(onSubmit)} | ||
{...props} | ||
> | ||
<Card> | ||
<Card.Header> | ||
<Card.Title className="flex items-center gap-2"> | ||
<Icons.eth /> | ||
Crypto Wallet{" "} | ||
{isConnected ? ( | ||
<Chip size="small" intent="green"> | ||
Connected | ||
</Chip> | ||
) : ( | ||
<Chip size="small" intent="rose"> | ||
Not connected | ||
</Chip> | ||
)} | ||
</Card.Title> | ||
<Card.Description> | ||
Connect your wallet to your Vamp account. This lets | ||
you participate in our on-chain, subsidized | ||
blockchain game on the Polygon network. | ||
</Card.Description> | ||
</Card.Header> | ||
<Card.Content className="flex flex-col items-start gap-8 pb-4"> | ||
{isConnected ? ( | ||
<ConnectWallet /> | ||
) : ( | ||
"Connect your wallet" | ||
)} | ||
</Card.Content> | ||
<Card.Footer className="flex flex-col items-start space-y-2 md:flex-row md:justify-between md:space-x-0"> | ||
{!isConnected && <ConnectWallet />} | ||
{isConnected && ( | ||
<Button onClick={signIn}> | ||
Sign message to link Wallet | ||
</Button> | ||
)} | ||
</Card.Footer> | ||
</Card> | ||
</form> | ||
</FormProvider> | ||
) | ||
} |
Oops, something went wrong.
0c3fdfa
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
vamp – ./
vamp-blue.vercel.app
vamp-davidtparks.vercel.app
vamp-git-main-davidtparks.vercel.app
vamp.sh
www.vamp.sh