Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,750 changes: 3,625 additions & 1,125 deletions package-lock.json

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "bunx next lint",
"format": "bunx prettier --write ."
"lint": "next lint",
"format": "prettier --write ."
},
"lint-staged": {
"src/**/*.{ts,tsx}": [
"bunx next lint --file",
"bunx prettier --check"
"next lint --file",
"prettier --check"
]
},
"dependencies": {
Expand Down Expand Up @@ -43,6 +43,7 @@
"react-medium-image-zoom": "^5.2.12",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"thirdweb": "^5.0.3",
"viem": "^2.26.2",
"wagmi": "^2.14.16"
},
Expand All @@ -53,10 +54,10 @@
"@types/react": "^19.0.1",
"@types/react-dom": "^19",
"@types/react-window": "^1.8.8",
"@typescript-eslint/eslint-plugin": "^8.18.0",
"@typescript-eslint/parser": "^8.18.0",
"@typescript-eslint/eslint-plugin": "7.18.0",
"@typescript-eslint/parser": "7.18.0",
"autoprefixer": "^10.4.20",
"eslint": "^9.17.0",
"eslint": "8.57.1",
"eslint-config-airbnb-typescript": "^18.0.0",
"eslint-config-next": "^15.1.0",
"eslint-config-prettier": "^9.1.0",
Expand All @@ -74,6 +75,5 @@
"remark-cli": "^12.0.1",
"tailwindcss": "^3.4.16",
"typescript": "^5.7.2"
},
"packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728"
}
}
}
48 changes: 48 additions & 0 deletions src/app/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";

// Extend the Session and User types to include the idToken
declare module "next-auth" {
interface User {
idToken?: string;
}
interface Session {
idToken?: string | undefined;
}
}

export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? (() => { throw new Error("GOOGLE_CLIENT_SECRET is not defined"); })(),
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code",
scope: "openid profile email",
},
},
}),
],
callbacks: {
// Store the Google id_token in the JWT
async jwt({ token, account }) {
if (account) {
token.idToken = account.id_token;
}
return token;
},
// Add the id_token to the session
async session({ session, token }) {
session.idToken = token.idToken as string;
return session;
},
},
session: {
strategy: "jwt",
},
});

export default handlers;
71 changes: 71 additions & 0 deletions src/app/api/auth/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use client"
import type { NextApiRequest, NextApiResponse } from "next";
import { getServerSession } from "next-auth";
import { createThirdwebClient, generatePayload, getUser, verifyPayload } from "thirdweb";
import { inMemoryWallet } from "thirdweb/wallets";
import { auth } from "./[...nextauth]";
import { serialize } from "cookie";

// Initialize ThirdWeb client
const client = createThirdwebClient({
clientId: process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID,
});

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}

try {
// Get the NextAuth session (Google OAuth)
const session = await getServerSession(req, res, auth);
if (!session || !session.idToken) {
return res.status(401).json({ error: "Unauthorized" });
}

// Use the Google id_token as the identifier
const idToken = session.idToken;

// Generate a ThirdWeb Auth payload
const payload = await generatePayload({
client,
address: idToken, // Use id_token as the unique identifier
});

// Sign the payload with an in-memory wallet (for demo purposes)
const wallet = inMemoryWallet();
const signedPayload = await wallet.signPayload(payload);

// Verify the signed payload
const verified = await verifyPayload({
client,
payload: signedPayload,
});

if (!verified) {
return res.status(401).json({ error: "Verification failed" });
}

// Get the ThirdWeb user (includes wallet address)
const thirdwebUser = await getUser({ client, address: idToken });

// Store the ThirdWeb session in a cookie
const sessionData = {
address: thirdwebUser.address,
idToken,
};
const encryptedSessionData = JSON.stringify(sessionData); // In production, encrypt this
const cookie = serialize("thirdweb_session", encryptedSessionData, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: 60 * 60 * 24 * 7, // 1 week
path: "/",
});

res.setHeader("Set-Cookie", cookie);
res.status(200).json({ message: "Login successful", address: thirdwebUser.address });
} catch (error) {
console.error("Login error:", error);
res.status(500).json({ error: "Internal server error" });
}
}
13 changes: 13 additions & 0 deletions src/app/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createThirdwebClient } from "thirdweb";

// Replace this with your client ID string
// refer to https://portal.thirdweb.com/typescript/v5/client on how to get a client ID
const clientId = process.env.NEXT_PUBLIC_TEMPLATE_CLIENT_ID;

if (!clientId) {
throw new Error("No client ID provided");
}

export const client = createThirdwebClient({
clientId: clientId,
});
5 changes: 5 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import Header from '@/components/Header'
import Footer from '@/components/Footer'

import { ContextProvider } from '@/context/ContextApi'
import { ThirdwebProvider } from "thirdweb/react";
import {SessionProvider} from "@/components/SessionProvider"
import { Providers } from './provider'

import '@/app/globals.css'
Expand All @@ -19,6 +22,7 @@ export default function RootLayout({
<html lang='en' className='h-full'>
<body className='h-full'>
<div className='border-8 border-black h-screen flex flex-col'>
<ThirdwebProvider>
<ContextProvider>
<Providers>
<Header />
Expand All @@ -28,6 +32,7 @@ export default function RootLayout({
<Footer />
</Providers>
</ContextProvider>
</ThirdwebProvider>
</div>
</body>
</html>
Expand Down
69 changes: 20 additions & 49 deletions src/components/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use client'
import { useState } from 'react'
import { useAccount } from 'wagmi'
import { ConnectButton } from '@rainbow-me/rainbowkit'

import { ConnectButton } from 'thirdweb/react'
import { client } from '@/app/client'
import Link from 'next/link'

const Login: React.FC = () => {
Expand All @@ -17,10 +19,10 @@ const Login: React.FC = () => {
{/* Main Content */}
<div className='flex h-[calc(95vh-160px)] min-h-[calc(98vh-160px)] flex-col items-center'>
{/* Heading */}
<div className=' w-full flex justify-center items-center '>
<h2 className=' font-bebas text-[105px] md:text-[140px] text-center xl:text-[200px] font-normal leading-none'>
DECLEANUP REWARDS
</h2>
<div className='flex w-full items-center justify-center'>
<h2 className='text-center font-bebas text-[105px] font-normal leading-none md:text-[140px] xl:text-[200px]'>
DECLEANUP REWARDS
</h2>
</div>

<hr className='my-4 w-full border-t-2 border-black' />
Expand All @@ -43,56 +45,25 @@ const Login: React.FC = () => {
<div className='w-full px-4 py-4'>
{!isConnected ? (
<div className='flex h-24 w-full items-center justify-center rounded bg-black py-3 font-bold text-[#FAFF00] transition-all hover:bg-gray-800'>
<ConnectButton.Custom>
{({ account, openAccountModal, openConnectModal, mounted }) => {
const connected = mounted && account

return (
<div>
<button
onClick={openConnectModal}
className='flex h-full w-full items-center justify-center'
>
<span className='text-2xl font-bebas font-medium text-[#FAFF00] md:text-7xl'>
Connect Wallet
</span>
</button>
</div>
)
}}
</ConnectButton.Custom>
<ConnectButton
client={client}
appMetadata={{
name: 'Example App',
url: 'https://example.com',
}}
/>

</div>
) : (
<div className='w-full space-y-4'>
<button className='h-24 w-full font-bebas rounded bg-black py-3 font-bold text-[#FAFF00] transition-all hover:bg-gray-800 md:text-4xl'>
<Link href={'/dashboard'} className='text-7xl mt-2'>START CLEANUP</Link>
<button className='h-24 w-full rounded bg-black py-3 font-bebas font-bold text-[#FAFF00] transition-all hover:bg-gray-800 md:text-4xl'>
<Link href={'/dashboard'} className='mt-2 text-7xl'>
START CLEANUP
</Link>
</button>

<div className='flex items-center justify-center rounded bg-black bg-opacity-80 text-white'>
<div className='flex items-center justify-center rounded bg-black bg-opacity-80 text-white'>
<div className='mr-2 h-3 w-3 rounded-full bg-[#58b12f]'></div>
<ConnectButton.Custom>
{({
account,
openAccountModal,
openConnectModal,
mounted,
}) => {
const connected = mounted && account

return (
<div>
<button
onClick={openAccountModal}
className='flex w-full items-center justify-center'
>
<span className='font-medium text-white'>
{account?.displayName}
</span>
</button>
</div>
)
}}
</ConnectButton.Custom>
</div>
</div>
)}
Expand Down
10 changes: 10 additions & 0 deletions src/components/SessionProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { SessionProvider as NextAuthSessionProvider } from "next-auth/react";
import { ReactNode } from "react";

interface SessionProviderProps {
children: ReactNode;
}

export function SessionProvider({ children }: SessionProviderProps) {
return <NextAuthSessionProvider>{children}</NextAuthSessionProvider>;
}
Loading