diff --git a/.changeset/config.json b/.changeset/config.json index 4db1fe225f5..2f30e515ed8 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -10,7 +10,13 @@ "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": ["playground-web", "thirdweb-dashboard", "wallet-ui", "portal"], + "ignore": [ + "playground-web", + "thirdweb-dashboard", + "wallet-ui", + "portal", + "thirdweb-login" + ], "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { "updateInternalDependents": "always", "onlyUpdatePeerDependentsWhenOutOfRange": true diff --git a/README.md b/README.md index 1967adf2ac8..71caae13270 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@


- +

@@ -31,12 +31,12 @@ ## Library Comparison -| | thirdweb | Wagmi + Viem | Ethers@6 | +| | thirdweb | Wagmi + Viem | Ethers@6 | | ----------------------------------------- | -------- | ------------------ | -------- | | Type safe contract API | ✅ | ✅ | ✅ | | Type safe wallet API | ✅ | ✅ | ✅ | | EVM utils | ✅ | ✅ | ✅ | -| RPC for any EVM | ✅  | ⚠️ public RPC only | ❌ | +| RPC for any EVM | ✅  | ⚠️ public RPC only | ❌ | | Automatic ABI Resolution | ✅ | ❌ | ❌ | | IPFS Upload/Download | ✅ | ❌ | ❌ | | Embedded wallet (email/ social login) | ✅ | ⚠️ via 3rd party | ❌ | diff --git a/apps/login/CHANGELOG.md b/apps/login/CHANGELOG.md deleted file mode 100644 index dd1c6399628..00000000000 --- a/apps/login/CHANGELOG.md +++ /dev/null @@ -1,73 +0,0 @@ -# thirdweb-login - -## 0.1.10 - -### Patch Changes - -- Updated dependencies [[`5abbae7`](https://github.com/thirdweb-dev/js/commit/5abbae7368854e1be4f774bd3a7bd48e0dde04f7)]: - - thirdweb@5.88.7 - -## 0.1.9 - -### Patch Changes - -- Updated dependencies [[`b182302`](https://github.com/thirdweb-dev/js/commit/b182302f590e75c9881cebd0ca1cc8b1425d50b8), [`5a08176`](https://github.com/thirdweb-dev/js/commit/5a08176eb0eb6d258d1d521a1e7a44c08145a043), [`a0f3557`](https://github.com/thirdweb-dev/js/commit/a0f355717ad3c829f3d692183be46a43187eca49), [`45ca033`](https://github.com/thirdweb-dev/js/commit/45ca0334bb4e8378fefd683e48aef95e4ce8b886)]: - - thirdweb@5.88.6 - -## 0.1.8 - -### Patch Changes - -- Updated dependencies [[`0574eac`](https://github.com/thirdweb-dev/js/commit/0574eac02c832c382972fd545df79c36e11796e1), [`562c534`](https://github.com/thirdweb-dev/js/commit/562c534d3763ac645618386d8fa9a3b8db3769d9), [`52cbcd2`](https://github.com/thirdweb-dev/js/commit/52cbcd2d57abf4d69ee417fe98fca815e19f2f34), [`b882e29`](https://github.com/thirdweb-dev/js/commit/b882e299353d895724c4e44216ffb47353a9bf7f)]: - - thirdweb@5.88.5 - -## 0.1.7 - -### Patch Changes - -- Updated dependencies [[`98b6198`](https://github.com/thirdweb-dev/js/commit/98b6198687b11022033fb08410407f0ccb09cd1c)]: - - thirdweb@5.88.4 - -## 0.1.6 - -### Patch Changes - -- Updated dependencies [[`ee1bc3e`](https://github.com/thirdweb-dev/js/commit/ee1bc3eb076a189fa806dd6008bf2f97e0bd052f)]: - - thirdweb@5.88.3 - -## 0.1.5 - -### Patch Changes - -- Updated dependencies [[`2fbf105`](https://github.com/thirdweb-dev/js/commit/2fbf105d156ee551eec29e545fa08c43fd649051), [`97c9ab7`](https://github.com/thirdweb-dev/js/commit/97c9ab7e1caa8b3e006ecc7f64b54b392ba83eda), [`6b2a90a`](https://github.com/thirdweb-dev/js/commit/6b2a90ac89fd8c1bb8b784fb67e2a1a1a0e99816), [`fbafb65`](https://github.com/thirdweb-dev/js/commit/fbafb65f5c8669efd23028dd365982274cc06034), [`66bbedb`](https://github.com/thirdweb-dev/js/commit/66bbedbe3afef8b3eb6902e47391bd914f18bcca), [`159ffbf`](https://github.com/thirdweb-dev/js/commit/159ffbfaeed19dc6e37c19ad502b45a6a9a80669), [`6b286c7`](https://github.com/thirdweb-dev/js/commit/6b286c7e83dbba4beb6eeedc6ec24d9d3617f0cd)]: - - thirdweb@5.88.2 - -## 0.1.4 - -### Patch Changes - -- Updated dependencies [[`f1cd253`](https://github.com/thirdweb-dev/js/commit/f1cd2539d1be15eb18807b3f5f2b90509e3d58cf)]: - - thirdweb@5.88.1 - -## 0.1.3 - -### Patch Changes - -- Updated dependencies [[`1f6bb7c`](https://github.com/thirdweb-dev/js/commit/1f6bb7c3294d70648b120a6a6a6cba13302a84fc), [`30e13e6`](https://github.com/thirdweb-dev/js/commit/30e13e6b9176265a2f4eddfa53578889abbcb750), [`2dfc245`](https://github.com/thirdweb-dev/js/commit/2dfc245d44dde86e42f6c799305db707316432aa), [`ee57ded`](https://github.com/thirdweb-dev/js/commit/ee57ded902cb69da6fc171599a4a90776e650149), [`9663079`](https://github.com/thirdweb-dev/js/commit/966307906212ac99dc0a2a9be88e514c920d39c4), [`64d7bf3`](https://github.com/thirdweb-dev/js/commit/64d7bf358fe2014b684688d41d525a75e47f1b82)]: - - thirdweb@5.88.0 - -## 0.1.2 - -### Patch Changes - -- Updated dependencies [[`f77165e`](https://github.com/thirdweb-dev/js/commit/f77165e2d1dd13a1887604c3431bd49b9bd67f28)]: - - thirdweb@5.87.4 - -## 0.1.1 - -### Patch Changes - -- [#6137](https://github.com/thirdweb-dev/js/pull/6137) [`a6b7e8d`](https://github.com/thirdweb-dev/js/commit/a6b7e8d81868b5f32f1c8b7ff093bb1f06c734ca) Thanks [@jnsdls](https://github.com/jnsdls)! - updated dependencies - -- Updated dependencies [[`a6b7e8d`](https://github.com/thirdweb-dev/js/commit/a6b7e8d81868b5f32f1c8b7ff093bb1f06c734ca), [`9d5828e`](https://github.com/thirdweb-dev/js/commit/9d5828eeab201960a720744ca3a59c85a0d8e548), [`b693b78`](https://github.com/thirdweb-dev/js/commit/b693b78645e2b214a5f8be0eec6d335d569ceb8c), [`08cc489`](https://github.com/thirdweb-dev/js/commit/08cc48910df351d068c1ce224d4102f40cb1dce1)]: - - thirdweb@5.87.3 diff --git a/apps/login/components.json b/apps/login/components.json new file mode 100644 index 00000000000..d9ef0ae537d --- /dev/null +++ b/apps/login/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/apps/login/eslint.config.mjs b/apps/login/eslint.config.mjs index f5b76f3927b..c85fb67c463 100644 --- a/apps/login/eslint.config.mjs +++ b/apps/login/eslint.config.mjs @@ -1,5 +1,5 @@ -import { dirname } from "node:path"; -import { fileURLToPath } from "node:url"; +import { dirname } from "path"; +import { fileURLToPath } from "url"; import { FlatCompat } from "@eslint/eslintrc"; const __filename = fileURLToPath(import.meta.url); diff --git a/apps/login/knip.json b/apps/login/knip.json deleted file mode 100644 index 4ea83160196..00000000000 --- a/apps/login/knip.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "$schema": "https://unpkg.com/knip@5/schema.json", - "next": true, - "ignore": ["public/**"], - "project": ["src/**"], - "ignoreBinaries": ["only-allow", "biome"], - "ignoreDependencies": ["thirdweb", "eslint-config-next"] -} diff --git a/apps/login/next.config.ts b/apps/login/next.config.ts index 8f6e5b90427..e9ffa3083ad 100644 --- a/apps/login/next.config.ts +++ b/apps/login/next.config.ts @@ -2,27 +2,6 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ - async headers() { - return [ - { - source: "/api/:path*", - headers: [ - { - key: "Access-Control-Allow-Origin", - value: "*", // Set your origin - }, - { - key: "Access-Control-Allow-Methods", - value: "GET, POST, PUT, DELETE, OPTIONS", - }, - { - key: "Access-Control-Allow-Headers", - value: "Content-Type, Authorization", - }, - ], - }, - ]; - }, }; export default nextConfig; diff --git a/apps/login/package.json b/apps/login/package.json index 9a263dc906e..77f73afd689 100644 --- a/apps/login/package.json +++ b/apps/login/package.json @@ -1,23 +1,38 @@ { "name": "thirdweb-login", - "version": "0.1.10", + "version": "0.1.0", "private": true, "scripts": { - "preinstall": "npx only-allow pnpm", "dev": "next dev --turbopack", "build": "next build", "start": "next start", "format": "biome format ./src --write", - "lint": "biome check ./src && knip && eslint ./src", - "fix": "biome check ./src --fix && eslint ./src --fix", - "typecheck": "tsc --noEmit", - "knip": "knip" + "prelint": "biome check ./src", + "lint": "eslint ./src", + "prefix": "biome check ./src --fix", + "fix": "eslint ./src --fix", + "typecheck": "tsc --noEmit" }, "dependencies": { + "@radix-ui/react-dropdown-menu": "^2.1.5", + "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-select": "^2.1.5", + "@radix-ui/react-separator": "^1.1.1", + "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-switch": "^1.1.2", + "@radix-ui/react-tooltip": "1.1.7", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "jose": "^5.10.0", + "lucide-react": "0.474.0", "next": "15.1.6", + "next-themes": "^0.4.4", "react": "19.0.0", "react-dom": "19.0.0", "server-only": "^0.0.1", + "sonner": "^1.7.4", + "tailwind-merge": "^2.6.0", + "tailwindcss-animate": "^1.0.7", "thirdweb": "workspace:*" }, "devDependencies": { @@ -27,9 +42,7 @@ "@types/react-dom": "19.0.3", "eslint": "^9", "eslint-config-next": "15.1.6", - "knip": "5.43.6", "postcss": "8.5.1", - "postcss-load-config": "^6.0.1", "tailwindcss": "3.4.17", "typescript": "5.7.3" } diff --git a/apps/login/public/logo.svg b/apps/login/public/logo.svg deleted file mode 100644 index 765b88b3eb2..00000000000 --- a/apps/login/public/logo.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/apps/login/public/placeholder.svg b/apps/login/public/placeholder.svg new file mode 100644 index 00000000000..e763910b27f --- /dev/null +++ b/apps/login/public/placeholder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/login/public/twl.js b/apps/login/public/twl.js deleted file mode 100644 index ea15dfdd63f..00000000000 --- a/apps/login/public/twl.js +++ /dev/null @@ -1,193 +0,0 @@ -// biome-ignore lint/complexity/useArrowFunction: This is a self-executing function, we do not want to use an arrow function here. -(function () { - const globalSetup = getSetup(); - - const JWT_KEY = "tw.login:jwt"; - const CODE_KEY = "tw.login:code"; - - // check if redirected first, this sets up the logged in state if it was from redirect - const result = parseURL(new URL(window.location)); - if ( - result && - result.length === 2 && - result[1] === localStorage.getItem(CODE_KEY) - ) { - // reset the URL - window.location.hash = ""; - window.location.search = ""; - - // write the jwt to local storage - localStorage.setItem(JWT_KEY, result[0]); - } - - // always reset the code - localStorage.removeItem(CODE_KEY); - - const jwt = localStorage.getItem(JWT_KEY); - - if (jwt) { - // handle logged in state - handleIsLoggedIn(); - } else { - // handle not logged in state - handleNotLoggedIn(); - } - - function handleIsLoggedIn() { - window.thirdweb = { - isLoggedIn: true, - getUser: async () => { - const res = await fetch(`${globalSetup.baseUrl}/api/user`, { - headers: { - Authorization: `Bearer ${localStorage.getItem(JWT_KEY)}`, - }, - }); - return res.json(); - }, - logout: () => { - window.localStorage.removeItem(JWT_KEY); - window.location.reload(); - }, - }; - - renderFloatingBubble(true); - } - - function handleNotLoggedIn() { - window.thirdweb = { login: onLogin, isLoggedIn: false }; - renderFloatingBubble(false); - } - - function onLogin() { - const code = window.crypto.getRandomValues(new Uint8Array(16)).join(""); - localStorage.setItem(CODE_KEY, code); - // redirect to the login page - const redirect = new URL(globalSetup.baseUrl); - redirect.searchParams.set("code", code); - redirect.searchParams.set("clientId", globalSetup.clientId); - redirect.searchParams.set( - "redirect", - window.location.origin + window.location.pathname, - ); - window.location.href = redirect.href; - } - - // utils - function getSetup() { - const el = document.currentScript; - if (!el) { - throw new Error("Could not find script element"); - } - const baseUrl = new URL(el.src).origin; - const dataset = el.dataset; - const clientId = dataset.clientId; - const theme = dataset.theme || "dark"; - if (!clientId) { - throw new Error("Missing client-id"); - } - return { clientId, baseUrl, theme }; - } - - /** - * @param {URL} url - * @returns null | [string, string] - */ - function parseURL(url) { - try { - const hash = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash; - const code = url.searchParams.get("code"); - if (!hash || !code) { - return null; - } - return [hash, code]; - } catch { - // if this fails, invalid data -> return null - return null; - } - } - - async function renderFloatingBubble(loggedIn) { - const el = document.createElement("div"); - el.id = "tw-floating-bubble"; - el.style.position = "fixed"; - el.style.bottom = "24px"; - el.style.right = "24px"; - el.style.zIndex = "1000"; - el.style.width = "138px"; - el.style.height = "40px"; - el.style.backgroundColor = - globalSetup.theme === "dark" ? "#131418" : "#ffffff"; - el.style.color = globalSetup.theme === "dark" ? "white" : "black"; - el.style.borderRadius = "8px"; - el.style.placeItems = "center"; - el.style.fontSize = loggedIn ? "12px" : "12px"; - el.style.cursor = "pointer"; - el.style.overflow = "hidden"; - el.style.boxShadow = "1px 1px 10px rgba(0, 0, 0, 0.5)"; - el.style.display = "flex"; - el.style.alignItems = "center"; - el.style.justifyContent = "space-around"; - el.style.fontFamily = "sans-serif"; - el.style.gap = "8px"; - el.style.padding = "0px 8px"; - el.onclick = () => { - if (loggedIn) { - window.thirdweb.logout(); - } else { - window.thirdweb.login(); - } - }; - el.innerHTML = loggedIn ? await renderBlobbie() : renderThirdwebLogo(); - document.body.appendChild(el); - } - - function renderThirdwebLogo() { - const el = document.createElement("img"); - el.src = `${globalSetup.baseUrl}/logo.svg`; - el.style.height = "16px"; - el.style.objectFit = "contain"; - el.style.flexShrink = "0"; - el.style.marginLeft = "-4px"; - return `${el.outerHTML} Login`; - } - - async function renderBlobbie() { - const address = (await window.thirdweb.getUser()).address; - - function hexToNumber(hex) { - if (typeof hex !== "string") - throw new Error(`hex string expected, got ${typeof hex}`); - return hex === "" ? _0n : BigInt(`0x${hex}`); - } - - const COLOR_OPTIONS = [ - ["#fca5a5", "#b91c1c"], - ["#fdba74", "#c2410c"], - ["#fcd34d", "#b45309"], - ["#fde047", "#a16207"], - ["#a3e635", "#4d7c0f"], - ["#86efac", "#15803d"], - ["#67e8f9", "#0e7490"], - ["#7dd3fc", "#0369a1"], - ["#93c5fd", "#1d4ed8"], - ["#a5b4fc", "#4338ca"], - ["#c4b5fd", "#6d28d9"], - ["#d8b4fe", "#7e22ce"], - ["#f0abfc", "#a21caf"], - ["#f9a8d4", "#be185d"], - ["#fda4af", "#be123c"], - ]; - const colors = - COLOR_OPTIONS[ - Number(hexToNumber(address.slice(2, 4))) % COLOR_OPTIONS.length - ]; - const el = document.createElement("div"); - el.style.backgroundImage = `radial-gradient(ellipse at left bottom, ${colors[0]}, ${colors[1]})`; - el.style.width = "24px"; - el.style.height = "24px"; - el.style.borderRadius = "50%"; - el.style.flexShrink = "0"; - - return `${el.outerHTML}${address.slice(0, 6)}...${address.slice(-4)}`; - } -})(); diff --git a/apps/login/public/twl.min.js b/apps/login/public/twl.min.js deleted file mode 100644 index 126cda5e518..00000000000 --- a/apps/login/public/twl.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){let e=function e(){let t=document.currentScript;if(!t)throw Error("Could not find script element");let a=new URL(t.src).origin,n=t.dataset,l=n.clientId,s=n.theme||"dark";if(!l)throw Error("Missing client-id");return{clientId:l,baseUrl:a,theme:s}}(),t="tw.login:jwt",a="tw.login:code",n=function e(t){try{let a=t.hash.startsWith("#")?t.hash.slice(1):t.hash,n=t.searchParams.get("code");if(!a||!n)return null;return[a,n]}catch{return null}}(new URL(window.location));n&&2===n.length&&n[1]===localStorage.getItem(a)&&(window.location.hash="",window.location.search="",localStorage.setItem(t,n[0])),localStorage.removeItem(a);let l=localStorage.getItem(t);function s(){let t=window.crypto.getRandomValues(new Uint8Array(16)).join("");localStorage.setItem(a,t);let n=new URL(e.baseUrl);n.searchParams.set("code",t),n.searchParams.set("clientId",e.clientId),n.searchParams.set("redirect",window.location.origin+window.location.pathname),window.location.href=n.href}async function i(t){let a=document.createElement("div");a.id="tw-floating-bubble",a.style.position="fixed",a.style.bottom="24px",a.style.right="24px",a.style.zIndex="1000",a.style.width="138px",a.style.height="40px",a.style.backgroundColor="dark"===e.theme?"#131418":"#ffffff",a.style.color="dark"===e.theme?"white":"black",a.style.borderRadius="8px",a.style.placeItems="center",a.style.fontSize="12px",a.style.cursor="pointer",a.style.overflow="hidden",a.style.boxShadow="1px 1px 10px rgba(0, 0, 0, 0.5)",a.style.display="flex",a.style.alignItems="center",a.style.justifyContent="space-around",a.style.fontFamily="sans-serif",a.style.gap="8px",a.style.padding="0px 8px",a.onclick=()=>{t?window.thirdweb.logout():window.thirdweb.login()},a.innerHTML=t?await r():function t(){let a=document.createElement("img");return a.src=`${e.baseUrl}/logo.svg`,a.style.height="16px",a.style.objectFit="contain",a.style.flexShrink="0",a.style.marginLeft="-4px",`${a.outerHTML} Login`}(),document.body.appendChild(a)}async function r(){let e=(await window.thirdweb.getUser()).address,t=[["#fca5a5","#b91c1c"],["#fdba74","#c2410c"],["#fcd34d","#b45309"],["#fde047","#a16207"],["#a3e635","#4d7c0f"],["#86efac","#15803d"],["#67e8f9","#0e7490"],["#7dd3fc","#0369a1"],["#93c5fd","#1d4ed8"],["#a5b4fc","#4338ca"],["#c4b5fd","#6d28d9"],["#d8b4fe","#7e22ce"],["#f0abfc","#a21caf"],["#f9a8d4","#be185d"],["#fda4af","#be123c"],],a=t[Number(function e(t){if("string"!=typeof t)throw Error(`hex string expected, got ${typeof t}`);return""===t?_0n:BigInt(`0x${t}`)}(e.slice(2,4)))%t.length],n=document.createElement("div");return n.style.backgroundImage=`radial-gradient(ellipse at left bottom, ${a[0]}, ${a[1]})`,n.style.width="24px",n.style.height="24px",n.style.borderRadius="50%",n.style.flexShrink="0",`${n.outerHTML}${e.slice(0,6)}...${e.slice(-4)}`}l?(window.thirdweb={isLoggedIn:!0,async getUser(){let a=await fetch(`${e.baseUrl}/api/user`,{headers:{Authorization:`Bearer ${localStorage.getItem(t)}`}});return a.json()},logout(){window.localStorage.removeItem(t),window.location.reload()}},i(!0)):(window.thirdweb={login:s,isLoggedIn:!1},i(!1))}(); \ No newline at end of file diff --git a/apps/login/src/actions/create-code.ts b/apps/login/src/actions/create-code.ts new file mode 100644 index 00000000000..09376f43002 --- /dev/null +++ b/apps/login/src/actions/create-code.ts @@ -0,0 +1,63 @@ +"use server"; +import "server-only"; +import type { PermissionState } from "@/components/permission-card"; +import { SignJWT } from "jose"; +import { redirect } from "next/navigation"; +import { getLoginConfig } from "../api/login/config"; +import { getKeyInfo } from "../lib/keys"; +import type { Oauth2AuthorizeParams, Oauth2CodePayload } from "../lib/oauth"; +import { getValidSessionToken } from "../lib/siwe-server"; + +export async function createCode(options: { + permissions: PermissionState; + oauthParams: Oauth2AuthorizeParams; +}) { + // 1) check if the user is logged in + const jwt = await getValidSessionToken(); + if (!jwt) { + return { + success: false, + error: "User is not logged in", + }; + } + + // 2) get the config for the client_id + const config = await getLoginConfig(options.oauthParams.client_id); + if (!config) { + return { + success: false, + error: "Invalid client_id", + }; + } + + // 3) TODO: validate redirect_uri against the config + + // 4) get the key info + const { privateKey, alg } = await getKeyInfo(); + + // 4) Create the authorization code as a short-lived JWT + const now = Math.floor(Date.now() / 1000); + const codeExpiresInSeconds = 60; // 60 seconds + + const codeJwt = await new SignJWT({ + sub: jwt.sub, + scope: options.oauthParams.scope ?? "", + client_id: options.oauthParams.client_id, + code_challenge: options.oauthParams.code_challenge, + code_challenge_method: options.oauthParams.code_challenge_method, + redirect_uri: options.oauthParams.redirect_uri, + permissions: options.permissions, + } satisfies Oauth2CodePayload) + .setProtectedHeader({ alg: alg, typ: "JWT" }) + .setIssuedAt(now) + .setExpirationTime(now + codeExpiresInSeconds) + .sign(privateKey); + + // 4) Redirect back to the client's redirectUri with the code and state + const url = new URL(options.oauthParams.redirect_uri); + url.searchParams.set("code", codeJwt); + url.searchParams.set("state", options.oauthParams.state); + + // redirect the user to the client's redirectUri with the code and state + redirect(url.toString()); +} diff --git a/apps/login/src/actions/siwe.ts b/apps/login/src/actions/siwe.ts new file mode 100644 index 00000000000..f499ab38d4c --- /dev/null +++ b/apps/login/src/actions/siwe.ts @@ -0,0 +1,58 @@ +"use server"; +import "server-only"; + +import { initDevMode } from "@/lib/dev-mode"; +import { cookies } from "next/headers"; +import { getAddress } from "thirdweb"; +import type { + GenerateLoginPayloadParams, + VerifyLoginPayloadParams, +} from "thirdweb/auth"; +import { + SESSION_COOKIE_NAME, + SESSION_EXPIRATION_TIME_SECONDS, + getValidSessionToken, +} from "../lib/siwe-server"; +import { SIWE_AUTH } from "../lib/siwe-server"; + +initDevMode(); + +export async function getLoginPayload(options: GenerateLoginPayloadParams) { + return SIWE_AUTH.generatePayload(options); +} + +export async function doLogin(params: VerifyLoginPayloadParams) { + const result = await SIWE_AUTH.verifyPayload(params); + if (!result.valid) { + throw new Error(result.error); + } + + const cookieStore = await cookies(); + + // create a session token + const token = await SIWE_AUTH.generateJWT(result); + cookieStore.set(SESSION_COOKIE_NAME, token, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + path: "/", + maxAge: SESSION_EXPIRATION_TIME_SECONDS, + }); +} + +export async function doLogout() { + const cookieStore = await cookies(); + cookieStore.delete(SESSION_COOKIE_NAME); +} + +export async function isLoggedIn(address: string) { + const jwt = await getValidSessionToken(); + if (!jwt) { + return false; + } + + try { + return getAddress(jwt.sub) === getAddress(address); + } catch { + return false; + } +} diff --git a/apps/login/src/api/login/config.ts b/apps/login/src/api/login/config.ts new file mode 100644 index 00000000000..9f8744985e1 --- /dev/null +++ b/apps/login/src/api/login/config.ts @@ -0,0 +1,131 @@ +import "server-only"; +import type { InAppWalletAuth } from "thirdweb/wallets"; +import type { Permission } from "../../components/permission-card"; + +export async function getLoginConfig(clientId: string) { + if (clientId === "demo") { + return DEMO_ENVIRONMENT; + } + // TODO: implement fetch for config from API server + return DEFAULT_CONFIG; +} + +export type LoginConfig = { + id: string; + name: string; + logo: string; + logoLink: string; + chainId: number; + authOptions: Exclude[]; + permissions: Permission[]; + sessionKeySignerAddress: string; +}; + +const DEMO_ENVIRONMENT: LoginConfig = { + id: "demo_app", + name: "thirdweb demo app", + logo: "https://thirdweb.com/brand/thirdweb-icon.svg", + logoLink: "https://thirdweb.com", + chainId: 84532, + sessionKeySignerAddress: "0x6f700ba0258886411D2536399624EAa7158d1742", + authOptions: [ + "google", + "apple", + "facebook", + "discord", + "line", + "x", + "coinbase", + "farcaster", + "telegram", + "github", + "twitch", + "steam", + "guest", + "email", + "phone", + "passkey", + "wallet", + ], + permissions: [ + { + id: "identity:read", + type: "toggle", + name: "User Identity", + description: "Access to read your identity information.", + mandatory: true, + initialState: true, + }, + { + id: "contracts:write", + type: "toggle", + name: "All Contracts", + description: "Access to write to all smart contracts on your behalf.", + mandatory: true, + initialState: true, + }, + { + id: "native:spend", + type: "number", + name: "Native Spend Limit", + description: + "Set the maximum amount of native currency you can spend on your behalf.", + mandatory: false, + initialState: 5, + min: 0, + step: 0.1, + }, + { + id: "expiration", + type: "date", + name: "Expiration", + description: "The expiration date of these permissions.", + mandatory: false, + initialState: addToDate(new Date(), 30), + }, + ], +}; + +const DEFAULT_CONFIG: LoginConfig = { + id: "default", + name: "thirdweb", + logo: "https://thirdweb.com/brand/thirdweb-icon.svg", + logoLink: "https://thirdweb.com", + chainId: 84532, + sessionKeySignerAddress: "0x6f700ba0258886411D2536399624EAa7158d1742", + authOptions: [ + "google", + "apple", + "facebook", + "x", + "email", + "phone", + "passkey", + "wallet", + "guest", + ], + permissions: [ + { + id: "identity:read", + type: "toggle", + name: "User Identity", + description: "Access to read your identity information.", + mandatory: true, + initialState: true, + }, + { + id: "contracts:write", + type: "toggle", + name: "Interact with Contracts", + description: "Access to write to all smart contracts on your behalf.", + mandatory: true, + initialState: true, + }, + ], +}; + +function addToDate(date: Date, days: number) { + const newDate = new Date(date); + newDate.setDate(newDate.getDate() + days); + return newDate; +} diff --git a/apps/login/src/app/api/jwks/route.ts b/apps/login/src/app/api/jwks/route.ts new file mode 100644 index 00000000000..00de5eabd2e --- /dev/null +++ b/apps/login/src/app/api/jwks/route.ts @@ -0,0 +1,18 @@ +import { exportJWK } from "jose"; +import { NextResponse } from "next/server"; +import { getKeyInfo } from "../../../lib/keys"; + +export async function GET() { + const { publicKey, kid } = await getKeyInfo(); + + // Convert to JWK + const jwk = await exportJWK(publicKey); + + // Add the kid to the JWK + jwk.kid = kid; + + return NextResponse.json({ + // Return the JWK as array of keys (today only the one key exists) + keys: [jwk], + }); +} diff --git a/apps/login/src/app/api/token/route.ts b/apps/login/src/app/api/token/route.ts new file mode 100644 index 00000000000..b7f5ce4bc77 --- /dev/null +++ b/apps/login/src/app/api/token/route.ts @@ -0,0 +1,269 @@ +import crypto from "node:crypto"; +import { SignJWT, jwtVerify } from "jose"; +import { type NextRequest, NextResponse } from "next/server"; +import { getKeyInfo } from "../../../lib/keys"; +import type { + Oauth2AuthToken, + Oauth2AuthTokenRequest, + Oauth2CodePayload, + Oauth2RefreshToken, + Oauth2RefreshTokenRequest, +} from "../../../lib/oauth"; + +const ACCESS_TOKEN_EXPIRATION_TIME = 900; // 15 minutes +const REFRESH_TOKEN_EXPIRATION_TIME = 60 * 60 * 24 * 30; // 30 days + +// Helper for PKCE S256 +function sha256(input: string) { + return crypto.createHash("sha256").update(input).digest(); +} + +// biome-ignore lint/style/noRestrictedGlobals: is ok +function base64url(buffer: Buffer | Uint8Array) { + return buffer + .toString("base64") + .replace(/=/g, "") + .replace(/\+/g, "-") + .replace(/\//g, "_"); +} + +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type", +}; + +export async function OPTIONS() { + return NextResponse.json(null, { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type", + }, + }); +} + +export async function POST(req: NextRequest) { + try { + // Retrieve form data + const body = (await req.json()) as + | Oauth2AuthTokenRequest + | Oauth2RefreshTokenRequest; + const grantType = body.grant_type; + + switch (grantType) { + case "authorization_code": + return handleAuthorizationCode(body); + case "refresh_token": + return handleRefreshToken(body); + default: + return NextResponse.json( + { error: "unsupported_grant_type" }, + { + status: 400, + headers: corsHeaders, + }, + ); + } + } catch (error) { + console.error(error); + return NextResponse.json( + { error: "server_error" }, + { status: 500, headers: corsHeaders }, + ); + } +} + +async function handleAuthorizationCode(body: Oauth2AuthTokenRequest) { + const code = body.code || ""; + const codeVerifier = body.code_verifier || ""; + const redirectUri = body.redirect_uri || ""; + const clientId = body.client_id || ""; + + // Basic checks + if (!code || !codeVerifier || !redirectUri || !clientId) { + return NextResponse.json( + { error: "invalid_request" }, + { status: 400, headers: corsHeaders }, + ); + } + + const keyInfo = await getKeyInfo(); + + // 1) Verify the code JWT + let payload: Oauth2CodePayload; + try { + const { payload: verifiedPayload } = await jwtVerify( + code, + keyInfo.publicKey, + ); + payload = verifiedPayload; + + // verify the redirect_uri + if (payload.redirect_uri !== redirectUri) { + return NextResponse.json( + { error: "invalid_grant" }, + { status: 400, headers: corsHeaders }, + ); + } + // verify the client_id + if (payload.client_id !== clientId) { + return NextResponse.json( + { error: "invalid_grant" }, + { status: 400, headers: corsHeaders }, + ); + } + } catch (err) { + console.error("failed to verify code", err); + return NextResponse.json( + { error: "invalid_grant" }, + { status: 400, headers: corsHeaders }, + ); + } + + // 2) Validate PKCE + const { code_challenge, code_challenge_method } = payload; + if (!code_challenge) { + return NextResponse.json( + { error: "invalid_grant" }, + { status: 400, headers: corsHeaders }, + ); + } + + switch (code_challenge_method) { + case "S256": + { + const expectedChallenge = base64url(sha256(codeVerifier)); + console.log( + "expectedChallenge", + expectedChallenge, + "code_challenge", + code_challenge, + codeVerifier, + ); + if (expectedChallenge !== code_challenge) { + return NextResponse.json( + { error: "invalid_grant" }, + { status: 400, headers: corsHeaders }, + ); + } + } + break; + case "plain": { + if (codeVerifier !== code_challenge) { + return NextResponse.json( + { error: "invalid_grant" }, + { status: 400, headers: corsHeaders }, + ); + } + break; + } + default: { + return NextResponse.json( + { error: "invalid_request" }, + { status: 400, headers: corsHeaders }, + ); + } + } + + // 3) Issue short-lived access token + + const now = Math.floor(Date.now() / 1000); + const accessTokenExpiresIn = 900; // e.g., 15 minutes + + const JWT_PAYLOAD: Oauth2AuthToken = { + sub: payload.sub, + scope: payload.scope, + permissions: payload.permissions, + data: {}, + }; + + const accessToken = await new SignJWT(JWT_PAYLOAD) + .setProtectedHeader({ alg: keyInfo.alg, typ: "JWT" }) + .setIssuedAt(now) + .setExpirationTime(now + accessTokenExpiresIn) + .sign(keyInfo.privateKey); + + // 4) Issue refresh token (longer-lived) + // TODO: consider storing somewhere for revocation/rotation + const refreshTokenExpiresIn = 60 * 60 * 24 * 30; // 30 days + const refreshToken = await new SignJWT({ + originalData: JWT_PAYLOAD, + } satisfies Oauth2RefreshToken) + .setProtectedHeader({ alg: keyInfo.alg, typ: "JWT" }) + .setIssuedAt(now) + .setExpirationTime(now + refreshTokenExpiresIn) + .sign(keyInfo.privateKey); + + // 5) Return tokens + return NextResponse.json( + { + token_type: "Bearer", + access_token: accessToken, + expires_in: accessTokenExpiresIn, + refresh_token: refreshToken, + }, + { + headers: corsHeaders, + }, + ); +} + +async function handleRefreshToken(body: Oauth2RefreshTokenRequest) { + const refreshToken = body.refresh_token || ""; + if (!refreshToken) { + return NextResponse.json( + { error: "invalid_request" }, + { status: 400, headers: corsHeaders }, + ); + } + + const keyInfo = await getKeyInfo(); + // 1) Verify the refresh token JWT + let payload: Oauth2RefreshToken; + try { + const { payload: verifiedPayload } = await jwtVerify( + refreshToken, + keyInfo.publicKey, + ); + payload = verifiedPayload; + // If expired, jwtVerify will throw + } catch (err) { + console.error("failed to verify refresh token", err); + return NextResponse.json( + { error: "invalid_grant" }, + { status: 400, headers: corsHeaders }, + ); + } + + // 2) Issue new short-lived access token + const now = Math.floor(Date.now() / 1000); + + const newAccessToken = await new SignJWT(payload.originalData) + .setProtectedHeader({ alg: keyInfo.alg, typ: "JWT" }) + .setIssuedAt(now) + .setExpirationTime(now + ACCESS_TOKEN_EXPIRATION_TIME) + .sign(keyInfo.privateKey); + + // 3) Optionally rotate refresh token + // TODO: do not need to to rotate this EVERY time, but also does not hurt to do, so carry on + const newRefreshToken = await new SignJWT({ + originalData: payload.originalData, + } satisfies Oauth2RefreshToken) + .setProtectedHeader({ alg: keyInfo.alg, typ: "JWT" }) + .setIssuedAt(now) + .setExpirationTime(now + REFRESH_TOKEN_EXPIRATION_TIME) + .sign(keyInfo.privateKey); + + return NextResponse.json( + { + token_type: "Bearer", + access_token: newAccessToken, + expires_in: ACCESS_TOKEN_EXPIRATION_TIME, + refresh_token: newRefreshToken, + }, + { + headers: corsHeaders, + }, + ); +} diff --git a/apps/login/src/app/api/user/route.ts b/apps/login/src/app/api/user/route.ts deleted file mode 100644 index 00a6e92115b..00000000000 --- a/apps/login/src/app/api/user/route.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { type NextRequest, NextResponse } from "next/server"; -import { verifyJWT } from "../../authorization/jwt"; - -export const GET = async (req: NextRequest) => { - const jwt = req.headers.get("Authorization")?.split("Bearer ")[1]; - if (!jwt) { - return NextResponse.json( - { - message: "No JWT provided", - }, - { - status: 401, - }, - ); - } - - try { - const verifiedPayload = await verifyJWT(jwt); - return NextResponse.json({ - address: verifiedPayload.sub, - }); - } catch (e) { - console.error("failed", e); - return NextResponse.json( - { - message: "Invalid JWT", - }, - { - status: 401, - }, - ); - } -}; diff --git a/apps/login/src/app/authorization/jwt.ts b/apps/login/src/app/authorization/jwt.ts deleted file mode 100644 index 0d5cd3d7113..00000000000 --- a/apps/login/src/app/authorization/jwt.ts +++ /dev/null @@ -1,58 +0,0 @@ -"server only"; - -import "server-only"; -import { createThirdwebClient } from "thirdweb"; -import { verifyEOASignature } from "thirdweb/auth"; -import { decodeJWT, encodeJWT, stringify } from "thirdweb/utils"; -import { privateKeyToAccount, randomPrivateKey } from "thirdweb/wallets"; - -const client = createThirdwebClient({ - clientId: "e9ba48c289e0cc3d06a23bfd370cc111", -}); -const privateKey = process.env.THIRDWEB_ADMIN_PRIVATE_KEY || randomPrivateKey(); - -if (!privateKey) { - throw new Error("Missing THIRDWEB_ADMIN_PRIVATE_KEY"); -} - -const serverAccount = privateKeyToAccount({ - client, - privateKey: privateKey, -}); - -export async function signJWT(data: { - address: string; - sessionKeySignerAddress: string; - code: string; -}) { - "use server"; - return await encodeJWT({ - account: serverAccount, - payload: { - iss: serverAccount.address, - sub: data.address, - aud: data.sessionKeySignerAddress, - exp: new Date(Date.now() + 1000 * 60 * 60), - nbf: new Date(), - iat: new Date(), - jti: data.code, - }, - }); -} - -export async function verifyJWT(jwt: string) { - const { payload, signature } = decodeJWT(jwt); - - console.log("payload", payload); - console.log("signature", signature); - // verify the signature - const verified = await verifyEOASignature({ - message: stringify(payload), - signature, - address: serverAccount.address, - }); - if (!verified) { - throw new Error("Invalid JWT signature"); - } - return payload; -} diff --git a/apps/login/src/app/authorize/page.tsx b/apps/login/src/app/authorize/page.tsx new file mode 100644 index 00000000000..b9fcc018410 --- /dev/null +++ b/apps/login/src/app/authorize/page.tsx @@ -0,0 +1,92 @@ +/* eslint-disable @next/next/no-img-element */ +import { getLoginConfig } from "@/api/login/config"; +import { LoginForm } from "@/components/login-form"; +import { ModeToggle } from "@/components/theme-toggle"; +import { notFound, redirect } from "next/navigation"; +import type { Oauth2AuthorizeParams } from "../../lib/oauth"; + +function validateParams(searchParams: Oauth2AuthorizeParams) { + if (!searchParams.redirect_uri) { + // can't redirect so I guess it's 404 + // TODO: show an error message + notFound(); + } + if (searchParams.response_type !== "code") { + redirect( + `${searchParams.redirect_uri}?error=invalid_request&error_description=Invalid response type`, + ); + } + if (!searchParams.client_id) { + redirect( + `${searchParams.redirect_uri}?error=invalid_request&error_description=Missing client_id`, + ); + } + + if (!searchParams.code_challenge) { + redirect( + `${searchParams.redirect_uri}?error=invalid_request&error_description=Missing code_challenge`, + ); + } + + if ( + searchParams.code_challenge_method !== "S256" && + searchParams.code_challenge_method !== "plain" + ) { + redirect( + `${searchParams.redirect_uri}?error=invalid_request&error_description=Invalid code challenge method`, + ); + } +} + +export default async function LoginPage(props: { + searchParams: Promise; +}) { + const searchParams = await props.searchParams; + // validate params + validateParams(searchParams); + + // get the config + const config = await getLoginConfig(searchParams.client_id); + + // if no config -> invalid client_id + if (!config) { + return ( +
+
+

Invalid Client Id

+
+
+ ); + } + + return ( +
+
+ brand content +
+
+ +
+
+ +
+
+
+
+ ); +} diff --git a/apps/login/src/app/components/LoginPageInner.tsx b/apps/login/src/app/components/LoginPageInner.tsx deleted file mode 100644 index ebbeb3fa72b..00000000000 --- a/apps/login/src/app/components/LoginPageInner.tsx +++ /dev/null @@ -1,186 +0,0 @@ -"use client"; - -import { useSearchParams } from "next/navigation"; -import { createThirdwebClient, getContract } from "thirdweb"; -import { baseSepolia } from "thirdweb/chains"; -import { addSessionKey, isActiveSigner } from "thirdweb/extensions/erc4337"; -import { - AccountAddress, - AccountBlobbie, - AccountProvider, - ConnectEmbed, - TransactionButton, - useActiveAccount, -} from "thirdweb/react"; -import { isContractDeployed, shortenAddress } from "thirdweb/utils"; - -const sessionKeySignerAddress = "0x6f700ba0258886411D2536399624EAa7158d1742"; - -export function LoginPageInner({ - generateJWT, -}: { - generateJWT: (opts: { - address: string; - sessionKeySignerAddress: string; - code: string; - }) => Promise; -}) { - const account = useActiveAccount(); - - const searchParams = useSearchParams(); - const code = searchParams.get("code"); - // const clientId = searchParams.get("clientId"); - const redirect = searchParams.get("redirect"); - - const client = createThirdwebClient({ - clientId: "e9ba48c289e0cc3d06a23bfd370cc111", - }); - return ( -
- {account ? ( -
- -
- -
- -
-
-
-
-
-

Grant Permissions

-

- App.xyz is asking you to grant it the following - permissions: -

-
-
-
    -
  • - Read your wallet address. -
  • -
-
-
    -
  • - ⚠️Interact with all{" "} - contracts on Base Sepolia -
  • -
  • - ⚠️Spend all your - funds on Base Sepolia -
  • -
  • - 📆Granted until{" "} - 02/02/2025 -
  • -
-
-
- - { - if (!account) { - throw new Error("No account found"); - } - if (!code) { - throw new Error("No code found"); - } - if (!redirect) { - throw new Error("No redirect found"); - } - const accountContract = getContract({ - address: account.address, - // hard coded for now - chain: baseSepolia, - client, - }); - let hasSessionKey = false; - // check if already added - const accountDeployed = - await isContractDeployed(accountContract); - if (accountDeployed) { - hasSessionKey = await isActiveSigner({ - contract: accountContract, - signer: sessionKeySignerAddress, - }); - } - // if not added, send tx to add the session key - if (!hasSessionKey) { - return addSessionKey({ - account, - contract: accountContract, - sessionKeyAddress: sessionKeySignerAddress, - // hard coded for now - permissions: { approvedTargets: "*" }, - }); - } - throw "already-added"; - }} - onError={async (e) => { - if (!code) { - throw new Error("No code found"); - } - if (!redirect) { - throw new Error("No redirect found"); - } - if (typeof e === "string" && e === "already-added") { - // sign jwt - const jwt = await generateJWT({ - address: account.address, - sessionKeySignerAddress, - code, - }); - // redirect - window.location.href = `${redirect}?code=${code}#${jwt}`; - } else { - console.error(e); - } - }} - onTransactionConfirmed={async () => { - if (!code) { - throw new Error("No code found"); - } - if (!redirect) { - throw new Error("No redirect found"); - } - // sign jwt - const jwt = await generateJWT({ - address: account.address, - sessionKeySignerAddress, - code, - }); - // redirect - window.location.href = `${redirect}?code=${code}#${jwt}`; - }} - > - Approve - -
-
- ) : ( - - )} -
- ); -} diff --git a/apps/login/src/app/globals.css b/apps/login/src/app/globals.css index d94e43201c0..ad970d04b5f 100644 --- a/apps/login/src/app/globals.css +++ b/apps/login/src/app/globals.css @@ -2,162 +2,114 @@ @tailwind components; @tailwind utilities; -html { - /* scroll-behavior: smooth; */ - font-feature-settings: "cv02", "cv03", "cv04", "cv11"; - font-feature-settings: "rlig" 1, "calt" 0; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - font-variation-settings: normal; - -webkit-text-size-adjust: 100%; - -webkit-tap-highlight-color: transparent; -} - @layer base { :root { + --background: 0 0% 100%; + + --foreground: 0 0% 3.9%; + + --card: 0 0% 100%; + + --card-foreground: 0 0% 3.9%; + + --popover: 0 0% 100%; + + --popover-foreground: 0 0% 3.9%; + + --primary: 0 0% 9%; + + --primary-foreground: 0 0% 98%; + + --secondary: 0 0% 96.1%; + + --secondary-foreground: 0 0% 9%; + + --muted: 0 0% 96.1%; + + --muted-foreground: 0 0% 45.1%; + + --accent: 0 0% 96.1%; + + --accent-foreground: 0 0% 9%; + + --destructive: 0 84.2% 60.2%; + + --destructive-foreground: 0 0% 98%; + + --border: 0 0% 89.8%; + + --input: 0 0% 89.8%; + + --ring: 0 0% 3.9%; + + --chart-1: 12 76% 61%; + + --chart-2: 173 58% 39%; + + --chart-3: 197 37% 24%; + + --chart-4: 43 74% 66%; + + --chart-5: 27 87% 67%; + --radius: 0.5rem; - --sticky-top-height: 70px; } + .dark { + --background: 0 0% 3.9%; - :root, - [data-theme="dark"] { - /* bg - neutral */ - --background: 0 0% 0%; - --card: 0 0% 3.92%; - --popover: 0 0% 0%; - --secondary: 0 0% 11%; - --muted: 0 0% 11%; - --accent: 0 0% 11%; - --inverted: 0 0% 100%; - - /* bg - colorful */ - --primary: 221 83% 54%; - --destructive: 360 72% 51%; - - /* Text */ --foreground: 0 0% 98%; + + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; - --primary-foreground: 0 0% 100%; + + --primary: 0 0% 98%; + + --primary-foreground: 0 0% 9%; + + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; - --muted-foreground: 0 0% 63%; - --accent-foreground: 0 0% 98%; - --destructive-foreground: 0 0% 100%; - --link-foreground: 215.88 100% 65%; - --warning-text: 38 92% 50%; - --destructive-text: 360 72% 55%; - --success-text: 142 75% 50%; - --inverted-foreground: 0 0% 0%; - - /* Borders */ - --border: 0 0% 15%; - --active-border: 0 0% 22%; - --ring: 0 0% 30%; - --input: 0 0% 15%; - } - [data-theme="light"] { - /* bg - neutral */ - --background: 0 0% 98%; - --popover: 0 0% 100%; - --card: 0 0% 100%; - --secondary: 0 0% 90%; - --muted: 0 0% 93%; - --accent: 0 0% 93%; - --inverted: 0 0 0%; - - /* bg - colorful */ - --primary: 221 83% 54%; - --destructive: 360 72% 51%; - - /* Text */ - --foreground: 0 0% 4%; - --card-foreground: 0 0% 4%; - --popover-foreground: 240 10% 4%; - --primary-foreground: 0 0% 100%; - --secondary-foreground: 0 0% 4%; - --muted-foreground: 0 0% 40%; - --accent-foreground: 0 0% 9%; - --destructive-foreground: 0 0% 100%; - --inverted-foreground: 0 0% 100%; - --link-foreground: 221.21deg 83.19% 53.33%; - --success-text: 142.09 70.56% 35.29%; - --warning-text: 38 92% 40%; - --destructive-text: 357.15deg 100% 68.72%; - - /* Borders */ - --border: 0 0% 85%; - --active-border: 0 0% 70%; - --input: 0 0% 85%; - --ring: 0 0% 80%; - } -} + --muted: 0 0% 14.9%; -/* If no data-theme is added in body, its in dark theme */ -body:not([data-theme="light"]) .light-only { - display: none; -} + --muted-foreground: 0 0% 63.9%; -[data-theme="light"] .dark-only { - display: none; -} + --accent: 0 0% 14.9%; -code span { - color: var(--code-dark-color); -} + --accent-foreground: 0 0% 98%; -body[data-theme="light"] code span { - color: var(--code-light-color); -} + --destructive: 0 62.8% 30.6%; -/* @layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - } -} */ + --destructive-foreground: 0 0% 98%; -.styled-scrollbar::-webkit-scrollbar { - width: 0.5rem; - height: 0.5rem; -} + --border: 0 0% 14.9%; -@media (max-width: 640px) { - .styled-scrollbar::-webkit-scrollbar { - width: 0; - height: 0; - } -} + --input: 0 0% 14.9%; -.styled-scrollbar::-webkit-scrollbar-thumb { - border-radius: 0.5rem; - transition: color 200ms ease; - background: var(--border); -} + --ring: 0 0% 83.1%; -.styled-scrollbar::-webkit-scrollbar-thumb:hover { - background: hsl(var(--foreground)); -} + --chart-1: 220 70% 50%; -.styled-scrollbar::-webkit-scrollbar-track { - background-color: transparent; -} + --chart-2: 160 60% 45%; -button { - -webkit-tap-highlight-color: transparent; -} + --chart-3: 30 80% 55%; -::selection { - background: hsl(var(--foreground)); - color: hsl(var(--background)); -} + --chart-4: 280 65% 60%; -.hide-scrollbar { - scrollbar-width: none; /* Firefox */ + --chart-5: 340 75% 55%; + } } -.hide-scrollbar::-webkit-scrollbar { - display: none; /* Safari and Chrome */ +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } } diff --git a/apps/login/src/app/layout.tsx b/apps/login/src/app/layout.tsx index 66a013de298..0dd299af731 100644 --- a/apps/login/src/app/layout.tsx +++ b/apps/login/src/app/layout.tsx @@ -1,9 +1,9 @@ import type { Metadata } from "next"; import { Fira_Code, Inter } from "next/font/google"; -import { Suspense } from "react"; - import "./globals.css"; -import { Providers } from "./providers"; +import { Toaster } from "@/components/ui/sonner"; +import { Provider } from "@/providers/thirdweb-provider"; +import { ThemeProvider } from "../providers/theme-provider"; const sansFont = Inter({ subsets: ["latin"], @@ -18,7 +18,7 @@ const monoFont = Fira_Code({ }); export const metadata: Metadata = { - title: "login with thirdweb", + title: "thirdweb login", }; export default function RootLayout({ @@ -27,13 +27,17 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - - - {children} - + + + + {children} + + ); diff --git a/apps/login/src/app/page.tsx b/apps/login/src/app/page.tsx deleted file mode 100644 index 2802c6068d2..00000000000 --- a/apps/login/src/app/page.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { signJWT } from "./authorization/jwt"; -import { LoginPageInner } from "./components/LoginPageInner"; - -export default function Page() { - return ; -} diff --git a/apps/login/src/app/providers.tsx b/apps/login/src/app/providers.tsx deleted file mode 100644 index 5b30433c549..00000000000 --- a/apps/login/src/app/providers.tsx +++ /dev/null @@ -1,11 +0,0 @@ -"use client"; - -import { ThirdwebProvider } from "thirdweb/react"; - -export const Providers = ({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) => { - return {children}; -}; diff --git a/apps/login/src/components/login-form.tsx b/apps/login/src/components/login-form.tsx new file mode 100644 index 00000000000..3ee105f4002 --- /dev/null +++ b/apps/login/src/components/login-form.tsx @@ -0,0 +1,205 @@ +"use client"; +import { thirdwebClient } from "@/lib/thirdweb-client"; +import { useTheme } from "next-themes"; +import { redirect } from "next/navigation"; +import { toast } from "sonner"; +import { getContract, sendAndConfirmTransaction } from "thirdweb"; +import type { Chain, ThirdwebClient } from "thirdweb"; +import { defineChain } from "thirdweb/chains"; +import { addSessionKey, isActiveSigner } from "thirdweb/extensions/erc4337"; +import { + ConnectButton, + ConnectEmbed, + useActiveAccount, + useActiveWallet, + useSiweAuth, +} from "thirdweb/react"; +import { isContractDeployed } from "thirdweb/utils"; +import { type Account, inAppWallet } from "thirdweb/wallets"; +import { createCode } from "../actions/create-code"; +import { + doLogin, + doLogout, + getLoginPayload, + isLoggedIn, +} from "../actions/siwe"; +import type { LoginConfig } from "../api/login/config"; +import type { Oauth2AuthorizeParams } from "../lib/oauth"; +import { PermissionCard, type PermissionState } from "./permission-card"; +import { Card, CardContent, CardHeader, CardTitle } from "./ui/card"; + +export function LoginForm(props: { + config: LoginConfig; + oauthParams: Oauth2AuthorizeParams; +}) { + const { resolvedTheme } = useTheme(); + + const activeWallet = useActiveWallet(); + const activeAccount = useActiveAccount(); + + const siweAuth = useSiweAuth(activeWallet, activeAccount, { + getLoginPayload: getLoginPayload, + doLogin: doLogin, + doLogout: doLogout, + isLoggedIn: isLoggedIn, + }); + + // render the logged in state + if (activeAccount && siweAuth.isLoggedIn) { + return ( +
+ + + Your Account + + +
+ +
+
+
+ { + const t = toast.loading("Adding session key...", { + dismissible: false, + }); + try { + await ensureSessionKey({ + account: activeAccount, + client: thirdwebClient, + chain: defineChain(props.config.chainId), + sessionKeySignerAddress: props.config.sessionKeySignerAddress, + permissions, + }); + + toast.loading("Finishing up...", { + id: t, + }); + + await createCode({ + permissions, + oauthParams: props.oauthParams, + }); + + toast.success("Login successful", { + id: t, + }); + } catch (err) { + console.error(err); + toast.error("Failed to add session key", { + id: t, + }); + } + }} + onDeny={() => { + redirect(props.oauthParams.redirect_uri); + }} + /> +
+ ); + } + + return ( + + ); +} + +async function ensureSessionKey(options: { + account: Account; + client: ThirdwebClient; + chain: Chain; + sessionKeySignerAddress: string; + permissions: PermissionState; +}) { + const accountContract = getContract({ + address: options.account.address, + chain: options.chain, + client: options.client, + }); + + // check if already added + const accountDeployed = await isContractDeployed(accountContract); + if (accountDeployed) { + if ( + await isActiveSigner({ + contract: accountContract, + signer: options.sessionKeySignerAddress, + }) + ) { + return { + success: true, + message: "Session key already added", + transaction: null, + }; + } + } + // if not added, send tx to add the session key + + const tx = await sendAndConfirmTransaction({ + account: options.account, + transaction: addSessionKey({ + account: options.account, + contract: accountContract, + sessionKeyAddress: options.sessionKeySignerAddress, + // hard coded for now + permissions: { + approvedTargets: + typeof options.permissions["contracts:write"] === "boolean" + ? "*" + : options.permissions["contracts:write"], + permissionEndTimestamp: options.permissions.expiration, + nativeTokenLimitPerTransaction: options.permissions["native:spend"], + }, + }), + }); + + return { + success: true, + message: "Session key added", + transaction: tx, + }; +} diff --git a/apps/login/src/components/permission-card.tsx b/apps/login/src/components/permission-card.tsx new file mode 100644 index 00000000000..8626fbba550 --- /dev/null +++ b/apps/login/src/components/permission-card.tsx @@ -0,0 +1,210 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import {} from "@/components/ui/select"; +import { Separator } from "@/components/ui/separator"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { Lock } from "lucide-react"; +import { useState } from "react"; + +type IdentityReadScope = { + id: "identity:read"; + type: "toggle"; + name: string; + description: string; + mandatory: true; + initialState: true; +}; + +// type ContractWriteScope = { +// id: "contracts:write"; +// type: "list"; +// name: string; +// description: string; +// mandatory: boolean; +// initialState: string[]; +// }; + +type AllContractsWriteScope = { + id: "contracts:write"; + type: "toggle"; + name: string; + description: string; + mandatory: true; + initialState: true; +}; + +type NativeSpend = { + id: "native:spend"; + type: "number"; + name: string; + description: string; + mandatory: boolean; + initialState: number; + min?: number; + max?: number; + step?: number; +}; + +type ExpirationScope = { + id: "expiration"; + type: "date"; + name: string; + description: string; + mandatory: boolean; + initialState: Date; +}; + +export type Permission = + | IdentityReadScope + // | ContractWriteScope + | AllContractsWriteScope + | NativeSpend + | ExpirationScope; + +export type PermissionState = { + "contracts:write": string[] | boolean; + "identity:read": boolean; + "native:spend"?: number; + expiration?: Date; +}; + +export function PermissionCard(props: { + name: string; + permissions: Permission[]; + onAccept: (permissions: PermissionState) => void; + onDeny: () => void; +}) { + const [permissions, setPermissions] = useState(() => { + const initialState = {} as PermissionState; + for (const perm of props.permissions) { + if (perm.type === "toggle") { + initialState[perm.id] = perm.mandatory; + } else if (perm.type === "number") { + initialState[perm.id] = perm.initialState || 0; + } else if (perm.type === "date") { + initialState[perm.id] = perm.initialState; + } + } + return initialState; + }); + + const handleNumberInputPermission = (id: string, value: string) => { + setPermissions((prev) => ({ ...prev, [id]: Number(value) })); + }; + + const handleDateInputPermission = (id: string, value: string) => { + setPermissions((prev) => ({ ...prev, [id]: new Date(value) })); + }; + + const mandatoryPermissions = props.permissions.filter((p) => p.mandatory); + const editablePermissions = props.permissions.filter((p) => !p.mandatory); + + const renderPermission = (permission: Permission) => ( +
+
+ {permission.mandatory && ( + + )} + + + + + +

{permission.description}

+
+
+
+ {!permission.mandatory && ( +
+ {permission.type === "number" && ( + + handleNumberInputPermission(permission.id, e.target.value) + } + min={permission.min} + max={permission.max} + step={permission.step} + className="w-[100px]" + /> + )} + {permission.type === "date" && ( + + handleDateInputPermission(permission.id, e.target.value) + } + /> + )} +
+ )} +
+ ); + + return ( + + + + App Permissions + + Review and manage the permissions requested by{" "} + {props.name} + + + +
+

Requried Permissions

+ {mandatoryPermissions.map(renderPermission)} +
+ {editablePermissions.length > 0 && ( + <> + +
+

+ Configurable Permissions +

+ {editablePermissions.map(renderPermission)} +
+ + )} +
+ + + + +
+
+ ); +} diff --git a/apps/login/src/components/theme-toggle.tsx b/apps/login/src/components/theme-toggle.tsx new file mode 100644 index 00000000000..240a0aac631 --- /dev/null +++ b/apps/login/src/components/theme-toggle.tsx @@ -0,0 +1,39 @@ +"use client"; + +import { Moon, Sun } from "lucide-react"; +import { useTheme } from "next-themes"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +export function ModeToggle() { + const { setTheme } = useTheme(); + + return ( + + + + + + setTheme("light")}> + Light + + setTheme("dark")}> + Dark + + setTheme("system")}> + System + + + + ); +} diff --git a/apps/login/src/components/ui/button.tsx b/apps/login/src/components/ui/button.tsx new file mode 100644 index 00000000000..1579cf371b2 --- /dev/null +++ b/apps/login/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import { Slot } from "@radix-ui/react-slot"; +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/apps/login/src/components/ui/card.tsx b/apps/login/src/components/ui/card.tsx new file mode 100644 index 00000000000..5a90768a118 --- /dev/null +++ b/apps/login/src/components/ui/card.tsx @@ -0,0 +1,86 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/apps/login/src/components/ui/dropdown-menu.tsx b/apps/login/src/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000000..bf4dd503da7 --- /dev/null +++ b/apps/login/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,200 @@ +"use client"; + +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { Check, ChevronRight, Circle } from "lucide-react"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const DropdownMenu = DropdownMenuPrimitive.Root; + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +const DropdownMenuGroup = DropdownMenuPrimitive.Group; + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; + +const DropdownMenuSub = DropdownMenuPrimitive.Sub; + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)); +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName; + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName; + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName; + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +}; diff --git a/apps/login/src/components/ui/input.tsx b/apps/login/src/components/ui/input.tsx new file mode 100644 index 00000000000..be7b421ba43 --- /dev/null +++ b/apps/login/src/components/ui/input.tsx @@ -0,0 +1,22 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; + +export { Input }; diff --git a/apps/login/src/components/ui/label.tsx b/apps/login/src/components/ui/label.tsx new file mode 100644 index 00000000000..663ca2319ef --- /dev/null +++ b/apps/login/src/components/ui/label.tsx @@ -0,0 +1,26 @@ +"use client"; + +import * as LabelPrimitive from "@radix-ui/react-label"; +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", +); + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label }; diff --git a/apps/login/src/components/ui/select.tsx b/apps/login/src/components/ui/select.tsx new file mode 100644 index 00000000000..3c0034fea2b --- /dev/null +++ b/apps/login/src/components/ui/select.tsx @@ -0,0 +1,160 @@ +"use client"; + +import * as SelectPrimitive from "@radix-ui/react-select"; +import { Check, ChevronDown, ChevronUp } from "lucide-react"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Select = SelectPrimitive.Root; + +const SelectGroup = SelectPrimitive.Group; + +const SelectValue = SelectPrimitive.Value; + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className, + )} + {...props} + > + {children} + + + + +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName; + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +}; diff --git a/apps/login/src/components/ui/separator.tsx b/apps/login/src/components/ui/separator.tsx new file mode 100644 index 00000000000..3cc446d24c2 --- /dev/null +++ b/apps/login/src/components/ui/separator.tsx @@ -0,0 +1,31 @@ +"use client"; + +import * as SeparatorPrimitive from "@radix-ui/react-separator"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...props }, + ref, + ) => ( + + ), +); +Separator.displayName = SeparatorPrimitive.Root.displayName; + +export { Separator }; diff --git a/apps/login/src/components/ui/sonner.tsx b/apps/login/src/components/ui/sonner.tsx new file mode 100644 index 00000000000..549cf8413d9 --- /dev/null +++ b/apps/login/src/components/ui/sonner.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { Toaster as Sonner } from "sonner"; + +type ToasterProps = React.ComponentProps; + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = "system" } = useTheme(); + + return ( + + ); +}; + +export { Toaster }; diff --git a/apps/login/src/components/ui/switch.tsx b/apps/login/src/components/ui/switch.tsx new file mode 100644 index 00000000000..4b6c039b12f --- /dev/null +++ b/apps/login/src/components/ui/switch.tsx @@ -0,0 +1,29 @@ +"use client"; + +import * as SwitchPrimitives from "@radix-ui/react-switch"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +Switch.displayName = SwitchPrimitives.Root.displayName; + +export { Switch }; diff --git a/apps/login/src/components/ui/tooltip.tsx b/apps/login/src/components/ui/tooltip.tsx new file mode 100644 index 00000000000..1a935647501 --- /dev/null +++ b/apps/login/src/components/ui/tooltip.tsx @@ -0,0 +1,30 @@ +"use client"; + +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; diff --git a/apps/login/src/lib/dev-mode.ts b/apps/login/src/lib/dev-mode.ts new file mode 100644 index 00000000000..40dc109bf58 --- /dev/null +++ b/apps/login/src/lib/dev-mode.ts @@ -0,0 +1,24 @@ +import { setThirdwebDomains } from "thirdweb/utils"; +import { + THIRDWEB_BUNDLER_DOMAIN, + THIRDWEB_INAPP_WALLET_DOMAIN, + THIRDWEB_PAY_DOMAIN, + THIRDWEB_RPC_DOMAIN, + THIRDWEB_SOCIAL_API_DOMAIN, + THIRDWEB_STORAGE_DOMAIN, +} from "../lib/urls"; +import { getVercelEnv } from "../lib/vercel"; + +export function initDevMode() { + if (getVercelEnv() !== "production") { + // if not on production: run this when creating a client to set the domains + setThirdwebDomains({ + rpc: THIRDWEB_RPC_DOMAIN, + inAppWallet: THIRDWEB_INAPP_WALLET_DOMAIN, + pay: THIRDWEB_PAY_DOMAIN, + storage: THIRDWEB_STORAGE_DOMAIN, + social: THIRDWEB_SOCIAL_API_DOMAIN, + bundler: THIRDWEB_BUNDLER_DOMAIN, + }); + } +} diff --git a/apps/login/src/lib/keys.ts b/apps/login/src/lib/keys.ts new file mode 100644 index 00000000000..d9647a00850 --- /dev/null +++ b/apps/login/src/lib/keys.ts @@ -0,0 +1,34 @@ +import "server-only"; + +import { type KeyLike, importPKCS8, importSPKI } from "jose"; + +const ALGORITHM = "RS256"; + +let privateKeyPromise: Promise | null = null; +let publicKeyPromise: Promise | null = null; + +export async function getKeyInfo() { + // Ensure these are loaded only once and cached + if (!privateKeyPromise) { + // biome-ignore lint/style/noNonNullAssertion: if this is not set the server will crash + privateKeyPromise = importPKCS8(process.env.RSA_PRIVATE_KEY!, ALGORITHM); + } + if (!publicKeyPromise) { + // biome-ignore lint/style/noNonNullAssertion: if this is not set the server will crash + publicKeyPromise = importSPKI(process.env.RSA_PUBLIC_KEY!, ALGORITHM); + } + + // Wait for both imports to resolve + const [privateKey, publicKey] = await Promise.all([ + privateKeyPromise, + publicKeyPromise, + ]); + + return { + privateKey, + publicKey, + // biome-ignore lint/style/noNonNullAssertion: if this is not set the server will crash + kid: process.env.RSA_KEY_ID!, + alg: ALGORITHM, + }; +} diff --git a/apps/login/src/lib/oauth.ts b/apps/login/src/lib/oauth.ts new file mode 100644 index 00000000000..91f3c872b66 --- /dev/null +++ b/apps/login/src/lib/oauth.ts @@ -0,0 +1,47 @@ +import type { PermissionState } from "@/components/permission-card"; + +export type Oauth2AuthorizeParams = { + client_id: string; + redirect_uri: string; + response_type: "code"; + scope?: string; // currently unused + state: string; + code_challenge: string; + code_challenge_method: "S256" | "plain"; +}; + +export type Oauth2CodePayload = { + sub: string; + scope: string; + client_id: string; + code_challenge: string; + code_challenge_method: "S256" | "plain"; + redirect_uri: string; + permissions: PermissionState; +}; + +export type Oauth2AuthTokenRequest = { + grant_type: "authorization_code"; + code: string; + code_verifier: string; + redirect_uri: string; + client_id: string; +}; + +export type Oauth2RefreshTokenRequest = { + grant_type: "refresh_token"; + refresh_token: string; + redirect_uri: string; + client_id: string; +}; + +export type Oauth2AuthToken = { + sub: string; + scope: string; + permissions: PermissionState; + data?: Record; +}; + +export type Oauth2RefreshToken = { + originalData: Oauth2AuthToken; +}; diff --git a/apps/login/src/lib/siwe-server.ts b/apps/login/src/lib/siwe-server.ts new file mode 100644 index 00000000000..86bb57d3393 --- /dev/null +++ b/apps/login/src/lib/siwe-server.ts @@ -0,0 +1,45 @@ +import "server-only"; + +import { cookies } from "next/headers"; +import { createAuth } from "thirdweb/auth"; +import { privateKeyToAccount } from "thirdweb/wallets"; +import { initDevMode } from "./dev-mode"; +import { thirdwebClient } from "./thirdweb-client"; + +initDevMode(); + +export const SESSION_EXPIRATION_TIME_SECONDS = 60 * 60 * 24 * 30; // 30 days + +export const SESSION_COOKIE_NAME = "tw_session"; + +export const SIWE_AUTH = createAuth({ + domain: "login.thirdweb.com", + client: thirdwebClient, + + adminAccount: privateKeyToAccount({ + // biome-ignore lint/style/noNonNullAssertion: we know this is set + privateKey: process.env.AUTH_PKEY!, + client: thirdwebClient, + }), + jwt: { + expirationTimeSeconds: SESSION_EXPIRATION_TIME_SECONDS, + }, +}); + +export async function getValidSessionToken() { + const cookieStore = await cookies(); + const token = cookieStore.get(SESSION_COOKIE_NAME); + if (!token) { + return null; + } + + const result = await SIWE_AUTH.verifyJWT({ + jwt: token.value, + }); + + if (!result.valid) { + return null; + } + + return result.parsedJWT; +} diff --git a/apps/login/src/lib/thirdweb-client.ts b/apps/login/src/lib/thirdweb-client.ts new file mode 100644 index 00000000000..1db4a07ef7b --- /dev/null +++ b/apps/login/src/lib/thirdweb-client.ts @@ -0,0 +1,8 @@ +import { createThirdwebClient } from "thirdweb"; + +export const thirdwebClient = createThirdwebClient({ + // biome-ignore lint/style/noNonNullAssertion: will throw error internally if not set + clientId: process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID!, + // will only exist in the server environment, but that is fine + secretKey: process.env.THIRDWEB_SECRET_KEY, +}); diff --git a/apps/login/src/lib/urls.ts b/apps/login/src/lib/urls.ts new file mode 100644 index 00000000000..a355e93719d --- /dev/null +++ b/apps/login/src/lib/urls.ts @@ -0,0 +1,18 @@ +export const THIRDWEB_PAY_DOMAIN = + process.env.NEXT_PUBLIC_PAY_URL || "pay.thirdweb-dev.com"; + +export const THIRDWEB_INAPP_WALLET_DOMAIN = + process.env.NEXT_PUBLIC_IN_APP_WALLET_URL || + "embedded-wallet.thirdweb-dev.com"; + +export const THIRDWEB_RPC_DOMAIN = + process.env.NEXT_PUBLIC_RPC_URL || "rpc.thirdweb-dev.com"; + +export const THIRDWEB_STORAGE_DOMAIN = + process.env.NEXT_PUBLIC_STORAGE_URL || "storage.thirdweb-dev.com"; + +export const THIRDWEB_SOCIAL_API_DOMAIN = + process.env.NEXT_PUBLIC_SOCIAL_URL || "social.thirdweb-dev.com"; + +export const THIRDWEB_BUNDLER_DOMAIN = + process.env.NEXT_PUBLIC_BUNDLER_URL || "bundler.thirdweb-dev.com"; diff --git a/apps/login/src/lib/utils.ts b/apps/login/src/lib/utils.ts new file mode 100644 index 00000000000..365058cebd7 --- /dev/null +++ b/apps/login/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/apps/login/src/lib/vercel.ts b/apps/login/src/lib/vercel.ts new file mode 100644 index 00000000000..b9af323c575 --- /dev/null +++ b/apps/login/src/lib/vercel.ts @@ -0,0 +1,9 @@ +export function getVercelEnv() { + const onVercel = process.env.vercel || process.env.NEXT_PUBLIC_VERCEL_ENV; + if (!onVercel) { + return "development"; + } + return (process.env.VERCEL_ENV || + process.env.NEXT_PUBLIC_VERCEL_ENV || + "production") as "production" | "preview" | "development"; +} diff --git a/apps/login/src/providers/theme-provider.tsx b/apps/login/src/providers/theme-provider.tsx new file mode 100644 index 00000000000..f2df7ce74f7 --- /dev/null +++ b/apps/login/src/providers/theme-provider.tsx @@ -0,0 +1,10 @@ +"use client"; + +import { ThemeProvider as NextThemesProvider } from "next-themes"; + +export function ThemeProvider({ + children, + ...props +}: React.ComponentProps) { + return {children}; +} diff --git a/apps/login/src/providers/thirdweb-provider.tsx b/apps/login/src/providers/thirdweb-provider.tsx new file mode 100644 index 00000000000..ef574ae2f3e --- /dev/null +++ b/apps/login/src/providers/thirdweb-provider.tsx @@ -0,0 +1,9 @@ +"use client"; +import { initDevMode } from "@/lib/dev-mode"; +import { ThirdwebProvider } from "thirdweb/react"; + +initDevMode(); + +export function Provider({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/apps/login/tailwind.config.ts b/apps/login/tailwind.config.ts index 109807be0f7..4b1affb351f 100644 --- a/apps/login/tailwind.config.ts +++ b/apps/login/tailwind.config.ts @@ -1,18 +1,61 @@ import type { Config } from "tailwindcss"; export default { + darkMode: ["class"], content: [ - "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", "./src/components/**/*.{js,ts,jsx,tsx,mdx}", "./src/app/**/*.{js,ts,jsx,tsx,mdx}", ], theme: { extend: { colors: { - background: "var(--background)", - foreground: "var(--foreground)", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + chart: { + "1": "hsl(var(--chart-1))", + "2": "hsl(var(--chart-2))", + "3": "hsl(var(--chart-3))", + "4": "hsl(var(--chart-4))", + "5": "hsl(var(--chart-5))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", }, }, }, - plugins: [], + plugins: [require("tailwindcss-animate")], } satisfies Config; diff --git a/apps/login/test/script-test.html b/apps/login/test/script-test.html deleted file mode 100644 index 96a52e95669..00000000000 --- a/apps/login/test/script-test.html +++ /dev/null @@ -1,11 +0,0 @@ - - - -

Test Page

- - - - - - - \ No newline at end of file diff --git a/apps/playground-web/package.json b/apps/playground-web/package.json index d32a6afc9a7..b7cfbdc4957 100644 --- a/apps/playground-web/package.json +++ b/apps/playground-web/package.json @@ -35,6 +35,7 @@ "@thirdweb-dev/engine": "^0.0.18", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "jose": "^5.10.0", "lucide-react": "0.474.0", "next": "15.1.6", "next-themes": "^0.4.4", diff --git a/apps/playground-web/src/app/login/_sdk_.ts b/apps/playground-web/src/app/login/_sdk_.ts new file mode 100644 index 00000000000..5e8a2e2e626 --- /dev/null +++ b/apps/playground-web/src/app/login/_sdk_.ts @@ -0,0 +1,95 @@ +// TODO: clean all of this up a heck of a lot! + +const LOGIN_URL = "https://login.thirdweb.com"; +const CLIENT_ID = "demo"; + +export async function triggerLogin() { + const { codeChallenge, codeVerifier } = await generateCodeChallenge(); + const state = generateState(); + storeCodeVerifier(codeVerifier); + storeState(state); + const redirectUri = window.location.href; + window.location.href = `${LOGIN_URL}/authorize?client_id=${CLIENT_ID}&redirect_uri=${redirectUri}&response_type=code&state=${state}&code_challenge=${codeChallenge}&code_challenge_method=S256`; +} + +// do code exchange +export async function handleLogin(code: string) { + const codeVerifier = getCodeVerifier(); + const state = getState(); + if (!codeVerifier || !state) { + console.error("No code verifier or state found"); + return; + } + // clear the code verifier and state -> we don't need them anymore + clearCodeVerifier(); + clearState(); + const redirectUri = window.location.href; + + fetch(`${LOGIN_URL}/api/token`, { + method: "POST", + body: JSON.stringify({ + code, + code_verifier: codeVerifier, + state, + client_id: CLIENT_ID, + redirect_uri: redirectUri, + grant_type: "authorization_code", + }), + headers: { + "Content-Type": "application/json", + }, + }) + .then((res) => res.json()) + .then((data) => { + console.log("data", data); + }) + .catch((err) => { + console.error("error", err); + }); +} + +// oauth2 PKCE code challenge generator (code challenge has to be url safe) +async function generateCodeChallenge() { + const codeVerifier = base64UrlEncode( + window.crypto.getRandomValues(new Uint8Array(64)), + ); + const codeChallenge = await window.crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(codeVerifier), + ); + return { + codeVerifier: codeVerifier, + codeChallenge: base64UrlEncode(codeChallenge), + }; +} + +function generateState() { + const random = window.crypto.getRandomValues(new Uint8Array(12)); + return base64UrlEncode(random); +} + +function base64UrlEncode(array: Uint8Array | ArrayBuffer) { + return btoa(String.fromCharCode(...new Uint8Array(array))) + .replace(/=/g, "") + .replace(/\+/g, "-") + .replace(/\//g, "_"); +} + +function storeCodeVerifier(codeVerifier: string) { + localStorage.setItem("codeVerifier", codeVerifier); +} +function getCodeVerifier() { + return localStorage.getItem("codeVerifier"); +} +function clearCodeVerifier() { + localStorage.removeItem("codeVerifier"); +} +function storeState(state: string) { + localStorage.setItem("state", state); +} +function getState() { + return localStorage.getItem("state"); +} +function clearState() { + localStorage.removeItem("state"); +} diff --git a/apps/playground-web/src/app/login/layout.tsx b/apps/playground-web/src/app/login/layout.tsx new file mode 100644 index 00000000000..fe0c8102ecf --- /dev/null +++ b/apps/playground-web/src/app/login/layout.tsx @@ -0,0 +1,9 @@ +import { Suspense } from "react"; + +export default function LoginLayout({ + children, +}: { + children: React.ReactNode; +}) { + return Loading...
}>{children}; +} diff --git a/apps/playground-web/src/app/login/page.tsx b/apps/playground-web/src/app/login/page.tsx new file mode 100644 index 00000000000..f85160457b8 --- /dev/null +++ b/apps/playground-web/src/app/login/page.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useSearchParams } from "next/navigation"; +import { handleLogin, triggerLogin } from "./_sdk_"; + +export default function LoginPage() { + const searchParams = useSearchParams(); + const code = searchParams.get("code"); + if (code) { + handleLogin(code); + } + return ( +
+ + + Login Test + + + + + +
+ ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0c5c5716303..77e0c8e4ee9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -135,7 +135,7 @@ importers: version: 1.1.7(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@sentry/nextjs': specifier: 8.53.0 - version: 8.53.0(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + version: 8.53.0(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) '@shazow/whatsabi': specifier: ^0.19.0 version: 0.19.0(@noble/hashes@1.7.1)(typescript@5.7.3)(zod@3.24.1) @@ -282,7 +282,7 @@ importers: version: 2.6.0 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3))) + version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3))) thirdweb: specifier: workspace:* version: link:../../packages/thirdweb @@ -331,7 +331,7 @@ importers: version: 8.5.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10)) '@storybook/nextjs': specifier: 8.5.2 - version: 8.5.2(@swc/core@1.10.12)(esbuild@0.24.2)(next@15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(type-fest@4.33.0)(typescript@5.7.3)(webpack-hot-middleware@2.26.1)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + version: 8.5.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(next@15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(type-fest@4.33.0)(typescript@5.7.3)(webpack-hot-middleware@2.26.1)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) '@storybook/react': specifier: 8.5.2 version: 8.5.2(@storybook/test@8.5.2(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(typescript@5.7.3) @@ -379,7 +379,7 @@ importers: version: 10.4.20(postcss@8.5.1) checkly: specifier: ^4.19.1 - version: 4.19.1(@swc/core@1.10.12)(@types/node@22.13.0)(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10) + version: 4.19.1(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10) eslint: specifier: 8.57.0 version: 8.57.0 @@ -406,16 +406,52 @@ importers: version: 8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10) tailwindcss: specifier: 3.4.17 - version: 3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3)) + version: 3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3)) typescript: specifier: 5.7.3 version: 5.7.3 apps/login: dependencies: + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.5 + version: 2.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-label': + specifier: ^2.1.1 + version: 2.1.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-select': + specifier: ^2.1.5 + version: 2.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-separator': + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': + specifier: ^1.1.1 + version: 1.1.1(@types/react@19.0.8)(react@19.0.0) + '@radix-ui/react-switch': + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-tooltip': + specifier: 1.1.7 + version: 1.1.7(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + jose: + specifier: ^5.10.0 + version: 5.10.0 + lucide-react: + specifier: 0.474.0 + version: 0.474.0(react@19.0.0) next: specifier: 15.1.6 version: 15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next-themes: + specifier: ^0.4.4 + version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: 19.0.0 version: 19.0.0 @@ -425,6 +461,15 @@ importers: server-only: specifier: ^0.0.1 version: 0.0.1 + sonner: + specifier: ^1.7.4 + version: 1.7.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.0 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3))) thirdweb: specifier: workspace:* version: link:../../packages/thirdweb @@ -447,18 +492,12 @@ importers: eslint-config-next: specifier: 15.1.6 version: 15.1.6(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3) - knip: - specifier: 5.43.6 - version: 5.43.6(@types/node@22.13.0)(typescript@5.7.3) postcss: specifier: 8.5.1 version: 8.5.1 - postcss-load-config: - specifier: ^6.0.1 - version: 6.0.1(jiti@2.4.2)(postcss@8.5.1)(tsx@4.19.2)(yaml@2.7.0) tailwindcss: specifier: 3.4.17 - version: 3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3)) + version: 3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3)) typescript: specifier: 5.7.3 version: 5.7.3 @@ -528,6 +567,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + jose: + specifier: ^5.10.0 + version: 5.10.0 lucide-react: specifier: 0.474.0 version: 0.474.0(react@19.0.0) @@ -603,10 +645,10 @@ importers: version: 8.5.1 tailwindcss: specifier: 3.4.17 - version: 3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3)) + version: 3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3)) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3))) + version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3))) typescript: specifier: 5.7.3 version: 5.7.3 @@ -618,13 +660,13 @@ importers: version: 1.0.6(react@19.0.0) '@mdx-js/loader': specifier: ^2.3.0 - version: 2.3.0(webpack@5.97.1) + version: 2.3.0(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) '@mdx-js/react': specifier: ^2.3.0 version: 2.3.0(react@19.0.0) '@next/mdx': specifier: 15.1.6 - version: 15.1.6(@mdx-js/loader@2.3.0(webpack@5.97.1))(@mdx-js/react@2.3.0(react@19.0.0)) + version: 15.1.6(@mdx-js/loader@2.3.0(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))))(@mdx-js/react@2.3.0(react@19.0.0)) '@radix-ui/react-dialog': specifier: 1.1.5 version: 1.1.5(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -702,7 +744,7 @@ importers: version: 2.6.0 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3))) + version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3))) thirdweb: specifier: workspace:* version: link:../../packages/thirdweb @@ -760,7 +802,7 @@ importers: version: 1.2.4 eslint-plugin-tailwindcss: specifier: ^3.18.0 - version: 3.18.0(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3))) + version: 3.18.0(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3))) knip: specifier: 5.43.6 version: 5.43.6(@types/node@22.13.0)(typescript@5.7.3) @@ -772,7 +814,7 @@ importers: version: 8.5.1 tailwindcss: specifier: 3.4.17 - version: 3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3)) + version: 3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3)) tsx: specifier: 4.19.2 version: 4.19.2 @@ -838,7 +880,7 @@ importers: version: 2.6.0 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3))) + version: 1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3))) thirdweb: specifier: workspace:* version: link:../../packages/thirdweb @@ -887,7 +929,7 @@ importers: version: 6.0.1(jiti@2.4.2)(postcss@8.5.1)(tsx@4.19.2)(yaml@2.7.0) tailwindcss: specifier: 3.4.17 - version: 3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3)) + version: 3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3)) typescript: specifier: 5.7.3 version: 5.7.3 @@ -1082,7 +1124,7 @@ importers: version: 2.1.0(react-native@0.76.6(@babel/core@7.26.7)(@babel/preset-env@7.26.7(@babel/core@7.26.7))(@types/react@19.0.8)(bufferutil@4.0.9)(encoding@0.1.13)(react@19.0.0)(utf-8-validate@5.0.10)) '@size-limit/preset-big-lib': specifier: 11.1.6 - version: 11.1.6(bufferutil@4.0.9)(size-limit@11.1.6)(utf-8-validate@5.0.10) + version: 11.1.6(bufferutil@4.0.9)(esbuild@0.24.2)(size-limit@11.1.6)(utf-8-validate@5.0.10) '@storybook/addon-essentials': specifier: 8.5.2 version: 8.5.2(@types/react@19.0.8)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.3.3)(utf-8-validate@5.0.10)) @@ -4331,7 +4373,7 @@ packages: '@radix-ui/react-context@1.1.1': resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} peerDependencies: - '@types/react': '*' + '@types/react': npm:types-react@19.0.0-rc.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -4388,7 +4430,7 @@ packages: '@radix-ui/react-focus-guards@1.1.1': resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: - '@types/react': '*' + '@types/react': npm:types-react@19.0.0-rc.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -4663,7 +4705,7 @@ packages: '@radix-ui/react-use-controllable-state@1.1.0': resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} peerDependencies: - '@types/react': '*' + '@types/react': npm:types-react@19.0.0-rc.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -4708,7 +4750,7 @@ packages: '@radix-ui/react-use-size@1.1.0': resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} peerDependencies: - '@types/react': '*' + '@types/react': npm:types-react@19.0.0-rc.1 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -10243,6 +10285,9 @@ packages: jose@4.15.9: resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} + jose@5.10.0: + resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -15194,7 +15239,7 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.592.0 + '@aws-sdk/client-sso-oidc': 3.592.0(@aws-sdk/client-sts@3.592.0) '@aws-sdk/client-sts': 3.592.0 '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) @@ -15240,7 +15285,7 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.592.0 + '@aws-sdk/client-sso-oidc': 3.592.0(@aws-sdk/client-sts@3.592.0) '@aws-sdk/client-sts': 3.592.0 '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) @@ -15286,7 +15331,7 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.592.0 + '@aws-sdk/client-sso-oidc': 3.592.0(@aws-sdk/client-sts@3.592.0) '@aws-sdk/client-sts': 3.592.0 '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) @@ -15333,7 +15378,7 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso-oidc@3.592.0': + '@aws-sdk/client-sso-oidc@3.592.0(@aws-sdk/client-sts@3.592.0)': dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 @@ -15376,6 +15421,7 @@ snapshots: '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 transitivePeerDependencies: + - '@aws-sdk/client-sts' - aws-crt '@aws-sdk/client-sso-oidc@3.738.0': @@ -15512,7 +15558,7 @@ snapshots: dependencies: '@aws-crypto/sha256-browser': 3.0.0 '@aws-crypto/sha256-js': 3.0.0 - '@aws-sdk/client-sso-oidc': 3.592.0 + '@aws-sdk/client-sso-oidc': 3.592.0(@aws-sdk/client-sts@3.592.0) '@aws-sdk/core': 3.592.0 '@aws-sdk/credential-provider-node': 3.592.0(@aws-sdk/client-sso-oidc@3.592.0)(@aws-sdk/client-sts@3.592.0) '@aws-sdk/middleware-host-header': 3.577.0 @@ -15954,7 +16000,7 @@ snapshots: '@aws-sdk/token-providers@3.587.0(@aws-sdk/client-sso-oidc@3.592.0)': dependencies: - '@aws-sdk/client-sso-oidc': 3.592.0 + '@aws-sdk/client-sso-oidc': 3.592.0(@aws-sdk/client-sts@3.592.0) '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.1.11 '@smithy/shared-ini-file-loader': 3.1.12 @@ -18712,11 +18758,11 @@ snapshots: react: 19.0.0 react-dom: 19.0.0(react@19.0.0) - '@mdx-js/loader@2.3.0(webpack@5.97.1)': + '@mdx-js/loader@2.3.0(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)))': dependencies: '@mdx-js/mdx': 2.3.0 source-map: 0.7.4 - webpack: 5.97.1 + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) transitivePeerDependencies: - supports-color @@ -19038,11 +19084,11 @@ snapshots: dependencies: fast-glob: 3.3.1 - '@next/mdx@15.1.6(@mdx-js/loader@2.3.0(webpack@5.97.1))(@mdx-js/react@2.3.0(react@19.0.0))': + '@next/mdx@15.1.6(@mdx-js/loader@2.3.0(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))))(@mdx-js/react@2.3.0(react@19.0.0))': dependencies: source-map: 0.7.4 optionalDependencies: - '@mdx-js/loader': 2.3.0(webpack@5.97.1) + '@mdx-js/loader': 2.3.0(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) '@mdx-js/react': 2.3.0(react@19.0.0) '@next/swc-darwin-arm64@15.1.6': @@ -19212,7 +19258,7 @@ snapshots: widest-line: 3.1.0 wrap-ansi: 7.0.0 - '@oclif/core@2.8.11(@swc/core@1.10.12)(@types/node@22.13.0)(typescript@5.7.3)': + '@oclif/core@2.8.11(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3)': dependencies: '@types/cli-progress': 3.11.6 ansi-escapes: 4.3.2 @@ -19238,7 +19284,7 @@ snapshots: strip-ansi: 6.0.1 supports-color: 8.1.1 supports-hyperlinks: 2.3.0 - ts-node: 10.9.2(@swc/core@1.10.12)(@types/node@22.13.0)(typescript@5.7.3) + ts-node: 10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3) tslib: 2.8.1 widest-line: 3.1.0 wordwrap: 1.0.0 @@ -19276,10 +19322,10 @@ snapshots: dependencies: '@oclif/core': 1.26.2 - '@oclif/plugin-not-found@2.3.23(@swc/core@1.10.12)(@types/node@22.13.0)(typescript@5.7.3)': + '@oclif/plugin-not-found@2.3.23(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3)': dependencies: '@oclif/color': 1.0.13 - '@oclif/core': 2.8.11(@swc/core@1.10.12)(@types/node@22.13.0)(typescript@5.7.3) + '@oclif/core': 2.8.11(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3) fast-levenshtein: 3.0.0 lodash: 4.17.21 transitivePeerDependencies: @@ -19304,9 +19350,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@oclif/plugin-warn-if-update-available@2.0.24(@swc/core@1.10.12)(@types/node@22.13.0)(typescript@5.7.3)': + '@oclif/plugin-warn-if-update-available@2.0.24(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3)': dependencies: - '@oclif/core': 2.8.11(@swc/core@1.10.12)(@types/node@22.13.0)(typescript@5.7.3) + '@oclif/core': 2.8.11(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3) chalk: 4.1.2 debug: 4.4.0(supports-color@8.1.1) fs-extra: 9.1.0 @@ -19624,7 +19670,7 @@ snapshots: dependencies: playwright: 1.50.1 - '@pmmmwh/react-refresh-webpack-plugin@0.5.15(react-refresh@0.14.2)(type-fest@4.33.0)(webpack-hot-middleware@2.26.1)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2))': + '@pmmmwh/react-refresh-webpack-plugin@0.5.15(react-refresh@0.14.2)(type-fest@4.33.0)(webpack-hot-middleware@2.26.1)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)))': dependencies: ansi-html: 0.0.9 core-js-pure: 3.40.0 @@ -19634,7 +19680,7 @@ snapshots: react-refresh: 0.14.2 schema-utils: 4.3.0 source-map: 0.7.4 - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) optionalDependencies: type-fest: 4.33.0 webpack-hot-middleware: 2.26.1 @@ -20774,7 +20820,7 @@ snapshots: '@sentry/core@8.53.0': {} - '@sentry/nextjs@8.53.0(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2))': + '@sentry/nextjs@8.53.0(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)))': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.28.0 @@ -20785,7 +20831,7 @@ snapshots: '@sentry/opentelemetry': 8.53.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.56.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.28.0) '@sentry/react': 8.53.0(react@19.0.0) '@sentry/vercel-edge': 8.53.0 - '@sentry/webpack-plugin': 2.22.7(encoding@0.1.13)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + '@sentry/webpack-plugin': 2.22.7(encoding@0.1.13)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) chalk: 3.0.0 next: 15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) resolve: 1.22.8 @@ -20861,12 +20907,12 @@ snapshots: '@opentelemetry/api': 1.9.0 '@sentry/core': 8.53.0 - '@sentry/webpack-plugin@2.22.7(encoding@0.1.13)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2))': + '@sentry/webpack-plugin@2.22.7(encoding@0.1.13)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)))': dependencies: '@sentry/bundler-plugin-core': 2.22.7(encoding@0.1.13) unplugin: 1.0.1 uuid: 9.0.1 - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) transitivePeerDependencies: - encoding - supports-color @@ -20957,11 +21003,11 @@ snapshots: dependencies: size-limit: 11.1.6 - '@size-limit/preset-big-lib@11.1.6(bufferutil@4.0.9)(size-limit@11.1.6)(utf-8-validate@5.0.10)': + '@size-limit/preset-big-lib@11.1.6(bufferutil@4.0.9)(esbuild@0.24.2)(size-limit@11.1.6)(utf-8-validate@5.0.10)': dependencies: '@size-limit/file': 11.1.6(size-limit@11.1.6) '@size-limit/time': 11.1.6(bufferutil@4.0.9)(size-limit@11.1.6)(utf-8-validate@5.0.10) - '@size-limit/webpack': 11.1.6(size-limit@11.1.6) + '@size-limit/webpack': 11.1.6(esbuild@0.24.2)(size-limit@11.1.6) size-limit: 11.1.6 transitivePeerDependencies: - '@swc/core' @@ -20981,11 +21027,11 @@ snapshots: - supports-color - utf-8-validate - '@size-limit/webpack@11.1.6(size-limit@11.1.6)': + '@size-limit/webpack@11.1.6(esbuild@0.24.2)(size-limit@11.1.6)': dependencies: nanoid: 5.0.9 size-limit: 11.1.6 - webpack: 5.97.1 + webpack: 5.97.1(esbuild@0.24.2) transitivePeerDependencies: - '@swc/core' - esbuild @@ -21954,7 +22000,7 @@ snapshots: ts-dedent: 2.2.0 vite: 6.0.11(@types/node@22.13.0)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(tsx@4.19.2)(yaml@2.7.0) - '@storybook/builder-webpack5@8.5.2(@swc/core@1.10.12)(esbuild@0.24.2)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(typescript@5.7.3)': + '@storybook/builder-webpack5@8.5.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(typescript@5.7.3)': dependencies: '@storybook/core-webpack': 8.5.2(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10)) '@types/semver': 7.5.8 @@ -21962,23 +22008,23 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.3 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + css-loader: 6.11.0(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) es-module-lexer: 1.6.0 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) - html-webpack-plugin: 5.6.3(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) + html-webpack-plugin: 5.6.3(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) magic-string: 0.30.17 path-browserify: 1.0.1 process: 0.11.10 semver: 7.7.0 storybook: 8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10) - style-loader: 3.3.4(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) - terser-webpack-plugin: 5.3.11(@swc/core@1.10.12)(esbuild@0.24.2)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + style-loader: 3.3.4(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) + terser-webpack-plugin: 5.3.11(@swc/core@1.10.12(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) - webpack-dev-middleware: 6.1.3(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) + webpack-dev-middleware: 6.1.3(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -22093,7 +22139,7 @@ snapshots: dependencies: storybook: 8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10) - '@storybook/nextjs@8.5.2(@swc/core@1.10.12)(esbuild@0.24.2)(next@15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(type-fest@4.33.0)(typescript@5.7.3)(webpack-hot-middleware@2.26.1)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2))': + '@storybook/nextjs@8.5.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(next@15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(type-fest@4.33.0)(typescript@5.7.3)(webpack-hot-middleware@2.26.1)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)))': dependencies: '@babel/core': 7.26.7 '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.7) @@ -22108,30 +22154,30 @@ snapshots: '@babel/preset-react': 7.26.3(@babel/core@7.26.7) '@babel/preset-typescript': 7.26.0(@babel/core@7.26.7) '@babel/runtime': 7.26.7 - '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(react-refresh@0.14.2)(type-fest@4.33.0)(webpack-hot-middleware@2.26.1)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) - '@storybook/builder-webpack5': 8.5.2(@swc/core@1.10.12)(esbuild@0.24.2)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(typescript@5.7.3) - '@storybook/preset-react-webpack': 8.5.2(@storybook/test@8.5.2(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10)))(@swc/core@1.10.12)(esbuild@0.24.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(typescript@5.7.3) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(react-refresh@0.14.2)(type-fest@4.33.0)(webpack-hot-middleware@2.26.1)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) + '@storybook/builder-webpack5': 8.5.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(typescript@5.7.3) + '@storybook/preset-react-webpack': 8.5.2(@storybook/test@8.5.2(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10)))(@swc/core@1.10.12(@swc/helpers@0.5.15))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(typescript@5.7.3) '@storybook/react': 8.5.2(@storybook/test@8.5.2(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(typescript@5.7.3) '@storybook/test': 8.5.2(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10)) '@types/semver': 7.5.8 - babel-loader: 9.2.1(@babel/core@7.26.7)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) - css-loader: 6.11.0(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + babel-loader: 9.2.1(@babel/core@7.26.7)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) + css-loader: 6.11.0(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) find-up: 5.0.0 image-size: 1.2.0 loader-utils: 3.3.1 next: 15.1.6(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.50.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - node-polyfill-webpack-plugin: 2.0.1(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + node-polyfill-webpack-plugin: 2.0.1(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) pnp-webpack-plugin: 1.7.0(typescript@5.7.3) postcss: 8.5.1 - postcss-loader: 8.1.1(postcss@8.5.1)(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + postcss-loader: 8.1.1(postcss@8.5.1)(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) react-refresh: 0.14.2 resolve-url-loader: 5.0.0 - sass-loader: 14.2.1(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + sass-loader: 14.2.1(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) semver: 7.7.0 storybook: 8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10) - style-loader: 3.3.4(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + style-loader: 3.3.4(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) styled-jsx: 5.1.6(@babel/core@7.26.7)(react@19.0.0) ts-dedent: 2.2.0 tsconfig-paths: 4.2.0 @@ -22139,7 +22185,7 @@ snapshots: optionalDependencies: sharp: 0.33.5 typescript: 5.7.3 - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -22158,11 +22204,11 @@ snapshots: - webpack-hot-middleware - webpack-plugin-serve - '@storybook/preset-react-webpack@8.5.2(@storybook/test@8.5.2(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10)))(@swc/core@1.10.12)(esbuild@0.24.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(typescript@5.7.3)': + '@storybook/preset-react-webpack@8.5.2(@storybook/test@8.5.2(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10)))(@swc/core@1.10.12(@swc/helpers@0.5.15))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(typescript@5.7.3)': dependencies: '@storybook/core-webpack': 8.5.2(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10)) '@storybook/react': 8.5.2(@storybook/test@8.5.2(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10)))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(storybook@8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10))(typescript@5.7.3) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) '@types/semver': 7.5.8 find-up: 5.0.0 magic-string: 0.30.17 @@ -22173,7 +22219,7 @@ snapshots: semver: 7.7.0 storybook: 8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10) tsconfig-paths: 4.2.0 - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) optionalDependencies: typescript: 5.7.3 transitivePeerDependencies: @@ -22192,7 +22238,7 @@ snapshots: dependencies: storybook: 8.5.2(bufferutil@4.0.9)(prettier@3.4.2)(utf-8-validate@5.0.10) - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)))': dependencies: debug: 4.4.0(supports-color@8.1.1) endent: 2.1.0 @@ -22202,7 +22248,7 @@ snapshots: react-docgen-typescript: 2.2.2(typescript@5.7.3) tslib: 2.8.1 typescript: 5.7.3 - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) transitivePeerDependencies: - supports-color @@ -22701,7 +22747,7 @@ snapshots: '@swc/core-win32-x64-msvc@1.10.12': optional: true - '@swc/core@1.10.12': + '@swc/core@1.10.12(@swc/helpers@0.5.15)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.17 @@ -22716,6 +22762,7 @@ snapshots: '@swc/core-win32-arm64-msvc': 1.10.12 '@swc/core-win32-ia32-msvc': 1.10.12 '@swc/core-win32-x64-msvc': 1.10.12 + '@swc/helpers': 0.5.15 optional: true '@swc/counter@0.1.3': {} @@ -24588,12 +24635,12 @@ snapshots: transitivePeerDependencies: - supports-color - babel-loader@9.2.1(@babel/core@7.26.7)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)): + babel-loader@9.2.1(@babel/core@7.26.7)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))): dependencies: '@babel/core': 7.26.7 find-cache-dir: 4.0.0 schema-utils: 4.3.0 - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) babel-plugin-istanbul@6.1.1: dependencies: @@ -25088,13 +25135,13 @@ snapshots: check-error@2.1.1: {} - checkly@4.19.1(@swc/core@1.10.12)(@types/node@22.13.0)(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10): + checkly@4.19.1(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10): dependencies: - '@oclif/core': 2.8.11(@swc/core@1.10.12)(@types/node@22.13.0)(typescript@5.7.3) + '@oclif/core': 2.8.11(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3) '@oclif/plugin-help': 5.1.20 - '@oclif/plugin-not-found': 2.3.23(@swc/core@1.10.12)(@types/node@22.13.0)(typescript@5.7.3) + '@oclif/plugin-not-found': 2.3.23(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3) '@oclif/plugin-plugins': 5.4.4 - '@oclif/plugin-warn-if-update-available': 2.0.24(@swc/core@1.10.12)(@types/node@22.13.0)(typescript@5.7.3) + '@oclif/plugin-warn-if-update-available': 2.0.24(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3) '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.7.3) acorn: 8.8.1 acorn-walk: 8.2.0 @@ -25531,7 +25578,7 @@ snapshots: css-gradient-parser@0.0.16: {} - css-loader@6.11.0(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)): + css-loader@6.11.0(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))): dependencies: icss-utils: 5.1.0(postcss@8.5.1) postcss: 8.5.1 @@ -25542,7 +25589,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.0 optionalDependencies: - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) css-select@4.3.0: dependencies: @@ -26273,8 +26320,8 @@ snapshots: '@typescript-eslint/parser': 7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3) eslint: 9.19.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.19.0(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.19.0(jiti@2.4.2)) eslint-plugin-react: 7.37.4(eslint@9.19.0(jiti@2.4.2)) eslint-plugin-react-hooks: 5.1.0(eslint@9.19.0(jiti@2.4.2)) @@ -26293,35 +26340,35 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)): + eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0(supports-color@8.1.1) enhanced-resolve: 5.18.0 - eslint: 9.19.0(jiti@2.4.2) + eslint: 8.57.0 fast-glob: 3.3.3 get-tsconfig: 4.10.0 is-bun-module: 1.3.0 is-glob: 4.0.3 stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0) transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@8.57.0): + eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@2.4.2)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.0(supports-color@8.1.1) enhanced-resolve: 5.18.0 - eslint: 8.57.0 + eslint: 9.19.0(jiti@2.4.2) fast-glob: 3.3.3 get-tsconfig: 4.10.0 is-bun-module: 1.3.0 is-glob: 4.0.3 stable-hash: 0.0.4 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.19.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color @@ -26357,14 +26404,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3) eslint: 9.19.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)) + eslint-import-resolver-typescript: 3.7.0(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color @@ -26397,7 +26444,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-typescript@3.7.0)(eslint@9.19.0(jiti@2.4.2)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -26408,7 +26455,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.19.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.14.1(eslint@9.19.0(jiti@2.4.2))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@2.4.2)))(eslint@9.19.0(jiti@2.4.2)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -26562,11 +26609,11 @@ snapshots: eslint-plugin-svg-jsx@1.2.4: {} - eslint-plugin-tailwindcss@3.18.0(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3))): + eslint-plugin-tailwindcss@3.18.0(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3))): dependencies: fast-glob: 3.3.3 postcss: 8.5.1 - tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3)) + tailwindcss: 3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3)) eslint-scope@5.1.1: dependencies: @@ -27355,7 +27402,7 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))): dependencies: '@babel/code-frame': 7.26.2 chalk: 4.1.2 @@ -27370,7 +27417,7 @@ snapshots: semver: 7.7.0 tapable: 2.2.1 typescript: 5.7.3 - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) form-data-encoder@2.1.4: {} @@ -27842,7 +27889,7 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.3(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)): + html-webpack-plugin@5.6.3(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -27850,7 +27897,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) htmlparser2@3.10.1: dependencies: @@ -28475,6 +28522,8 @@ snapshots: jose@4.15.9: {} + jose@5.10.0: {} + joycon@3.1.1: {} js-cookie@3.0.5: {} @@ -30372,7 +30421,7 @@ snapshots: node-int64@0.4.0: {} - node-polyfill-webpack-plugin@2.0.1(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)): + node-polyfill-webpack-plugin@2.0.1(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))): dependencies: assert: 2.1.0 browserify-zlib: 0.2.0 @@ -30399,7 +30448,7 @@ snapshots: url: 0.11.4 util: 0.12.5 vm-browserify: 1.1.2 - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) node-releases@2.0.19: {} @@ -31052,13 +31101,13 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.5.1 - postcss-load-config@4.0.2(postcss@8.5.1)(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3)): + postcss-load-config@4.0.2(postcss@8.5.1)(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3)): dependencies: lilconfig: 3.1.3 yaml: 2.7.0 optionalDependencies: postcss: 8.5.1 - ts-node: 10.9.2(@swc/core@1.10.12)(@types/node@22.13.0)(typescript@5.7.3) + ts-node: 10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3) postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.5.1)(tsx@4.19.2)(yaml@2.7.0): dependencies: @@ -31069,14 +31118,14 @@ snapshots: tsx: 4.19.2 yaml: 2.7.0 - postcss-loader@8.1.1(postcss@8.5.1)(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)): + postcss-loader@8.1.1(postcss@8.5.1)(typescript@5.7.3)(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))): dependencies: cosmiconfig: 9.0.0(typescript@5.7.3) jiti: 1.21.7 postcss: 8.5.1 semver: 7.7.0 optionalDependencies: - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) transitivePeerDependencies: - typescript @@ -32282,11 +32331,11 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@14.2.1(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)): + sass-loader@14.2.1(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))): dependencies: neo-async: 2.6.2 optionalDependencies: - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) satori@0.12.1: dependencies: @@ -32887,9 +32936,9 @@ snapshots: structured-headers@0.4.1: {} - style-loader@3.3.4(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)): + style-loader@3.3.4(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))): dependencies: - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) style-to-object@0.4.4: dependencies: @@ -33051,11 +33100,11 @@ snapshots: tailwind-merge@2.6.0: {} - tailwindcss-animate@1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3))): dependencies: - tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3)) + tailwindcss: 3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3)) - tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3)): + tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -33074,7 +33123,7 @@ snapshots: postcss: 8.5.1 postcss-import: 15.1.0(postcss@8.5.1) postcss-js: 4.0.1(postcss@8.5.1) - postcss-load-config: 4.0.2(postcss@8.5.1)(ts-node@10.9.2(@types/node@22.13.0)(typescript@5.7.3)) + postcss-load-config: 4.0.2(postcss@8.5.1)(ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3)) postcss-nested: 6.2.0(postcss@8.5.1) postcss-selector-parser: 6.1.2 resolve: 1.22.10 @@ -33136,26 +33185,27 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 - terser-webpack-plugin@5.3.11(@swc/core@1.10.12)(esbuild@0.24.2)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)): + terser-webpack-plugin@5.3.11(@swc/core@1.10.12(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.0 serialize-javascript: 6.0.2 terser: 5.37.0 - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) optionalDependencies: - '@swc/core': 1.10.12 - esbuild: 0.24.2 + '@swc/core': 1.10.12(@swc/helpers@0.5.15) - terser-webpack-plugin@5.3.11(webpack@5.97.1): + terser-webpack-plugin@5.3.11(esbuild@0.24.2)(webpack@5.97.1(esbuild@0.24.2)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.0 serialize-javascript: 6.0.2 terser: 5.37.0 - webpack: 5.97.1 + webpack: 5.97.1(esbuild@0.24.2) + optionalDependencies: + esbuild: 0.24.2 terser@5.37.0: dependencies: @@ -33293,7 +33343,7 @@ snapshots: ts-mixer@6.0.4: {} - ts-node@10.9.2(@swc/core@1.10.12)(@types/node@22.13.0)(typescript@5.7.3): + ts-node@10.9.2(@swc/core@1.10.12(@swc/helpers@0.5.15))(@types/node@22.13.0)(typescript@5.7.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -33311,7 +33361,7 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.10.12 + '@swc/core': 1.10.12(@swc/helpers@0.5.15) ts-pnp@1.2.0(typescript@5.7.3): optionalDependencies: @@ -34111,7 +34161,7 @@ snapshots: - bufferutil - utf-8-validate - webpack-dev-middleware@6.1.3(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)): + webpack-dev-middleware@6.1.3(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))): dependencies: colorette: 2.0.20 memfs: 3.5.3 @@ -34119,7 +34169,7 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.0 optionalDependencies: - webpack: 5.97.1(@swc/core@1.10.12)(esbuild@0.24.2) + webpack: 5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)) webpack-hot-middleware@2.26.1: dependencies: @@ -34133,7 +34183,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.97.1: + webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15)): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.6 @@ -34155,7 +34205,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.11(webpack@5.97.1) + terser-webpack-plugin: 5.3.11(@swc/core@1.10.12(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.12(@swc/helpers@0.5.15))) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -34163,7 +34213,7 @@ snapshots: - esbuild - uglify-js - webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2): + webpack@5.97.1(esbuild@0.24.2): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.6 @@ -34185,7 +34235,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.11(@swc/core@1.10.12)(esbuild@0.24.2)(webpack@5.97.1(@swc/core@1.10.12)(esbuild@0.24.2)) + terser-webpack-plugin: 5.3.11(esbuild@0.24.2)(webpack@5.97.1(esbuild@0.24.2)) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: