init
This commit is contained in:
57
pages/_app.tsx
Normal file
57
pages/_app.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import "../styles/globals.css";
|
||||
import type { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
import { PrivyProvider } from "@privy-io/react-auth";
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link
|
||||
rel="preload"
|
||||
href="/fonts/AdelleSans-Regular.woff"
|
||||
as="font"
|
||||
crossOrigin=""
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="/fonts/AdelleSans-Regular.woff2"
|
||||
as="font"
|
||||
crossOrigin=""
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="/fonts/AdelleSans-Semibold.woff"
|
||||
as="font"
|
||||
crossOrigin=""
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
href="/fonts/AdelleSans-Semibold.woff2"
|
||||
as="font"
|
||||
crossOrigin=""
|
||||
/>
|
||||
|
||||
<link rel="icon" href="/favicons/favicon.ico" sizes="any" />
|
||||
<link rel="icon" href="/favicons/icon.svg" type="image/svg+xml" />
|
||||
<link rel="apple-touch-icon" href="/favicons/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/favicons/manifest.json" />
|
||||
|
||||
<title>Privy Auth Starter</title>
|
||||
<meta name="description" content="Privy Auth Starter" />
|
||||
</Head>
|
||||
<PrivyProvider
|
||||
appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID || ""}
|
||||
config={{
|
||||
embeddedWallets: {
|
||||
createOnLogin: "all-users",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</PrivyProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyApp;
|
||||
37
pages/api/verify.ts
Normal file
37
pages/api/verify.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { PrivyClient, AuthTokenClaims } from "@privy-io/server-auth";
|
||||
|
||||
const PRIVY_APP_ID = process.env.NEXT_PUBLIC_PRIVY_APP_ID;
|
||||
const PRIVY_APP_SECRET = process.env.PRIVY_APP_SECRET;
|
||||
const client = new PrivyClient(PRIVY_APP_ID!, PRIVY_APP_SECRET!);
|
||||
|
||||
export type AuthenticateSuccessResponse = {
|
||||
claims: AuthTokenClaims;
|
||||
};
|
||||
|
||||
export type AuthenticationErrorResponse = {
|
||||
error: string;
|
||||
};
|
||||
|
||||
async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<
|
||||
AuthenticateSuccessResponse | AuthenticationErrorResponse
|
||||
>,
|
||||
) {
|
||||
const headerAuthToken = req.headers.authorization?.replace(/^Bearer /, "");
|
||||
const cookieAuthToken = req.cookies["privy-token"];
|
||||
|
||||
const authToken = cookieAuthToken || headerAuthToken;
|
||||
if (!authToken) return res.status(401).json({ error: "Missing auth token" });
|
||||
|
||||
try {
|
||||
const claims = await client.verifyAuthToken(authToken);
|
||||
return res.status(200).json({ claims });
|
||||
} catch (e: any) {
|
||||
return res.status(401).json({ error: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
export default handler;
|
||||
146
pages/dashboard.tsx
Normal file
146
pages/dashboard.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getAccessToken, usePrivy } from "@privy-io/react-auth";
|
||||
import Head from "next/head";
|
||||
|
||||
async function verifyToken() {
|
||||
const url = "/api/verify";
|
||||
const accessToken = await getAccessToken();
|
||||
const result = await fetch(url, {
|
||||
headers: {
|
||||
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined),
|
||||
},
|
||||
});
|
||||
|
||||
return await result.json();
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const [verifyResult, setVerifyResult] = useState();
|
||||
const router = useRouter();
|
||||
const {
|
||||
ready,
|
||||
authenticated,
|
||||
user,
|
||||
logout,
|
||||
linkEmail,
|
||||
linkWallet,
|
||||
unlinkEmail,
|
||||
linkPhone,
|
||||
unlinkPhone,
|
||||
unlinkWallet,
|
||||
linkGoogle,
|
||||
unlinkGoogle,
|
||||
linkTwitter,
|
||||
unlinkTwitter,
|
||||
linkDiscord,
|
||||
unlinkDiscord,
|
||||
} = usePrivy();
|
||||
|
||||
useEffect(() => {
|
||||
if (ready && !authenticated) {
|
||||
router.push("/");
|
||||
}
|
||||
}, [ready, authenticated, router]);
|
||||
|
||||
const numAccounts = user?.linkedAccounts?.length || 0;
|
||||
const canRemoveAccount = numAccounts > 1;
|
||||
|
||||
const email = user?.email;
|
||||
const phone = user?.phone;
|
||||
const wallet = user?.wallet;
|
||||
|
||||
const googleSubject = user?.google?.subject || null;
|
||||
const twitterSubject = user?.twitter?.subject || null;
|
||||
const discordSubject = user?.discord?.subject || null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Account Settings</title>
|
||||
</Head>
|
||||
|
||||
<main className="flex flex-col min-h-screen px-4 sm:px-20 py-6 sm:py-10 bg-black text-white">
|
||||
{ready && authenticated ? (
|
||||
<>
|
||||
<div className="flex flex-row justify-between">
|
||||
<h1 className="text-2xl font-semibold text-white">Welcome Warlock,</h1>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="text-sm bg-red-500 hover:bg-red-700 py-2 px-4 rounded-md text-violet-100"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
<div className="items-center flex flex-wrap justify-center p-20">
|
||||
<h1 className="text-4xl">$0.1</h1>
|
||||
</div>
|
||||
<div className="mt-12 flex gap-4 flex-wrap">
|
||||
|
||||
{twitterSubject ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
unlinkTwitter(twitterSubject);
|
||||
}}
|
||||
className="text-sm border border-violet-600 hover:border-violet-700 py-2 px-4 rounded-md text-violet-600 hover:text-violet-700 disabled:border-gray-500 disabled:text-gray-500 hover:disabled:text-gray-500"
|
||||
disabled={!canRemoveAccount}
|
||||
>
|
||||
Unlink Twitter
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="text-sm bg-violet-600 hover:bg-violet-700 py-2 px-4 rounded-md text-white"
|
||||
onClick={() => {
|
||||
linkTwitter();
|
||||
}}
|
||||
>
|
||||
Link Twitter
|
||||
</button>
|
||||
)}
|
||||
|
||||
{discordSubject ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
unlinkDiscord(discordSubject);
|
||||
}}
|
||||
className="text-sm border border-violet-600 hover:border-violet-700 py-2 px-4 rounded-md text-violet-600 hover:text-violet-700 disabled:border-gray-500 disabled:text-gray-500 hover:disabled:text-gray-500"
|
||||
disabled={!canRemoveAccount}
|
||||
>
|
||||
Unlink Discord
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="text-sm bg-violet-600 hover:bg-violet-700 py-2 px-4 rounded-md text-white"
|
||||
onClick={() => {
|
||||
linkDiscord();
|
||||
}}
|
||||
>
|
||||
Link Discord
|
||||
</button>
|
||||
)}
|
||||
{wallet ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
unlinkWallet(wallet.address);
|
||||
}}
|
||||
className="text-sm border border-violet-600 hover:border-violet-700 py-2 px-4 rounded-md text-violet-600 hover:text-violet-700 disabled:border-gray-500 disabled:text-gray-500 hover:disabled:text-gray-500"
|
||||
disabled={!canRemoveAccount}
|
||||
>
|
||||
Unlink wallet
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={linkWallet}
|
||||
className="text-sm bg-violet-600 hover:bg-violet-700 py-2 px-4 rounded-md text-white border-none"
|
||||
>
|
||||
Connect wallet
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</>
|
||||
) : null}
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
64
pages/index.tsx
Normal file
64
pages/index.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import Portal from "../components/graphics/portal";
|
||||
import { useLogin } from "@privy-io/react-auth";
|
||||
import { PrivyClient } from "@privy-io/server-auth";
|
||||
import { GetServerSideProps } from "next";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async ({ req }) => {
|
||||
const cookieAuthToken = req.cookies["privy-token"];
|
||||
|
||||
// If no cookie is found, skip any further checks
|
||||
if (!cookieAuthToken) return { props: {} };
|
||||
|
||||
const PRIVY_APP_ID = process.env.NEXT_PUBLIC_PRIVY_APP_ID;
|
||||
const PRIVY_APP_SECRET = process.env.PRIVY_APP_SECRET;
|
||||
const client = new PrivyClient(PRIVY_APP_ID!, PRIVY_APP_SECRET!);
|
||||
|
||||
try {
|
||||
const claims = await client.verifyAuthToken(cookieAuthToken);
|
||||
// Use this result to pass props to a page for server rendering or to drive redirects!
|
||||
// ref https://nextjs.org/docs/pages/api-reference/functions/get-server-side-props
|
||||
console.log({ claims });
|
||||
|
||||
return {
|
||||
props: {},
|
||||
redirect: { destination: "/dashboard", permanent: false },
|
||||
};
|
||||
} catch (error) {
|
||||
return { props: {} };
|
||||
}
|
||||
};
|
||||
|
||||
export default function LoginPage() {
|
||||
const router = useRouter();
|
||||
const { login } = useLogin({
|
||||
onComplete: () => router.push("/dashboard"),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Login · Privy</title>
|
||||
</Head>
|
||||
|
||||
<main className="flex min-h-screen min-w-full">
|
||||
<div className="flex bg-black flex-1 p-6 justify-center items-center">
|
||||
<div>
|
||||
<div>
|
||||
{/* <Portal style={{ maxWidth: "100%", height: "auto" }} /> */}
|
||||
</div>
|
||||
<div className="mt-6 flex justify-center text-center">
|
||||
<button
|
||||
className="bg-violet-600 hover:bg-violet-700 py-3 px-6 text-white rounded-lg"
|
||||
onClick={login}
|
||||
>
|
||||
Log in
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user