From f2cdca622ff619ab7d95f10340a001bcdf3dc3cd Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Mon, 28 Oct 2024 18:22:57 -0400 Subject: [PATCH 01/73] init commit - very messy --- examples/oauth/src/pages/_app.tsx | 2 +- examples/oauth/src/pages/index.tsx | 18 +- examples/react-components/next-env.d.ts | 5 + package.json | 1 + packages/sdk-react/package.json | 10 +- packages/sdk-react/src/api/auth.ts | 39 + packages/sdk-react/src/api/getSuborgs.ts | 32 + packages/sdk-react/src/api/initAuth.ts | 34 + .../src/components/auth/Auth.module.css | 175 +++++ .../sdk-react/src/components/auth/Auth.tsx | 200 +++++ .../src/components/auth/PhoneInput.css | 191 +++++ .../sdk-react/src/components/auth/index.ts | 1 + packages/sdk-react/src/components/index.ts | 1 + packages/sdk-react/src/index.ts | 8 +- pnpm-lock.yaml | 685 +++++++++++++++++- rollup.config.base.mjs | 26 +- 16 files changed, 1396 insertions(+), 32 deletions(-) create mode 100644 examples/react-components/next-env.d.ts create mode 100644 packages/sdk-react/src/api/auth.ts create mode 100644 packages/sdk-react/src/api/getSuborgs.ts create mode 100644 packages/sdk-react/src/api/initAuth.ts create mode 100644 packages/sdk-react/src/components/auth/Auth.module.css create mode 100644 packages/sdk-react/src/components/auth/Auth.tsx create mode 100644 packages/sdk-react/src/components/auth/PhoneInput.css create mode 100644 packages/sdk-react/src/components/auth/index.ts create mode 100644 packages/sdk-react/src/components/index.ts diff --git a/examples/oauth/src/pages/_app.tsx b/examples/oauth/src/pages/_app.tsx index a1268bf86..8a159d5cf 100644 --- a/examples/oauth/src/pages/_app.tsx +++ b/examples/oauth/src/pages/_app.tsx @@ -1,6 +1,6 @@ import { AppProps } from "next/app"; import Head from "next/head"; - +import '@turnkey/sdk-react/styles'; import { TurnkeyProvider } from "@turnkey/sdk-react"; const turnkeyConfig = { diff --git a/examples/oauth/src/pages/index.tsx b/examples/oauth/src/pages/index.tsx index 3be98ac90..da5217d34 100644 --- a/examples/oauth/src/pages/index.tsx +++ b/examples/oauth/src/pages/index.tsx @@ -1,14 +1,14 @@ import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google"; import Image from "next/image"; import styles from "./index.module.css"; -import { useTurnkey } from "@turnkey/sdk-react"; import { useForm } from "react-hook-form"; import axios from "axios"; import * as React from "react"; import { useState } from "react"; import { sha256 } from "@noble/hashes/sha2"; import { bytesToHex } from "@noble/hashes/utils"; - +import { useTurnkey, Auth} from "@turnkey/sdk-react"; +import { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; /** * Type definition for the server response coming back from `/api/auth` */ @@ -31,13 +31,20 @@ type AuthFormData = { export default function AuthPage() { const [authResponse, setAuthResponse] = useState(null); const { authIframeClient } = useTurnkey(); + console.log(process.env.API_PUBLIC_KEY!) + const turnkeyClient = new TurnkeySDKClient({ + apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, + apiPublicKey: "02fa9dbc9eeb32897675eab279687e0e73319791c9754c03a4d66a160e69703c47", + apiPrivateKey: "72d77ee664c821a9ff285373c751c3205e82b33f3763cd8219114e455b6971a9", + defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, + }); + const { register: authFormRegister, handleSubmit: authFormSubmit } = useForm(); const { register: injectCredentialsFormRegister, handleSubmit: injectCredentialsFormSubmit, - } = useForm(); - + } = useForm() const handleGoogleLogin = async (response: any) => { let targetSubOrgId: string; const getSuborgsResponse = await axios.post("api/getSuborgs", { @@ -135,9 +142,8 @@ export default function AuthPage() { priority /> - {!authIframeClient &&

Loading...

} - + {authIframeClient && authIframeClient.iframePublicKey && authResponse === null && ( diff --git a/examples/react-components/next-env.d.ts b/examples/react-components/next-env.d.ts new file mode 100644 index 000000000..40c3d6809 --- /dev/null +++ b/examples/react-components/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/package.json b/package.json index 8b1911456..fb33deb00 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "rimraf": "^3.0.2", "rollup": "^4.22.4", "rollup-plugin-node-externals": "^6.1.2", + "rollup-plugin-postcss": "^4.0.2", "tsx": "^3.12.7", "typescript": "^5.1.4" }, diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index 76244006b..6ece15d0e 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -9,6 +9,11 @@ "import": "./dist/index.mjs", "require": "./dist/index.js", "default": "./dist/index.mjs" + }, + "./styles": { + "import": "./dist/styles.esm.css", + "require": "./dist/styles.cjs.css", + "default": "./dist/styles.esm.css" } }, "types": "./dist/index.d.ts", @@ -45,7 +50,10 @@ "dependencies": { "@turnkey/sdk-browser": "workspace:*", "@turnkey/wallet-stamper": "workspace:*", - "usehooks-ts": "^3.1.0" + "usehooks-ts": "^3.1.0", + "@turnkey/sdk-server": "workspace:*", + "react-hook-form": "^7.45.1", + "react-international-phone": "^4.3.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" diff --git a/packages/sdk-react/src/api/auth.ts b/packages/sdk-react/src/api/auth.ts new file mode 100644 index 000000000..9780e509c --- /dev/null +++ b/packages/sdk-react/src/api/auth.ts @@ -0,0 +1,39 @@ +import type {Turnkey as TurnkeySDKClient} from "@turnkey/sdk-server"; + +type AuthRequest = { + suborgID: string; + otpId: string; + otpCode: string; + targetPublicKey: string; +}; + +type AuthResponse = { + userId: string; + apiKeyId: string; + credentialBundle: string; +}; + +export async function auth( + request: AuthRequest, + turnkeyClient: TurnkeySDKClient +): Promise { + try { + const otpAuthResponse = await turnkeyClient.apiClient().otpAuth({ + otpId: request.otpId, + otpCode: request.otpCode, + targetPublicKey: request.targetPublicKey, + organizationId: request.suborgID + }); + const { credentialBundle, apiKeyId, userId } = otpAuthResponse; + + if (!apiKeyId || !credentialBundle || !userId) { + throw new Error("Expected a non-null otp auth response."); + } + + return { credentialBundle, apiKeyId, userId }; + } catch (e) { + return + } +} + + diff --git a/packages/sdk-react/src/api/getSuborgs.ts b/packages/sdk-react/src/api/getSuborgs.ts new file mode 100644 index 000000000..2cc259594 --- /dev/null +++ b/packages/sdk-react/src/api/getSuborgs.ts @@ -0,0 +1,32 @@ +import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; + +type GetSuborgsRequest = { + filterValue: string; + filterType: string; +}; + +type GetSuborgsResponse = { + organizationIds: string[]; +}; + +export async function getSuborgs( + request: GetSuborgsRequest, + turnkeyClient: TurnkeySDKClient +): Promise { + try { + const response = await turnkeyClient.apiClient().getSubOrgIds({ + organizationId: turnkeyClient.config.defaultOrganizationId, + filterType: request.filterType, + filterValue: request.filterValue, + }); + + if (!response || !response.organizationIds) { + throw new Error("Expected a non-null response with organizationIds."); + } + + return { organizationIds: response.organizationIds }; + } catch (e) { + console.error(e); + return + } +} diff --git a/packages/sdk-react/src/api/initAuth.ts b/packages/sdk-react/src/api/initAuth.ts new file mode 100644 index 000000000..09d14b229 --- /dev/null +++ b/packages/sdk-react/src/api/initAuth.ts @@ -0,0 +1,34 @@ +import type {Turnkey as TurnkeySDKClient} from "@turnkey/sdk-server"; + +type InitAuthRequest = { + suborgID: string; + otpType: string; + contact: string; +}; + +type InitAuthResponse = { + otpId: string; +}; + + +export async function initAuth( + request: InitAuthRequest, + turnkeyClient: TurnkeySDKClient +): Promise { + try { + const initOtpAuthResponse = await turnkeyClient.apiClient().initOtpAuth({ + contact: request.contact, + otpType: request.otpType, + organizationId: request.suborgID + }); + const { otpId } = initOtpAuthResponse; + + if (!otpId) { + throw new Error("Expected a non-null otpId."); + } + + return { otpId }; + } catch (e) { + return + } +} diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css new file mode 100644 index 000000000..00e531d7a --- /dev/null +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -0,0 +1,175 @@ +/* Auth Card */ +.authCard { + position: relative; /* Ensure contained elements respect authCard boundaries */ + width: 100%; + max-width: 450px; + margin: 0 auto; + padding: 20px; + background: var(--Greyscale-20, #F5F7FB); + border: 1px solid var(--Greyscale-100, #EBEDF2); + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.authCard h2 { + font-family: Inter; + font-size: 20px; + font-weight: 700; + line-height: 26px; + letter-spacing: -0.01em; + text-align: center; + font-size: 1.5rem; + margin-bottom: 16px; +} + +/* Form */ +.authForm { + margin-bottom: 16px; +} + +.inputGroup { + margin-bottom: 12px; +} + +.inputGroup input { + width: 100%; + padding: 12px; + font-size: 1rem; + border: 1px solid var(--Greyscale-200, #D8DBE3); + border-radius: 4px; + box-sizing: border-box; +} + +button { +padding: 10px 16px 10px 16px; +gap: 8px; +color: #ffffff; + width: 100%; + font-size: 1rem; + background: var(--Greyscale-900, #2B2F33); + border: 1px solid var(--Greyscale-800, #3F464B); + border-radius: 8px; + cursor: pointer; +} + +button:disabled { + color: var(--Greyscale-700, #A2A7AE); + background: var(--Greyscale-200, #DDDDDD); + border-color: var(--Greyscale-400, #A2A7AE); + cursor: not-allowed; +} + + + + + +.passkeyButton { + margin-top: 12px; + padding: 10px 16px 10px 16px; +gap: 8px; +color: var(--Greyscale-600, #6C727E); + + width: 100%; + font-size: 1rem; + background: var(--Greyscale-1, #FFFFFF); + border: 1px solid var(--Greyscale-400, #A2A7AE); + border-radius: 8px; + cursor: pointer; +} + +.passkeyButton:disabled { + color: var(--Greyscale-500, #A2A7AE); + background: var(--Greyscale-200, #DDDDDD); + border-color: var(--Greyscale-400, #A2A7AE); + cursor: not-allowed; +} +/* Separator */ +.separator { + text-align: center; + margin: 16px 0; + position: relative; +} + +.separator span { + color: var(--Greyscale-500, #868C95); + padding: 0 10px; + position: relative; + z-index: 1; + font-size: 0.75rem; + background: var(--Greyscale-20, #F5F7FB); + color: #666; +} + +.separator::before { + content: ""; + display: block; + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 1px; + background: var(--Greyscale-100, #EBEDF2); + z-index: 0; +} +.tos { + font-family: Inter; + font-size: 12px; + font-weight: 400; + line-height: 16px; + letter-spacing: -0.01em; + text-align: center; + color: var(--Greyscale-500, #868C95); + margin-top: 16px; +} + +.tosBold { + color: var(--Greyscale-900, #2B2F33); + font-weight: 700; + cursor: pointer; +} +/* Auth Button Styles */ +.google, +.facebook, +.apple { + display: block; + width: 100%; + padding: 12px; + font-size: 1rem; + margin-top: 8px; + color: #fff; + background-color: #4285f4; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.google:hover { + background-color: #357ae8; +} + +.facebook { + background-color: #3b5998; +} + +.apple { + background-color: #000; +} + +.poweredBy { + display: flex; + align-items: center; + justify-content: center; + gap: 2px; + font-family: Inter; + font-size: 12px; + font-weight: 400; + line-height: 16px; + letter-spacing: -0.01em; + color: var(--Greyscale-500, #868C95); + margin-top: 16px; +} + +.poweredBy span { + position: relative; + top: -1px; +} diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx new file mode 100644 index 000000000..2f4ed8926 --- /dev/null +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -0,0 +1,200 @@ +import styles from "./Auth.module.css"; +import "./PhoneInput.css" +import { SetStateAction, useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { useTurnkey } from "../../hooks/useTurnkey"; +import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; +import { initAuth } from "../../api/initAuth"; +import { getSuborgs } from "../../api/getSuborgs"; +import {PhoneInput} from 'react-international-phone'; + +interface AuthProps { + turnkeyClient: TurnkeySDKClient; +} + +const emailSchema = { + email: "", +}; + +const phoneSchema = { + phone: "", +}; + +const otpSchema = { + otp: "", +}; + +const Auth: React.FC = ({ turnkeyClient }) => { + const { turnkey, passkeyClient, authIframeClient } = useTurnkey(); + const [loadingAction, setLoadingAction] = useState(null); + const [error, setError] = useState(null); + const [phone, setPhone] = useState(""); + const [otpId, setOtpId] = useState(null); + const [authConfig, setAuthConfig] = useState({ + email: true, + passkey: true, + phone: true, + socials: { + google: true, + facebook: true, + apple: true, + }, + }); + + const emailForm = useForm({ + defaultValues: emailSchema, + }); + + const phoneForm = useForm({ + defaultValues: phoneSchema, + }); + + const otpForm = useForm({ + defaultValues: otpSchema, + }); + + useEffect(() => { + if (error) { + alert(error); + } + }, [error]); + + const handlePasskeyLogin = async (email: string) => { + setLoadingAction("passkey"); + setLoadingAction(null); + }; + + const handleEmailLogin = async (email: string) => { + const getSuborgsRequest = { + filterType: "EMAIL", + filterValue: email, + }; + const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); + + const initAuthRequest = { + suborgID: getSuborgsResponse!.organizationIds[0]!, + otpType: "OTP_TYPE_EMAIL", + contact: email, + }; + const initAuthResponse = await initAuth(initAuthRequest, turnkeyClient); + + setOtpId(initAuthResponse!.otpId); + setLoadingAction("email"); + setLoadingAction(null); + }; + + const handlePhoneLogin = async () => { + if (!phone) return; + setLoadingAction("phone"); + setLoadingAction(null); + }; + + const handleEnterOtp = async (otp: string) => { + setLoadingAction("otp"); + // Here you would add your logic to verify the OTP using the otpId + console.log(`Verifying OTP: ${otp} with otpId: ${otpId}`); + setLoadingAction(null); + }; + + const onSubmitEmail = async (values: any) => { + const email = values.email; + await handleEmailLogin(email); + }; + + const onSubmitPhone = async (values: any) => { + // TODO: Handle phone login + }; + + const onSubmitOtp = async (values: any) => { + const otp = values.otp; + await handleEnterOtp(otp); + }; + + return ( +
+

Log in or sign up

+
+ {authConfig.email && !otpId && ( +
+
+ +
+ +
+ )} + +{authConfig.passkey && !otpId && ( +
+ +
+ )} + + {authConfig.phone && ( +
+
+ ) => setPhone(phone)} + /> +
+ +
+ )} + + {otpId && ( +
+
+ +
+ +
+ )} +
+ +
+ OR +
+ +
+By logging in you agree to our Terms of Service & Privacy Policy +
+ +
+ Powered by + + + + + + + + + + + +
+ {/* {authConfig.socials.google && } + {authConfig.socials.facebook && } + {authConfig.socials.apple && } */} +
+ ); +}; + +export default Auth; diff --git a/packages/sdk-react/src/components/auth/PhoneInput.css b/packages/sdk-react/src/components/auth/PhoneInput.css new file mode 100644 index 000000000..2efd88a7c --- /dev/null +++ b/packages/sdk-react/src/components/auth/PhoneInput.css @@ -0,0 +1,191 @@ +.reactInternationalPhoneCountrySelector { + position: relative; + } + + .reactInternationalPhoneCountrySelectorButton { + display: flex; + height: var(--react-international-phone-height, 36px); + box-sizing: border-box; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid var(--react-international-phone-country-selector-border-color, var(--react-international-phone-border-color, gainsboro)); + margin: 0; + appearance: button; + -webkit-appearance: button; + background-color: var(--react-international-phone-country-selector-background-color, var(--react-international-phone-background-color, white)); + cursor: pointer; + text-transform: none; + user-select: none; + } + + .reactInternationalPhoneCountrySelectorButton:hover { + background-color: var(--react-international-phone-country-selector-background-color-hover, whitesmoke); + } + + .reactInternationalPhoneCountrySelectorButtonHideDropdown { + cursor: auto; + } + + .reactInternationalPhoneCountrySelectorButtonHideDropdown:hover { + background-color: transparent; + } + + .reactInternationalPhoneCountrySelectorButtonContent { + display: flex; + align-items: center; + justify-content: center; + } + + .reactInternationalPhoneFlagEmoji { + margin: 0 4px; + } + + .reactInternationalPhoneFlagEmojiDisabled { + opacity: 0.75; + } + + .reactInternationalPhoneDropdownArrow { + border-top: var(--react-international-phone-country-selector-arrow-size, 4px) solid var(--react-international-phone-country-selector-arrow-color, #777); + border-right: var(--react-international-phone-country-selector-arrow-size, 4px) solid transparent; + border-left: var(--react-international-phone-country-selector-arrow-size, 4px) solid transparent; + margin-right: 4px; + transition: all 0.1s ease-out; + } + + .reactInternationalPhoneDropdownArrowActive { + transform: rotateX(180deg); + } + + .reactInternationalPhoneDropdownArrowDisabled { + border-top-color: var(--react-international-phone-disabled-country-selector-arrow-color, #999); + } + + .reactInternationalPhoneCountrySelectorButtonDisabled { + background-color: var(--react-international-phone-disabled-country-selector-background-color, var(--react-international-phone-disabled-background-color, whitesmoke)); + cursor: auto; + } + + .reactInternationalPhoneCountrySelectorButtonDisabled:hover { + background-color: var(--react-international-phone-disabled-country-selector-background-color, var(--react-international-phone-disabled-background-color, whitesmoke)); + } + + .reactInternationalPhoneCountrySelectorDropdown { + position: absolute; + z-index: 1; + top: var(--react-international-phone-dropdown-top, 44px); + left: var(--react-international-phone-dropdown-left, 0); + display: flex; + width: 300px; + max-height: 200px; + flex-direction: column; + padding: 4px 0; + margin: 0; + background-color: var(--react-international-phone-dropdown-item-background-color, var(--react-international-phone-background-color, white)); + box-shadow: var(--react-international-phone-dropdown-shadow, 2px 2px 16px rgba(0, 0, 0, 0.25)); + color: var(--react-international-phone-dropdown-item-text-color, var(--react-international-phone-text-color, #222)); + list-style: none; + overflow-y: scroll; + } + + .reactInternationalPhoneCountrySelectorDropdownPreferredListDivider { + height: 1px; + border: none; + margin: var(--react-international-phone-dropdown-preferred-list-divider-margin, 0); + background: var(--react-international-phone-dropdown-preferred-list-divider-color, var(--react-international-phone-border-color, gainsboro)); + } + + .reactInternationalPhoneCountrySelectorDropdownListItem { + display: flex; + min-height: var(--react-international-phone-dropdown-item-height, 28px); + box-sizing: border-box; + align-items: center; + padding: 2px 8px; + } + + .reactInternationalPhoneCountrySelectorDropdownListItemFlagEmoji { + margin-right: 8px; + } + + .reactInternationalPhoneCountrySelectorDropdownListItemCountryName { + overflow: hidden; + margin-right: 8px; + font-size: var(--react-international-phone-dropdown-item-font-size, 14px); + text-overflow: ellipsis; + white-space: nowrap; + } + + .reactInternationalPhoneCountrySelectorDropdownListItemDialCode { + color: var(--react-international-phone-dropdown-item-dial-code-color, gray); + font-size: var(--react-international-phone-dropdown-item-font-size, 14px); + } + + .reactInternationalPhoneCountrySelectorDropdownListItem:hover { + background-color: var(--react-international-phone-selected-dropdown-item-background-color, whitesmoke); + cursor: pointer; + } + + .reactInternationalPhoneCountrySelectorDropdownListItemSelected, + .reactInternationalPhoneCountrySelectorDropdownListItemFocused { + background-color: var(--react-international-phone-selected-dropdown-item-background-color, whitesmoke); + color: var(--react-international-phone-selected-dropdown-item-text-color, #222); + } + + .reactInternationalPhoneCountrySelectorDropdownListItemSelected .reactInternationalPhoneCountrySelectorDropdownListItemDialCode, + .reactInternationalPhoneCountrySelectorDropdownListItemFocused .reactInternationalPhoneCountrySelectorDropdownListItemDialCode { + color: var(--react-international-phone-selected-dropdown-item-dial-code-color, gray); + } + + .reactInternationalPhoneDialCodePreview { + display: flex; + align-items: center; + justify-content: center; + padding: 0 8px; + border: 1px solid var(--react-international-phone-dial-code-preview-border-color, gainsboro); + margin-right: -1px; + background-color: var(--react-international-phone-dial-code-preview-background-color, white); + color: var(--react-international-phone-dial-code-preview-text-color, #222); + font-size: var(--react-international-phone-dial-code-preview-font-size, 13px); + } + + .reactInternationalPhoneDialCodePreviewDisabled { + background-color: var(--react-international-phone-dial-code-preview-disabled-background-color, whitesmoke); + color: var(--react-international-phone-dial-code-preview-disabled-text-color, #666); + } + + .reactInternationalPhoneInputContainer { + display: flex; + } + + .reactInternationalPhoneInputContainer .reactInternationalPhoneCountrySelectorButton { + border-radius: var(--react-international-phone-border-radius, 4px); + margin-right: -1px; + border-bottom-right-radius: 0; + border-top-right-radius: 0; + } + + .reactInternationalPhoneInputContainer .reactInternationalPhoneInput { + overflow: visible; + height: var(--react-international-phone-height, 36px); + box-sizing: border-box; + padding: 0 8px; + border: 1px solid var(--react-international-phone-border-color, gainsboro); + border-radius: var(--react-international-phone-border-radius, 4px); + margin: 0; + background-color: var(--react-international-phone-background-color, white); + border-bottom-left-radius: 0; + border-top-left-radius: 0; + color: var(--react-international-phone-text-color, #222); + font-family: inherit; + font-size: var(--react-international-phone-font-size, 13px); + } + + .reactInternationalPhoneInputContainer .reactInternationalPhoneInput:focus { + outline: none; + } + + .reactInternationalPhoneInputContainer .reactInternationalPhoneInputDisabled { + background-color: var(--react-international-phone-disabled-background-color, whitesmoke); + color: var(--react-international-phone-disabled-text-color, #666); + } + \ No newline at end of file diff --git a/packages/sdk-react/src/components/auth/index.ts b/packages/sdk-react/src/components/auth/index.ts new file mode 100644 index 000000000..46a0209a0 --- /dev/null +++ b/packages/sdk-react/src/components/auth/index.ts @@ -0,0 +1 @@ +export {default as Auth} from "./Auth" \ No newline at end of file diff --git a/packages/sdk-react/src/components/index.ts b/packages/sdk-react/src/components/index.ts new file mode 100644 index 000000000..63978cda7 --- /dev/null +++ b/packages/sdk-react/src/components/index.ts @@ -0,0 +1 @@ +export * from "./auth" \ No newline at end of file diff --git a/packages/sdk-react/src/index.ts b/packages/sdk-react/src/index.ts index 78f74c83d..cab836e5a 100644 --- a/packages/sdk-react/src/index.ts +++ b/packages/sdk-react/src/index.ts @@ -1,4 +1,6 @@ +import "./components/auth/Auth.module.css"; +import "./components/auth/PhoneInput.css"; import { TurnkeyContext, TurnkeyProvider } from "./contexts/TurnkeyContext"; -import { useTurnkey } from "./hooks/use-turnkey"; - -export { TurnkeyContext, TurnkeyProvider, useTurnkey }; +import { useTurnkey } from "./hooks/useTurnkey"; +export * from "./components" +export { TurnkeyContext, TurnkeyProvider, useTurnkey}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd397ad93..b917fd616 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: rollup-plugin-node-externals: specifier: ^6.1.2 version: 6.1.2(rollup@4.22.4) + rollup-plugin-postcss: + specifier: ^4.0.2 + version: 4.0.2(postcss@8.4.38) tsx: specifier: ^3.12.7 version: 3.12.7 @@ -1726,6 +1729,15 @@ importers: usehooks-ts: specifier: ^3.1.0 version: 3.1.0(react@18.2.0) + '@turnkey/sdk-server': + specifier: workspace:* + version: link:../sdk-server + react-hook-form: + specifier: ^7.45.1 + version: 7.45.1(react@18.2.0) + react-international-phone: + specifier: ^4.3.0 + version: 4.3.0(react@18.2.0) devDependencies: '@types/react': specifier: ^18.2.75 @@ -11825,6 +11837,11 @@ packages: wif: 4.0.0 dev: false + /@trysound/sax@0.2.0: + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + dev: true + /@tsconfig/node16-strictest@1.0.4: resolution: {integrity: sha512-kp6/DuAoKzHVv5U+p0uOesYbjrEvrYVNdQMl163a+yXXUv9twabvkCGEn3pmVxKXB45JU5MPGolDDWnONZL5ZQ==} dev: true @@ -13979,6 +13996,10 @@ packages: /bn.js@5.2.1: resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: true + /borsh@0.7.0: resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} dependencies: @@ -14291,6 +14312,15 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} + /caniuse-api@3.0.0: + resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} + dependencies: + browserslist: 4.22.3 + caniuse-lite: 1.0.30001581 + lodash.memoize: 4.1.2 + lodash.uniq: 4.5.0 + dev: true + /caniuse-lite@1.0.30001581: resolution: {integrity: sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==} @@ -14549,6 +14579,10 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + dev: true + /colorette@1.4.0: resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} @@ -14600,6 +14634,11 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + /commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + dev: true + /commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} @@ -14639,6 +14678,12 @@ packages: /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + /concat-with-sourcemaps@1.1.0: + resolution: {integrity: sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==} + dependencies: + source-map: 0.6.1 + dev: true + /confbox@0.1.7: resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} dev: false @@ -14831,24 +14876,107 @@ packages: deprecated: This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in. dev: true + /css-declaration-sorter@6.4.1(postcss@8.4.38): + resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} + engines: {node: ^10 || ^12 || >=14} + peerDependencies: + postcss: ^8.0.9 + dependencies: + postcss: 8.4.38 + dev: true + + /css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + dev: true + + /css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + dev: true + + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: true + /cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true - /cssom@0.3.8: - resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} + /cssnano-preset-default@5.2.14(postcss@8.4.38): + resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + css-declaration-sorter: 6.4.1(postcss@8.4.38) + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-calc: 8.2.4(postcss@8.4.38) + postcss-colormin: 5.3.1(postcss@8.4.38) + postcss-convert-values: 5.1.3(postcss@8.4.38) + postcss-discard-comments: 5.1.2(postcss@8.4.38) + postcss-discard-duplicates: 5.1.0(postcss@8.4.38) + postcss-discard-empty: 5.1.1(postcss@8.4.38) + postcss-discard-overridden: 5.1.0(postcss@8.4.38) + postcss-merge-longhand: 5.1.7(postcss@8.4.38) + postcss-merge-rules: 5.1.4(postcss@8.4.38) + postcss-minify-font-values: 5.1.0(postcss@8.4.38) + postcss-minify-gradients: 5.1.1(postcss@8.4.38) + postcss-minify-params: 5.1.4(postcss@8.4.38) + postcss-minify-selectors: 5.2.1(postcss@8.4.38) + postcss-normalize-charset: 5.1.0(postcss@8.4.38) + postcss-normalize-display-values: 5.1.0(postcss@8.4.38) + postcss-normalize-positions: 5.1.1(postcss@8.4.38) + postcss-normalize-repeat-style: 5.1.1(postcss@8.4.38) + postcss-normalize-string: 5.1.0(postcss@8.4.38) + postcss-normalize-timing-functions: 5.1.0(postcss@8.4.38) + postcss-normalize-unicode: 5.1.1(postcss@8.4.38) + postcss-normalize-url: 5.1.0(postcss@8.4.38) + postcss-normalize-whitespace: 5.1.1(postcss@8.4.38) + postcss-ordered-values: 5.1.3(postcss@8.4.38) + postcss-reduce-initial: 5.1.2(postcss@8.4.38) + postcss-reduce-transforms: 5.1.0(postcss@8.4.38) + postcss-svgo: 5.1.0(postcss@8.4.38) + postcss-unique-selectors: 5.1.1(postcss@8.4.38) + dev: true + + /cssnano-utils@3.1.0(postcss@8.4.38): + resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 dev: true - /cssom@0.5.0: - resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + /cssnano@5.1.15(postcss@8.4.38): + resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + cssnano-preset-default: 5.2.14(postcss@8.4.38) + lilconfig: 2.1.0 + postcss: 8.4.38 + yaml: 1.10.2 dev: true - /cssstyle@2.3.0: - resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} - engines: {node: '>=8'} + /csso@4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} dependencies: - cssom: 0.3.8 + css-tree: 1.1.3 dev: true /csstype@3.1.2: @@ -15178,6 +15306,31 @@ packages: deprecated: Use your platform's native DOMException instead dependencies: webidl-conversions: 7.0.0 + /dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + dev: true + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: true + + /domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: true + + /domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 dev: true /dot-case@3.0.4: @@ -15340,9 +15493,14 @@ packages: ansi-colors: 4.1.3 strip-ansi: 6.0.1 +<<<<<<< HEAD /entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} +======= + /entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} +>>>>>>> ddafbe76 (init commit - very messy) dev: true /env-paths@2.2.1: @@ -15959,6 +16117,10 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + /estree-walker@0.6.1: + resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} + dev: true + /estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true @@ -16110,7 +16272,6 @@ packages: /eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - dev: false /eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} @@ -16504,6 +16665,12 @@ packages: /functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + /generic-names@4.0.0: + resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==} + dependencies: + loader-utils: 3.3.1 + dev: true + /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -17172,6 +17339,19 @@ packages: dependencies: safer-buffer: 2.1.2 + /icss-replace-symbols@1.1.0: + resolution: {integrity: sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==} + dev: true + + /icss-utils@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + postcss: 8.4.38 + dev: true + /idb-keyval@6.2.1: resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==} dev: false @@ -17198,6 +17378,13 @@ packages: /immutable@4.3.4: resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==} + /import-cwd@3.0.0: + resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} + engines: {node: '>=8'} + dependencies: + import-from: 3.0.0 + dev: true + /import-fresh@2.0.0: resolution: {integrity: sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==} engines: {node: '>=4'} @@ -17213,6 +17400,13 @@ packages: parent-module: 1.0.1 resolve-from: 4.0.0 + /import-from@3.0.0: + resolution: {integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==} + engines: {node: '>=8'} + dependencies: + resolve-from: 5.0.0 + dev: true + /import-local@3.1.0: resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} engines: {node: '>=8'} @@ -18660,6 +18854,11 @@ packages: strip-bom: 3.0.0 dev: true + /loader-utils@3.3.1: + resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} + engines: {node: '>= 12.13.0'} + dev: true + /locate-path@2.0.0: resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} engines: {node: '>=4'} @@ -18702,6 +18901,10 @@ packages: /lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + /lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: true + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -18713,6 +18916,10 @@ packages: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} dev: false + /lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + dev: true + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -18839,6 +19046,10 @@ packages: inherits: 2.0.4 safe-buffer: 5.2.1 + /mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + dev: true + /memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} dev: false @@ -19549,7 +19760,6 @@ packages: /normalize-url@6.1.0: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} - dev: false /npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} @@ -19636,6 +19846,12 @@ packages: - which - write-file-atomic + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: true + /nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} dev: false @@ -19860,6 +20076,11 @@ packages: p-map: 2.1.0 dev: true + /p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + dev: true + /p-limit@1.3.0: resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} engines: {node: '>=4'} @@ -19914,6 +20135,21 @@ packages: dependencies: aggregate-error: 3.1.0 + /p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + dev: true + + /p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + dependencies: + p-finally: 1.0.0 + dev: true + /p-try@1.0.0: resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} engines: {node: '>=4'} @@ -20036,6 +20272,11 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + /pify@5.0.0: + resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} + engines: {node: '>=10'} + dev: true + /pino-abstract-transport@0.5.0: resolution: {integrity: sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==} dependencies: @@ -20126,6 +20367,76 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} + /postcss-calc@8.2.4(postcss@8.4.38): + resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} + peerDependencies: + postcss: ^8.2.2 + dependencies: + postcss: 8.4.38 + postcss-selector-parser: 6.1.1 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-colormin@5.3.1(postcss@8.4.38): + resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.22.3 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-convert-values@5.1.3(postcss@8.4.38): + resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.22.3 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-discard-comments@5.1.2(postcss@8.4.38): + resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + dev: true + + /postcss-discard-duplicates@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + dev: true + + /postcss-discard-empty@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + dev: true + + /postcss-discard-overridden@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + dev: true + /postcss-import@14.1.0(postcss@8.4.38): resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} engines: {node: '>=10.0.0'} @@ -20189,6 +20500,131 @@ packages: postcss: 8.4.38 yaml: 2.3.4 + /postcss-merge-longhand@5.1.7(postcss@8.4.38): + resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + stylehacks: 5.1.1(postcss@8.4.38) + dev: true + + /postcss-merge-rules@5.1.4(postcss@8.4.38): + resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.22.3 + caniuse-api: 3.0.0 + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-selector-parser: 6.1.1 + dev: true + + /postcss-minify-font-values@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-minify-gradients@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + colord: 2.9.3 + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-minify-params@5.1.4(postcss@8.4.38): + resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.22.3 + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-minify-selectors@5.2.1(postcss@8.4.38): + resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-selector-parser: 6.1.1 + dev: true + + /postcss-modules-extract-imports@3.1.0(postcss@8.4.38): + resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + postcss: 8.4.38 + dev: true + + /postcss-modules-local-by-default@4.0.5(postcss@8.4.38): + resolution: {integrity: sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + icss-utils: 5.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-selector-parser: 6.1.1 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-modules-scope@3.2.0(postcss@8.4.38): + resolution: {integrity: sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + postcss: 8.4.38 + postcss-selector-parser: 6.1.1 + dev: true + + /postcss-modules-values@4.0.0(postcss@8.4.38): + resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + icss-utils: 5.1.0(postcss@8.4.38) + postcss: 8.4.38 + dev: true + + /postcss-modules@4.3.1(postcss@8.4.38): + resolution: {integrity: sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==} + peerDependencies: + postcss: ^8.0.0 + dependencies: + generic-names: 4.0.0 + icss-replace-symbols: 1.1.0 + lodash.camelcase: 4.3.0 + postcss: 8.4.38 + postcss-modules-extract-imports: 3.1.0(postcss@8.4.38) + postcss-modules-local-by-default: 4.0.5(postcss@8.4.38) + postcss-modules-scope: 3.2.0(postcss@8.4.38) + postcss-modules-values: 4.0.0(postcss@8.4.38) + string-hash: 1.1.3 + dev: true + /postcss-nested@6.0.0(postcss@8.4.38): resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} engines: {node: '>=12.0'} @@ -20207,6 +20643,129 @@ packages: postcss: 8.4.38 postcss-selector-parser: 6.1.1 + /postcss-normalize-charset@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + dev: true + + /postcss-normalize-display-values@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-positions@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-repeat-style@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-string@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-timing-functions@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-unicode@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.22.3 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-url@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + normalize-url: 6.1.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-whitespace@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-ordered-values@5.1.3(postcss@8.4.38): + resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-reduce-initial@5.1.2(postcss@8.4.38): + resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.22.3 + caniuse-api: 3.0.0 + postcss: 8.4.38 + dev: true + + /postcss-reduce-transforms@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + /postcss-selector-parser@6.0.16: resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} engines: {node: '>=4'} @@ -20221,6 +20780,27 @@ packages: cssesc: 3.0.0 util-deprecate: 1.0.2 + /postcss-svgo@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + svgo: 2.8.0 + dev: true + + /postcss-unique-selectors@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-selector-parser: 6.1.1 + dev: true + /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -20309,6 +20889,11 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} + /promise.series@0.2.0: + resolution: {integrity: sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ==} + engines: {node: '>=0.12'} + dev: true + /promise@8.3.0: resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} dependencies: @@ -20565,6 +21150,14 @@ packages: react: 18.2.0 dev: false + /react-international-phone@4.3.0(react@18.2.0): + resolution: {integrity: sha512-lIntIkwq2j0m3j4RsRiGJl/buHMLBd+mQ9S9RfiX3KbiUCUtbawoPCV2r8BvyeRMZI0cDtovoukBCuZ+70QzJA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -21190,6 +21783,36 @@ packages: rollup: 4.22.4 dev: true + /rollup-plugin-postcss@4.0.2(postcss@8.4.38): + resolution: {integrity: sha512-05EaY6zvZdmvPUDi3uCcAQoESDcYnv8ogJJQRp6V5kZ6J6P7uAVJlrTZcaaA20wTH527YTnKfkAoPxWI/jPp4w==} + engines: {node: '>=10'} + peerDependencies: + postcss: 8.x + dependencies: + chalk: 4.1.2 + concat-with-sourcemaps: 1.1.0 + cssnano: 5.1.15(postcss@8.4.38) + import-cwd: 3.0.0 + p-queue: 6.6.2 + pify: 5.0.0 + postcss: 8.4.38 + postcss-load-config: 3.1.4(postcss@8.4.38) + postcss-modules: 4.3.1(postcss@8.4.38) + promise.series: 0.2.0 + resolve: 1.22.8 + rollup-pluginutils: 2.8.2 + safe-identifier: 0.4.2 + style-inject: 0.3.0 + transitivePeerDependencies: + - ts-node + dev: true + + /rollup-pluginutils@2.8.2: + resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} + dependencies: + estree-walker: 0.6.1 + dev: true + /rollup@4.22.4: resolution: {integrity: sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -21295,6 +21918,10 @@ packages: /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + /safe-identifier@0.4.2: + resolution: {integrity: sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==} + dev: true + /safe-regex-test@1.0.3: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} @@ -21729,6 +22356,11 @@ packages: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} dev: false + /stable@0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + dev: true + /stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -21792,6 +22424,10 @@ packages: resolution: {integrity: sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==} dev: true + /string-hash@1.1.3: + resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==} + dev: true + /string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -21934,6 +22570,10 @@ packages: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} dev: false + /style-inject@0.3.0: + resolution: {integrity: sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==} + dev: true + /styled-jsx@5.1.1(@babel/core@7.24.5)(react@18.2.0): resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -21970,6 +22610,17 @@ packages: react: 18.3.1 dev: false + /stylehacks@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.22.3 + postcss: 8.4.38 + postcss-selector-parser: 6.1.1 + dev: true + /sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -22026,6 +22677,20 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + /svgo@2.8.0: + resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} + engines: {node: '>=10.13.0'} + hasBin: true + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.0.0 + stable: 0.1.8 + dev: true + /symbol-observable@2.0.3: resolution: {integrity: sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==} engines: {node: '>=0.10'} diff --git a/rollup.config.base.mjs b/rollup.config.base.mjs index e558e2ace..de74ad699 100644 --- a/rollup.config.base.mjs +++ b/rollup.config.base.mjs @@ -1,6 +1,7 @@ import typescript from "@rollup/plugin-typescript"; import nodeExternals from "rollup-plugin-node-externals"; import path from "node:path"; +import postcss from 'rollup-plugin-postcss'; const getFormatConfig = (format) => { const pkgPath = path.join(process.cwd(), "package.json"); @@ -16,9 +17,16 @@ const getFormatConfig = (format) => { sourcemap: true, }, plugins: [ + postcss({ + modules: true, + extensions: ['.css', '.scss'], + use: ['sass'], + extract: `styles.${format}.css`, + minimize: true, + sourceMap: true, + }), typescript({ tsconfig: './tsconfig.json', - outputToFilesystem: false, compilerOptions: { outDir: "dist", composite: false, @@ -32,16 +40,12 @@ const getFormatConfig = (format) => { builtinsPrefix: 'ignore', }), ], - } -} - + }; +}; -export default () => { +export default () => { const esm = getFormatConfig('esm'); const cjs = getFormatConfig('cjs'); - - return [ - esm, - cjs - ] -} + + return [esm, cjs]; +}; From f9fd84008cfd5f1c7c10bc559811089662dd0bf6 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Tue, 29 Oct 2024 12:50:56 -0400 Subject: [PATCH 02/73] more styling updates --- packages/sdk-react/package.json | 6 + .../src/components/auth/Auth.module.css | 10 +- .../sdk-react/src/components/auth/Auth.tsx | 43 +- .../src/components/auth/PhoneInput.css | 192 +-- .../src/components/auth/PhoneInput.tsx | 127 ++ pnpm-lock.yaml | 1111 +++++++++++------ 6 files changed, 910 insertions(+), 579 deletions(-) create mode 100644 packages/sdk-react/src/components/auth/PhoneInput.tsx diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index 6ece15d0e..ca65c0bf2 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -48,6 +48,12 @@ "typecheck": "tsc -p tsconfig.typecheck.json" }, "dependencies": { + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", + "@mui/icons-material": "^6.1.5", + "@mui/material": "^6.1.5", + "@noble/hashes": "1.4.0", + "@react-oauth/google": "^0.12.1", "@turnkey/sdk-browser": "workspace:*", "@turnkey/wallet-stamper": "workspace:*", "usehooks-ts": "^3.1.0", diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index 00e531d7a..25f7dafe0 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -38,6 +38,9 @@ border: 1px solid var(--Greyscale-200, #D8DBE3); border-radius: 4px; box-sizing: border-box; + &::placeholder { + color: rgba(0, 0, 0, 0.4); + } } button { @@ -94,7 +97,7 @@ color: var(--Greyscale-600, #6C727E); color: var(--Greyscale-500, #868C95); padding: 0 10px; position: relative; - z-index: 1; + z-index: 0; font-size: 0.75rem; background: var(--Greyscale-20, #F5F7FB); color: #666; @@ -173,3 +176,8 @@ color: var(--Greyscale-600, #6C727E); position: relative; top: -1px; } + +.phoneInput { + margin-top: 12px; + margin-bottom: 12px; +} \ No newline at end of file diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 2f4ed8926..d06584215 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -1,13 +1,15 @@ import styles from "./Auth.module.css"; -import "./PhoneInput.css" import { SetStateAction, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { useTurnkey } from "../../hooks/useTurnkey"; import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; import { initAuth } from "../../api/initAuth"; import { getSuborgs } from "../../api/getSuborgs"; -import {PhoneInput} from 'react-international-phone'; - +import { MuiPhone } from "./PhoneInput"; +import type { SetStateAction } from "react"; +import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google"; +import { sha256 } from "@noble/hashes/sha2"; +import { bytesToHex } from "@noble/hashes/utils"; interface AuthProps { turnkeyClient: TurnkeySDKClient; } @@ -64,6 +66,10 @@ const Auth: React.FC = ({ turnkeyClient }) => { setLoadingAction(null); }; + const handleGoogleLogin = async() => { + setLoadingAction("google"); + setLoadingAction(null); + } const handleEmailLogin = async (email: string) => { const getSuborgsRequest = { filterType: "EMAIL", @@ -138,18 +144,19 @@ const Auth: React.FC = ({ turnkeyClient }) => { )} {authConfig.phone && ( +
+
+ OR +
-
- ) => setPhone(phone)} - /> +
+ ) => setPhone(phone)}/>
+
)} {otpId && ( @@ -171,7 +178,20 @@ const Auth: React.FC = ({ turnkeyClient }) => {
OR
- + {authConfig.socials.google && authIframeClient && + + + + + } + {/* {authConfig.socials.facebook && } + {authConfig.socials.apple && } */}
By logging in you agree to our Terms of Service & Privacy Policy
@@ -190,9 +210,6 @@ const Auth: React.FC = ({ turnkeyClient }) => {
- {/* {authConfig.socials.google && } - {authConfig.socials.facebook && } - {authConfig.socials.apple && } */} ); }; diff --git a/packages/sdk-react/src/components/auth/PhoneInput.css b/packages/sdk-react/src/components/auth/PhoneInput.css index 2efd88a7c..287d4a11f 100644 --- a/packages/sdk-react/src/components/auth/PhoneInput.css +++ b/packages/sdk-react/src/components/auth/PhoneInput.css @@ -1,191 +1 @@ -.reactInternationalPhoneCountrySelector { - position: relative; - } - - .reactInternationalPhoneCountrySelectorButton { - display: flex; - height: var(--react-international-phone-height, 36px); - box-sizing: border-box; - align-items: center; - justify-content: center; - padding: 0; - border: 1px solid var(--react-international-phone-country-selector-border-color, var(--react-international-phone-border-color, gainsboro)); - margin: 0; - appearance: button; - -webkit-appearance: button; - background-color: var(--react-international-phone-country-selector-background-color, var(--react-international-phone-background-color, white)); - cursor: pointer; - text-transform: none; - user-select: none; - } - - .reactInternationalPhoneCountrySelectorButton:hover { - background-color: var(--react-international-phone-country-selector-background-color-hover, whitesmoke); - } - - .reactInternationalPhoneCountrySelectorButtonHideDropdown { - cursor: auto; - } - - .reactInternationalPhoneCountrySelectorButtonHideDropdown:hover { - background-color: transparent; - } - - .reactInternationalPhoneCountrySelectorButtonContent { - display: flex; - align-items: center; - justify-content: center; - } - - .reactInternationalPhoneFlagEmoji { - margin: 0 4px; - } - - .reactInternationalPhoneFlagEmojiDisabled { - opacity: 0.75; - } - - .reactInternationalPhoneDropdownArrow { - border-top: var(--react-international-phone-country-selector-arrow-size, 4px) solid var(--react-international-phone-country-selector-arrow-color, #777); - border-right: var(--react-international-phone-country-selector-arrow-size, 4px) solid transparent; - border-left: var(--react-international-phone-country-selector-arrow-size, 4px) solid transparent; - margin-right: 4px; - transition: all 0.1s ease-out; - } - - .reactInternationalPhoneDropdownArrowActive { - transform: rotateX(180deg); - } - - .reactInternationalPhoneDropdownArrowDisabled { - border-top-color: var(--react-international-phone-disabled-country-selector-arrow-color, #999); - } - - .reactInternationalPhoneCountrySelectorButtonDisabled { - background-color: var(--react-international-phone-disabled-country-selector-background-color, var(--react-international-phone-disabled-background-color, whitesmoke)); - cursor: auto; - } - - .reactInternationalPhoneCountrySelectorButtonDisabled:hover { - background-color: var(--react-international-phone-disabled-country-selector-background-color, var(--react-international-phone-disabled-background-color, whitesmoke)); - } - - .reactInternationalPhoneCountrySelectorDropdown { - position: absolute; - z-index: 1; - top: var(--react-international-phone-dropdown-top, 44px); - left: var(--react-international-phone-dropdown-left, 0); - display: flex; - width: 300px; - max-height: 200px; - flex-direction: column; - padding: 4px 0; - margin: 0; - background-color: var(--react-international-phone-dropdown-item-background-color, var(--react-international-phone-background-color, white)); - box-shadow: var(--react-international-phone-dropdown-shadow, 2px 2px 16px rgba(0, 0, 0, 0.25)); - color: var(--react-international-phone-dropdown-item-text-color, var(--react-international-phone-text-color, #222)); - list-style: none; - overflow-y: scroll; - } - - .reactInternationalPhoneCountrySelectorDropdownPreferredListDivider { - height: 1px; - border: none; - margin: var(--react-international-phone-dropdown-preferred-list-divider-margin, 0); - background: var(--react-international-phone-dropdown-preferred-list-divider-color, var(--react-international-phone-border-color, gainsboro)); - } - - .reactInternationalPhoneCountrySelectorDropdownListItem { - display: flex; - min-height: var(--react-international-phone-dropdown-item-height, 28px); - box-sizing: border-box; - align-items: center; - padding: 2px 8px; - } - - .reactInternationalPhoneCountrySelectorDropdownListItemFlagEmoji { - margin-right: 8px; - } - - .reactInternationalPhoneCountrySelectorDropdownListItemCountryName { - overflow: hidden; - margin-right: 8px; - font-size: var(--react-international-phone-dropdown-item-font-size, 14px); - text-overflow: ellipsis; - white-space: nowrap; - } - - .reactInternationalPhoneCountrySelectorDropdownListItemDialCode { - color: var(--react-international-phone-dropdown-item-dial-code-color, gray); - font-size: var(--react-international-phone-dropdown-item-font-size, 14px); - } - - .reactInternationalPhoneCountrySelectorDropdownListItem:hover { - background-color: var(--react-international-phone-selected-dropdown-item-background-color, whitesmoke); - cursor: pointer; - } - - .reactInternationalPhoneCountrySelectorDropdownListItemSelected, - .reactInternationalPhoneCountrySelectorDropdownListItemFocused { - background-color: var(--react-international-phone-selected-dropdown-item-background-color, whitesmoke); - color: var(--react-international-phone-selected-dropdown-item-text-color, #222); - } - - .reactInternationalPhoneCountrySelectorDropdownListItemSelected .reactInternationalPhoneCountrySelectorDropdownListItemDialCode, - .reactInternationalPhoneCountrySelectorDropdownListItemFocused .reactInternationalPhoneCountrySelectorDropdownListItemDialCode { - color: var(--react-international-phone-selected-dropdown-item-dial-code-color, gray); - } - - .reactInternationalPhoneDialCodePreview { - display: flex; - align-items: center; - justify-content: center; - padding: 0 8px; - border: 1px solid var(--react-international-phone-dial-code-preview-border-color, gainsboro); - margin-right: -1px; - background-color: var(--react-international-phone-dial-code-preview-background-color, white); - color: var(--react-international-phone-dial-code-preview-text-color, #222); - font-size: var(--react-international-phone-dial-code-preview-font-size, 13px); - } - - .reactInternationalPhoneDialCodePreviewDisabled { - background-color: var(--react-international-phone-dial-code-preview-disabled-background-color, whitesmoke); - color: var(--react-international-phone-dial-code-preview-disabled-text-color, #666); - } - - .reactInternationalPhoneInputContainer { - display: flex; - } - - .reactInternationalPhoneInputContainer .reactInternationalPhoneCountrySelectorButton { - border-radius: var(--react-international-phone-border-radius, 4px); - margin-right: -1px; - border-bottom-right-radius: 0; - border-top-right-radius: 0; - } - - .reactInternationalPhoneInputContainer .reactInternationalPhoneInput { - overflow: visible; - height: var(--react-international-phone-height, 36px); - box-sizing: border-box; - padding: 0 8px; - border: 1px solid var(--react-international-phone-border-color, gainsboro); - border-radius: var(--react-international-phone-border-radius, 4px); - margin: 0; - background-color: var(--react-international-phone-background-color, white); - border-bottom-left-radius: 0; - border-top-left-radius: 0; - color: var(--react-international-phone-text-color, #222); - font-family: inherit; - font-size: var(--react-international-phone-font-size, 13px); - } - - .reactInternationalPhoneInputContainer .reactInternationalPhoneInput:focus { - outline: none; - } - - .reactInternationalPhoneInputContainer .reactInternationalPhoneInputDisabled { - background-color: var(--react-international-phone-disabled-background-color, whitesmoke); - color: var(--react-international-phone-disabled-text-color, #666); - } - \ No newline at end of file +@import 'react-international-phone/style.css'; \ No newline at end of file diff --git a/packages/sdk-react/src/components/auth/PhoneInput.tsx b/packages/sdk-react/src/components/auth/PhoneInput.tsx new file mode 100644 index 000000000..d4f4ce7cb --- /dev/null +++ b/packages/sdk-react/src/components/auth/PhoneInput.tsx @@ -0,0 +1,127 @@ +import "./PhoneInput.css"; +import { + BaseTextFieldProps, + InputAdornment, + MenuItem, + Select, + TextField, + Typography, +} from "@mui/material"; +import { + CountryIso2, + defaultCountries, + FlagImage, + parseCountry, + usePhoneInput, +} from "react-international-phone"; + +const countries = defaultCountries.filter((country) => { + const { iso2 } = parseCountry(country); + return ['us', 'ca'].includes(iso2); +}); + + +export interface MUIPhoneProps extends BaseTextFieldProps { + value: string; + onChange: (phone: string) => void; +} + +export const MuiPhone: React.FC = ({ + value, + onChange, + ...restProps +}) => { + const { inputValue, handlePhoneValueChange, inputRef, country, setCountry } = + usePhoneInput({ + defaultCountry: "us", + disableDialCodeAndPrefix: true, + value, + countries: countries, + onChange: (data) => { + onChange(data.phone); + }, + }); + + return ( + + + + ), + }} + {...restProps} + /> + ); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b917fd616..86411ea08 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -829,7 +829,7 @@ importers: dependencies: '@biconomy/account': specifier: ^4.5.6 - version: 4.5.6(typescript@5.1.3)(viem@2.21.29) + version: 4.5.6(typescript@5.1.3)(viem@2.21.48) '@turnkey/api-key-stamper': specifier: workspace:* version: link:../../packages/api-key-stamper @@ -904,7 +904,7 @@ importers: version: 5.1.3 viem: specifier: ^2.21.9 - version: 2.21.29(typescript@5.1.3) + version: 2.21.48(typescript@5.1.3) examples/with-ethers: dependencies: @@ -1382,7 +1382,7 @@ importers: version: 0.9.35(@solana/web3.js@1.88.1)(bs58@5.0.0)(react-dom@18.3.1)(react-native@0.74.0)(react@18.3.1) '@solana/wallet-adapter-wallets': specifier: ^0.19.32 - version: 0.19.32(@babel/core@7.24.5)(@babel/runtime@7.25.6)(@solana/web3.js@1.88.1)(bs58@5.0.0)(react-dom@18.3.1)(react-native@0.74.0)(react@18.3.1)(tslib@2.8.1) + version: 0.19.32(@babel/core@7.24.5)(@babel/runtime@7.26.0)(@solana/web3.js@1.88.1)(bs58@5.0.0)(react-dom@18.3.1)(react-native@0.74.0)(react@18.3.1)(tslib@2.8.1) '@solana/web3.js': specifier: ^1.88.1 version: 1.88.1 @@ -1720,24 +1720,42 @@ importers: packages/sdk-react: dependencies: + '@emotion/react': + specifier: ^11.13.3 + version: 11.13.3(@types/react@18.2.75)(react@18.2.0) + '@emotion/styled': + specifier: ^11.13.0 + version: 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.75)(react@18.2.0) + '@mui/icons-material': + specifier: ^6.1.5 + version: 6.1.5(@mui/material@6.1.5)(@types/react@18.2.75)(react@18.2.0) + '@mui/material': + specifier: ^6.1.5 + version: 6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.75)(react-dom@18.3.1)(react@18.2.0) + '@noble/hashes': + specifier: 1.4.0 + version: 1.4.0 + '@react-oauth/google': + specifier: ^0.12.1 + version: 0.12.1(react-dom@18.3.1)(react@18.2.0) '@turnkey/sdk-browser': specifier: workspace:* version: link:../sdk-browser - '@turnkey/wallet-stamper': - specifier: workspace:* - version: link:../wallet-stamper - usehooks-ts: - specifier: ^3.1.0 - version: 3.1.0(react@18.2.0) '@turnkey/sdk-server': specifier: workspace:* version: link:../sdk-server + '@turnkey/wallet-stamper': + specifier: workspace:* + version: link:../wallet-stamper react-hook-form: specifier: ^7.45.1 version: 7.45.1(react@18.2.0) react-international-phone: specifier: ^4.3.0 version: 4.3.0(react@18.2.0) + usehooks-ts: + specifier: ^3.1.0 + version: 3.1.0(react@18.2.0) devDependencies: '@types/react': specifier: ^18.2.75 @@ -1854,7 +1872,7 @@ importers: optionalDependencies: viem: specifier: ^2.21.35 - version: 2.21.35(typescript@5.1.5) + version: 2.21.48(typescript@5.1.5) devDependencies: '@solana/web3.js': specifier: ^1.88.1 @@ -1989,7 +2007,7 @@ packages: '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5) '@babel/helpers': 7.24.5 '@babel/parser': 7.24.8 - '@babel/template': 7.24.0 + '@babel/template': 7.24.7 '@babel/traverse': 7.23.2 '@babel/types': 7.24.9 convert-source-map: 2.0.0 @@ -2004,7 +2022,7 @@ packages: resolution: {integrity: sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 '@jridgewell/gen-mapping': 0.3.5 jsesc: 2.5.2 dev: false @@ -2022,7 +2040,7 @@ packages: resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 dev: false /@babel/helper-annotate-as-pure@7.22.5: @@ -2044,7 +2062,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/helper-explode-assignable-expression': 7.18.6 - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 dev: false /@babel/helper-compilation-targets@7.20.7(@babel/core@7.20.12): @@ -2090,13 +2108,13 @@ packages: dependencies: '@babel/core': 7.20.12 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 '@babel/helper-member-expression-to-functions': 7.23.0 '@babel/helper-optimise-call-expression': 7.22.5 '@babel/helper-replace-supers': 7.22.20(@babel/core@7.20.12) '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/helper-split-export-declaration': 7.24.5 + '@babel/helper-split-export-declaration': 7.24.7 dev: false /@babel/helper-create-class-features-plugin@7.20.12(@babel/core@7.24.5): @@ -2107,13 +2125,13 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 '@babel/helper-member-expression-to-functions': 7.23.0 '@babel/helper-optimise-call-expression': 7.22.5 '@babel/helper-replace-supers': 7.22.20(@babel/core@7.24.5) '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/helper-split-export-declaration': 7.24.5 + '@babel/helper-split-export-declaration': 7.24.7 dev: false /@babel/helper-create-class-features-plugin@7.24.8(@babel/core@7.24.5): @@ -2205,16 +2223,6 @@ packages: - supports-color dev: false - /@babel/helper-environment-visitor@7.18.9: - resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helper-environment-visitor@7.22.20: - resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} - engines: {node: '>=6.9.0'} - dev: false - /@babel/helper-environment-visitor@7.24.7: resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} engines: {node: '>=6.9.0'} @@ -2228,22 +2236,6 @@ packages: '@babel/types': 7.24.9 dev: false - /@babel/helper-function-name@7.19.0: - resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.24.0 - '@babel/types': 7.24.5 - dev: false - - /@babel/helper-function-name@7.23.0: - resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.24.0 - '@babel/types': 7.24.5 - dev: false - /@babel/helper-function-name@7.24.7: resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} engines: {node: '>=6.9.0'} @@ -2251,18 +2243,11 @@ packages: '@babel/template': 7.24.7 '@babel/types': 7.24.9 - /@babel/helper-hoist-variables@7.18.6: - resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.5 - dev: false - /@babel/helper-hoist-variables@7.22.5: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 /@babel/helper-member-expression-to-functions@7.23.0: resolution: {integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==} @@ -2281,19 +2266,6 @@ packages: - supports-color dev: false - /@babel/helper-module-imports@7.18.6: - resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.5 - dev: false - - /@babel/helper-module-imports@7.24.3: - resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.9 - /@babel/helper-module-imports@7.24.7: resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} engines: {node: '>=6.9.0'} @@ -2302,20 +2274,19 @@ packages: '@babel/types': 7.24.9 transitivePeerDependencies: - supports-color - dev: false /@babel/helper-module-transforms@7.20.11: resolution: {integrity: sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-module-imports': 7.18.6 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-module-imports': 7.24.7 '@babel/helper-simple-access': 7.20.2 - '@babel/helper-split-export-declaration': 7.18.6 + '@babel/helper-split-export-declaration': 7.24.7 '@babel/helper-validator-identifier': 7.24.7 - '@babel/template': 7.22.15 + '@babel/template': 7.24.7 '@babel/traverse': 7.23.2 - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 transitivePeerDependencies: - supports-color dev: false @@ -2327,11 +2298,13 @@ packages: '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.20.12 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-module-imports': 7.24.3 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-module-imports': 7.24.7 '@babel/helper-simple-access': 7.24.5 - '@babel/helper-split-export-declaration': 7.24.5 + '@babel/helper-split-export-declaration': 7.24.7 '@babel/helper-validator-identifier': 7.24.7 + transitivePeerDependencies: + - supports-color dev: false /@babel/helper-module-transforms@7.22.20(@babel/core@7.24.5): @@ -2341,11 +2314,13 @@ packages: '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.24.5 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-module-imports': 7.24.3 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-module-imports': 7.24.7 '@babel/helper-simple-access': 7.24.5 - '@babel/helper-split-export-declaration': 7.24.5 + '@babel/helper-split-export-declaration': 7.24.7 '@babel/helper-validator-identifier': 7.24.7 + transitivePeerDependencies: + - supports-color dev: false /@babel/helper-module-transforms@7.24.5(@babel/core@7.24.5): @@ -2356,10 +2331,12 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-module-imports': 7.24.3 + '@babel/helper-module-imports': 7.24.7 '@babel/helper-simple-access': 7.24.5 '@babel/helper-split-export-declaration': 7.24.7 '@babel/helper-validator-identifier': 7.24.7 + transitivePeerDependencies: + - supports-color /@babel/helper-module-transforms@7.24.9(@babel/core@7.24.5): resolution: {integrity: sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==} @@ -2381,7 +2358,7 @@ packages: resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 dev: false /@babel/helper-optimise-call-expression@7.22.5: @@ -2420,9 +2397,9 @@ packages: dependencies: '@babel/core': 7.20.12 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-wrap-function': 7.20.5 - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 transitivePeerDependencies: - supports-color dev: false @@ -2435,9 +2412,9 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-wrap-function': 7.20.5 - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 transitivePeerDependencies: - supports-color dev: false @@ -2446,12 +2423,12 @@ packages: resolution: {integrity: sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-member-expression-to-functions': 7.23.0 '@babel/helper-optimise-call-expression': 7.22.5 - '@babel/template': 7.24.0 + '@babel/template': 7.24.7 '@babel/traverse': 7.23.2 - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 transitivePeerDependencies: - supports-color dev: false @@ -2498,7 +2475,7 @@ packages: resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 dev: false /@babel/helper-simple-access@7.24.5: @@ -2521,14 +2498,14 @@ packages: resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 dev: false /@babel/helper-skip-transparent-expression-wrappers@7.22.5: resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 dev: false /@babel/helper-skip-transparent-expression-wrappers@7.24.7: @@ -2541,35 +2518,12 @@ packages: - supports-color dev: false - /@babel/helper-split-export-declaration@7.18.6: - resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.5 - dev: false - - /@babel/helper-split-export-declaration@7.24.5: - resolution: {integrity: sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.9 - dev: false - /@babel/helper-split-export-declaration@7.24.7: resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.24.9 - /@babel/helper-string-parser@7.22.5: - resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helper-string-parser@7.24.1: - resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} - engines: {node: '>=6.9.0'} - /@babel/helper-string-parser@7.24.8: resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} engines: {node: '>=6.9.0'} @@ -2613,9 +2567,9 @@ packages: resolution: {integrity: sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.22.15 + '@babel/template': 7.24.7 '@babel/traverse': 7.23.2 - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 transitivePeerDependencies: - supports-color dev: false @@ -2644,16 +2598,9 @@ packages: engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 dev: false - /@babel/parser@7.24.5: - resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.24.5 - /@babel/parser@7.24.8: resolution: {integrity: sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==} engines: {node: '>=6.0.0'} @@ -2713,7 +2660,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.20.12 - '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.20.12) '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.20.12) @@ -2729,7 +2676,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.24.5 - '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.24.5) '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.5) @@ -3518,7 +3465,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.20.12 - '@babel/helper-module-imports': 7.18.6 + '@babel/helper-module-imports': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.20.12) transitivePeerDependencies: @@ -3532,7 +3479,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.24.5 - '@babel/helper-module-imports': 7.18.6 + '@babel/helper-module-imports': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.24.5) transitivePeerDependencies: @@ -3588,12 +3535,12 @@ packages: '@babel/core': 7.20.12 '@babel/helper-annotate-as-pure': 7.18.6 '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 '@babel/helper-optimise-call-expression': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-replace-supers': 7.20.7 - '@babel/helper-split-export-declaration': 7.18.6 + '@babel/helper-split-export-declaration': 7.24.7 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -3608,12 +3555,12 @@ packages: '@babel/core': 7.24.5 '@babel/helper-annotate-as-pure': 7.18.6 '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.19.0 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 '@babel/helper-optimise-call-expression': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-replace-supers': 7.20.7 - '@babel/helper-split-export-declaration': 7.18.6 + '@babel/helper-split-export-declaration': 7.24.7 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -3627,7 +3574,7 @@ packages: dependencies: '@babel/core': 7.20.12 '@babel/helper-plugin-utils': 7.22.5 - '@babel/template': 7.22.15 + '@babel/template': 7.24.7 dev: false /@babel/plugin-transform-computed-properties@7.20.7(@babel/core@7.24.5): @@ -3638,7 +3585,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/template': 7.22.15 + '@babel/template': 7.24.7 dev: false /@babel/plugin-transform-destructuring@7.20.7(@babel/core@7.20.12): @@ -3775,7 +3722,7 @@ packages: dependencies: '@babel/core': 7.20.12 '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-function-name': 7.19.0 + '@babel/helper-function-name': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 dev: false @@ -3787,7 +3734,7 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-function-name': 7.19.0 + '@babel/helper-function-name': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 dev: false @@ -3840,6 +3787,8 @@ packages: '@babel/core': 7.20.12 '@babel/helper-module-transforms': 7.22.20(@babel/core@7.20.12) '@babel/helper-plugin-utils': 7.22.5 + transitivePeerDependencies: + - supports-color dev: false /@babel/plugin-transform-modules-amd@7.20.11(@babel/core@7.24.5): @@ -3851,6 +3800,8 @@ packages: '@babel/core': 7.24.5 '@babel/helper-module-transforms': 7.22.20(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.22.5 + transitivePeerDependencies: + - supports-color dev: false /@babel/plugin-transform-modules-commonjs@7.20.11(@babel/core@7.20.12): @@ -3863,6 +3814,8 @@ packages: '@babel/helper-module-transforms': 7.22.20(@babel/core@7.20.12) '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-simple-access': 7.20.2 + transitivePeerDependencies: + - supports-color dev: false /@babel/plugin-transform-modules-commonjs@7.20.11(@babel/core@7.24.5): @@ -3875,6 +3828,8 @@ packages: '@babel/helper-module-transforms': 7.22.20(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-simple-access': 7.20.2 + transitivePeerDependencies: + - supports-color dev: false /@babel/plugin-transform-modules-commonjs@7.24.8(@babel/core@7.24.5): @@ -3898,10 +3853,12 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.20.12 - '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-module-transforms': 7.22.20(@babel/core@7.20.12) '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-validator-identifier': 7.24.7 + transitivePeerDependencies: + - supports-color dev: false /@babel/plugin-transform-modules-systemjs@7.20.11(@babel/core@7.24.5): @@ -3911,10 +3868,12 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.24.5 - '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-module-transforms': 7.22.20(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-validator-identifier': 7.24.7 + transitivePeerDependencies: + - supports-color dev: false /@babel/plugin-transform-modules-umd@7.18.6(@babel/core@7.20.12): @@ -3926,6 +3885,8 @@ packages: '@babel/core': 7.20.12 '@babel/helper-module-transforms': 7.22.20(@babel/core@7.20.12) '@babel/helper-plugin-utils': 7.22.5 + transitivePeerDependencies: + - supports-color dev: false /@babel/plugin-transform-modules-umd@7.18.6(@babel/core@7.24.5): @@ -3937,6 +3898,8 @@ packages: '@babel/core': 7.24.5 '@babel/helper-module-transforms': 7.22.20(@babel/core@7.24.5) '@babel/helper-plugin-utils': 7.22.5 + transitivePeerDependencies: + - supports-color dev: false /@babel/plugin-transform-named-capturing-groups-regex@7.20.5(@babel/core@7.20.12): @@ -4103,6 +4066,8 @@ packages: dependencies: '@babel/core': 7.20.12 '@babel/plugin-transform-react-jsx': 7.20.13(@babel/core@7.20.12) + transitivePeerDependencies: + - supports-color dev: false /@babel/plugin-transform-react-jsx-self@7.23.3(@babel/core@7.24.5): @@ -4133,10 +4098,12 @@ packages: dependencies: '@babel/core': 7.20.12 '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-module-imports': 7.18.6 + '@babel/helper-module-imports': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.20.12) - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 + transitivePeerDependencies: + - supports-color dev: false /@babel/plugin-transform-react-jsx@7.20.13(@babel/core@7.24.5): @@ -4147,10 +4114,12 @@ packages: dependencies: '@babel/core': 7.24.5 '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-module-imports': 7.18.6 + '@babel/helper-module-imports': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.24.5) - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 + transitivePeerDependencies: + - supports-color dev: false /@babel/plugin-transform-react-pure-annotations@7.18.6(@babel/core@7.20.12): @@ -4599,7 +4568,7 @@ packages: '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.20.12) '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.20.12) - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 esutils: 2.0.3 dev: false @@ -4612,7 +4581,7 @@ packages: '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.24.5) '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.24.5) - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 esutils: 2.0.3 dev: false @@ -4629,6 +4598,8 @@ packages: '@babel/plugin-transform-react-jsx': 7.20.13(@babel/core@7.20.12) '@babel/plugin-transform-react-jsx-development': 7.18.6(@babel/core@7.20.12) '@babel/plugin-transform-react-pure-annotations': 7.18.6(@babel/core@7.20.12) + transitivePeerDependencies: + - supports-color dev: false /@babel/preset-typescript@7.18.6(@babel/core@7.20.12): @@ -4689,8 +4660,8 @@ packages: dependencies: regenerator-runtime: 0.14.1 - /@babel/runtime@7.25.6: - resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} + /@babel/runtime@7.26.0: + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 @@ -4698,28 +4669,11 @@ packages: /@babel/template@7.20.7: resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.24.5 - '@babel/types': 7.24.5 - dev: false - - /@babel/template@7.22.15: - resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.24.5 - '@babel/types': 7.24.5 - dev: false - - /@babel/template@7.24.0: - resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} - engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.24.7 '@babel/parser': 7.24.8 '@babel/types': 7.24.9 + dev: false /@babel/template@7.24.7: resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} @@ -4750,7 +4704,7 @@ packages: resolution: {integrity: sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.24.1 + '@babel/helper-string-parser': 7.24.8 '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 dev: false @@ -4759,19 +4713,11 @@ packages: resolution: {integrity: sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-string-parser': 7.22.5 + '@babel/helper-string-parser': 7.24.8 '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 dev: false - /@babel/types@7.24.5: - resolution: {integrity: sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.24.1 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 - /@babel/types@7.24.9: resolution: {integrity: sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==} engines: {node: '>=6.9.0'} @@ -4784,7 +4730,7 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@biconomy/account@4.5.6(typescript@5.1.3)(viem@2.21.29): + /@biconomy/account@4.5.6(typescript@5.1.3)(viem@2.21.48): resolution: {integrity: sha512-Mq0X9HF4fsPTkf87eXklJJE/Hl3GQwQHN/2/D1fCA4Q4o4AM9HV7Tzpb+hBsh8cUJ+s2j0Q9wORXA1c0onAImQ==} peerDependencies: typescript: ^5 @@ -4793,7 +4739,7 @@ packages: '@silencelaboratories/walletprovider-sdk': 0.1.0(typescript@5.1.3) merkletreejs: 0.4.0 typescript: 5.1.3 - viem: 2.21.29(typescript@5.1.3) + viem: 2.21.48(typescript@5.1.3) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -4867,7 +4813,7 @@ packages: /@changesets/apply-release-plan@7.0.3: resolution: {integrity: sha512-klL6LCdmfbEe9oyfLxnidIf/stFXmrbFO/3gT5LU5pcyoZytzJe4gWpTBx3BPmyNPl16dZ1xrkcW7b98e3tYkA==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/config': 3.0.1 '@changesets/get-version-range-type': 0.4.0 '@changesets/git': 3.0.0 @@ -4886,7 +4832,7 @@ packages: /@changesets/assemble-release-plan@6.0.2: resolution: {integrity: sha512-n9/Tdq+ze+iUtjmq0mZO3pEhJTKkku9hUxtUadW30jlN7kONqJG3O6ALeXrmc6gsi/nvoCuKjqEJ68Hk8RbMTQ==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/errors': 0.2.0 '@changesets/get-dependents-graph': 2.1.0 '@changesets/should-skip-package': 0.1.0 @@ -4971,7 +4917,7 @@ packages: /@changesets/get-release-plan@4.0.2: resolution: {integrity: sha512-rOalz7nMuMV2vyeP7KBeAhqEB7FM2GFPO5RQSoOoUKKH9L6wW3QyPA2K+/rG9kBrWl2HckPVES73/AuwPvbH3w==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/assemble-release-plan': 6.0.2 '@changesets/config': 3.0.1 '@changesets/pre': 2.0.0 @@ -4987,7 +4933,7 @@ packages: /@changesets/git@3.0.0: resolution: {integrity: sha512-vvhnZDHe2eiBNRFHEgMiGd2CT+164dfYyrJDhwwxTVD/OW0FUD6G7+4DIx1dNwkwjHyzisxGAU96q0sVNBns0w==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/errors': 0.2.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 @@ -5012,7 +4958,7 @@ packages: /@changesets/pre@2.0.0: resolution: {integrity: sha512-HLTNYX/A4jZxc+Sq8D1AMBsv+1qD6rmmJtjsCJa/9MSRybdxh0mjbTvE6JYZQ/ZiQ0mMlDOlGPXTm9KLTU3jyw==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/errors': 0.2.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 @@ -5022,7 +4968,7 @@ packages: /@changesets/read@0.6.0: resolution: {integrity: sha512-ZypqX8+/im1Fm98K4YcZtmLKgjs1kDQ5zHpc2U1qdtNBmZZfo/IBiG162RoP0CUF05tvp2y4IspH11PLnPxuuw==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/git': 3.0.0 '@changesets/logger': 0.1.0 '@changesets/parse': 0.4.0 @@ -5035,7 +4981,7 @@ packages: /@changesets/should-skip-package@0.1.0: resolution: {integrity: sha512-FxG6Mhjw7yFStlSM7Z0Gmg3RiyQ98d/9VpQAZ3Fzr59dCOM9G6ZdYbjiSAt0XtFr9JR5U2tBaJWPjrkGGc618g==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 dev: true @@ -5051,7 +4997,7 @@ packages: /@changesets/write@0.3.1: resolution: {integrity: sha512-SyGtMXzH3qFqlHKcvFY2eX+6b0NGiFcNav8AFsYwy5l8hejOeoeTDemu5Yjmke2V5jpzY+pBvM0vCCQ3gdZpfw==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/types': 6.0.0 fs-extra: 7.0.1 human-id: 1.0.2 @@ -5065,13 +5011,13 @@ packages: protobufjs: 7.2.6 dev: false - /@coral-xyz/borsh@0.26.0(@solana/web3.js@1.95.3): + /@coral-xyz/borsh@0.26.0(@solana/web3.js@1.95.4): resolution: {integrity: sha512-uCZ0xus0CszQPHYfWAqKS5swS1UxvePu83oOF+TWpUkedsNlg6p2p4azxZNSSqwXb9uXMFgxhuMBX9r3Xoi0vQ==} engines: {node: '>=10'} peerDependencies: '@solana/web3.js': ^1.68.0 dependencies: - '@solana/web3.js': 1.95.3 + '@solana/web3.js': 1.95.4 bn.js: 5.2.1 buffer-layout: 1.2.2 dev: false @@ -5192,6 +5138,128 @@ packages: resolution: {integrity: sha512-nNcycZWUYLNJlrIXgpcgVRqdl6BXjF4YlXdxobQWpW9Tikk61bEGeAFhDYtC0PwHlokCNw0KxWiHGJL4nL7Q5A==} dev: false + /@emotion/babel-plugin@11.12.0: + resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==} + dependencies: + '@babel/helper-module-imports': 7.24.7 + '@babel/runtime': 7.26.0 + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/serialize': 1.3.2 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + dev: false + + /@emotion/cache@11.13.1: + resolution: {integrity: sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==} + dependencies: + '@emotion/memoize': 0.9.0 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.1 + '@emotion/weak-memoize': 0.4.0 + stylis: 4.2.0 + dev: false + + /@emotion/hash@0.9.2: + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + dev: false + + /@emotion/is-prop-valid@1.3.1: + resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==} + dependencies: + '@emotion/memoize': 0.9.0 + dev: false + + /@emotion/memoize@0.9.0: + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + dev: false + + /@emotion/react@11.13.3(@types/react@18.2.75)(react@18.2.0): + resolution: {integrity: sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/babel-plugin': 11.12.0 + '@emotion/cache': 11.13.1 + '@emotion/serialize': 1.3.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.2.0) + '@emotion/utils': 1.4.1 + '@emotion/weak-memoize': 0.4.0 + '@types/react': 18.2.75 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + transitivePeerDependencies: + - supports-color + dev: false + + /@emotion/serialize@1.3.2: + resolution: {integrity: sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==} + dependencies: + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.1 + csstype: 3.1.3 + dev: false + + /@emotion/sheet@1.4.0: + resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} + dev: false + + /@emotion/styled@11.13.0(@emotion/react@11.13.3)(@types/react@18.2.75)(react@18.2.0): + resolution: {integrity: sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/babel-plugin': 11.12.0 + '@emotion/is-prop-valid': 1.3.1 + '@emotion/react': 11.13.3(@types/react@18.2.75)(react@18.2.0) + '@emotion/serialize': 1.3.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.2.0) + '@emotion/utils': 1.4.1 + '@types/react': 18.2.75 + react: 18.2.0 + transitivePeerDependencies: + - supports-color + dev: false + + /@emotion/unitless@0.10.0: + resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + dev: false + + /@emotion/use-insertion-effect-with-fallbacks@1.1.0(react@18.2.0): + resolution: {integrity: sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + dev: false + + /@emotion/utils@1.4.1: + resolution: {integrity: sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==} + dev: false + + /@emotion/weak-memoize@0.4.0: + resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + dev: false + /@emurgo/cardano-serialization-lib-browser@11.5.0: resolution: {integrity: sha512-qchOJ9NYDUz10tzs5r5QhP9hK0p+ZOlRiBwPdTAxqAYLw/8emYBkQQLaS8T1DF6EkeudyrgS00ym5Trw1fo4iA==} dev: false @@ -5202,6 +5270,7 @@ packages: /@esbuild-kit/cjs-loader@2.4.2: resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==} + deprecated: 'Merged into tsx: https://tsx.is' dependencies: '@esbuild-kit/core-utils': 3.1.0 get-tsconfig: 4.8.1 @@ -5930,7 +5999,7 @@ packages: dependencies: '@inquirer/type': 1.5.5 '@types/mute-stream': 0.0.1 - '@types/node': 20.17.6 + '@types/node': 20.17.9 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -5950,7 +6019,7 @@ packages: dependencies: '@inquirer/type': 1.5.5 '@types/mute-stream': 0.0.4 - '@types/node': 20.17.6 + '@types/node': 20.17.9 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -6455,7 +6524,7 @@ packages: engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 dev: false /@jridgewell/gen-mapping@0.3.5: @@ -6463,7 +6532,7 @@ packages: engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 /@jridgewell/resolve-uri@3.1.1: @@ -6481,14 +6550,14 @@ packages: '@jridgewell/trace-mapping': 0.3.25 dev: false - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + /@jridgewell/sourcemap-codec@1.5.0: + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} /@jridgewell/trace-mapping@0.3.25: resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} dependencies: '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 /@keystonehq/alias-sampling@0.1.2: resolution: {integrity: sha512-5ukLB3bcgltgaFfQfYKYwHDUbwHicekYo53fSEa7xhVkAEqsA74kxdIwoBIURmGUtXe3EVIRm4SYlgcrt2Ri0w==} @@ -6529,7 +6598,7 @@ packages: '@keystonehq/bc-ur-registry': 0.5.5 '@keystonehq/bc-ur-registry-sol': 0.3.1 '@keystonehq/sdk': 0.13.1 - '@solana/web3.js': 1.95.3 + '@solana/web3.js': 1.95.4 bs58: 5.0.0 uuid: 8.3.2 transitivePeerDependencies: @@ -6575,7 +6644,7 @@ packages: /@manypkg/find-root@1.1.0: resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/node': 12.20.55 find-up: 4.1.0 fs-extra: 8.1.0 @@ -6584,7 +6653,7 @@ packages: /@manypkg/get-packages@1.1.3: resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@changesets/types': 4.1.0 '@manypkg/find-root': 1.1.0 fs-extra: 8.1.0 @@ -6630,6 +6699,168 @@ packages: engines: {node: '>= 10.*'} dev: false + /@mui/core-downloads-tracker@6.1.5: + resolution: {integrity: sha512-3J96098GrC95XsLw/TpGNMxhUOnoG9NZ/17Pfk1CrJj+4rcuolsF2RdF3XAFTu/3a/A+5ouxlSIykzYz6Ee87g==} + dev: false + + /@mui/icons-material@6.1.5(@mui/material@6.1.5)(@types/react@18.2.75)(react@18.2.0): + resolution: {integrity: sha512-SbxFtO5I4cXfvhjAMgGib/t2lQUzcEzcDFYiRHRufZUeMMeXuoKaGsptfwAHTepYkv0VqcCwvxtvtWbpZLAbjQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@mui/material': ^6.1.5 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@mui/material': 6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.75)(react-dom@18.3.1)(react@18.2.0) + '@types/react': 18.2.75 + react: 18.2.0 + dev: false + + /@mui/material@6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.75)(react-dom@18.3.1)(react@18.2.0): + resolution: {integrity: sha512-rhaxC7LnlOG8zIVYv7BycNbWkC5dlm9A/tcDUp0CuwA7Zf9B9JP6M3rr50cNKxI7Z0GIUesAT86ceVm44quwnQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@mui/material-pigment-css': ^6.1.5 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@mui/material-pigment-css': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/react': 11.13.3(@types/react@18.2.75)(react@18.2.0) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.75)(react@18.2.0) + '@mui/core-downloads-tracker': 6.1.5 + '@mui/system': 6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.75)(react@18.2.0) + '@mui/types': 7.2.18(@types/react@18.2.75) + '@mui/utils': 6.1.5(@types/react@18.2.75)(react@18.2.0) + '@popperjs/core': 2.11.8 + '@types/react': 18.2.75 + '@types/react-transition-group': 4.4.11 + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.3.1(react@18.2.0) + react-is: 18.3.1 + react-transition-group: 4.4.5(react-dom@18.3.1)(react@18.2.0) + dev: false + + /@mui/private-theming@6.1.5(@types/react@18.2.75)(react@18.2.0): + resolution: {integrity: sha512-FJqweqEXk0KdtTho9C2h6JEKXsOT7MAVH2Uj3N5oIqs6YKxnwBn2/zL2QuYYEtj5OJ87rEUnCfFic6ldClvzJw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@mui/utils': 6.1.5(@types/react@18.2.75)(react@18.2.0) + '@types/react': 18.2.75 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/styled-engine@6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.2.0): + resolution: {integrity: sha512-tiyWzMkHeWlOoE6AqomWvYvdml8Nv5k5T+LDwOiwHEawx8P9Lyja6ZwWPU6xljwPXYYPT2KBp1XvMly7dsK46A==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/cache': 11.13.1 + '@emotion/react': 11.13.3(@types/react@18.2.75)(react@18.2.0) + '@emotion/serialize': 1.3.2 + '@emotion/sheet': 1.4.0 + '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.75)(react@18.2.0) + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/system@6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.75)(react@18.2.0): + resolution: {integrity: sha512-vPM9ocQ8qquRDByTG3XF/wfYTL7IWL/20EiiKqByLDps8wOmbrDG9rVznSE3ZbcjFCFfMRMhtxvN92bwe/63SA==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/react': 11.13.3(@types/react@18.2.75)(react@18.2.0) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.75)(react@18.2.0) + '@mui/private-theming': 6.1.5(@types/react@18.2.75)(react@18.2.0) + '@mui/styled-engine': 6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.2.0) + '@mui/types': 7.2.18(@types/react@18.2.75) + '@mui/utils': 6.1.5(@types/react@18.2.75)(react@18.2.0) + '@types/react': 18.2.75 + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/types@7.2.18(@types/react@18.2.75): + resolution: {integrity: sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.75 + dev: false + + /@mui/utils@6.1.5(@types/react@18.2.75)(react@18.2.0): + resolution: {integrity: sha512-vp2WfNDY+IbKUIGg+eqX1Ry4t/BilMjzp6p9xO1rfqpYjH1mj8coQxxDfKxcQLzBQkmBJjymjoGOak5VUYwXug==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@mui/types': 7.2.18(@types/react@18.2.75) + '@types/prop-types': 15.7.13 + '@types/react': 18.2.75 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.2.0 + react-is: 18.3.1 + dev: false + /@mysten/bcs@0.7.3: resolution: {integrity: sha512-fbusBfsyc2MpTACi72H5edWJ670T84va+qn9jSPpb5BzZ+pzUM1Q0ApPrF5OT+mB1o5Ng+mxPQpBCZQkfiV2TA==} dependencies: @@ -7746,12 +7977,16 @@ packages: picocolors: 1.0.0 tslib: 2.8.1 + /@popperjs/core@2.11.8: + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + dev: false + /@project-serum/anchor@0.26.0: resolution: {integrity: sha512-Nq+COIjE1135T7qfnOHEn7E0q39bQTgXLFk837/rgFe6Hkew9WML7eHsS+lSYD2p3OJaTiUOHTAq1lHy36oIqQ==} engines: {node: '>=11'} dependencies: - '@coral-xyz/borsh': 0.26.0(@solana/web3.js@1.95.3) - '@solana/web3.js': 1.95.3 + '@coral-xyz/borsh': 0.26.0(@solana/web3.js@1.95.4) + '@solana/web3.js': 1.95.4 base64-js: 1.5.1 bn.js: 5.2.1 bs58: 4.0.1 @@ -7818,13 +8053,13 @@ packages: /@radix-ui/number@1.0.1: resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 dev: false /@radix-ui/primitive@1.0.1: resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 dev: false /@radix-ui/primitive@1.1.0: @@ -7844,7 +8079,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) '@types/react': 18.2.14 '@types/react-dom': 18.2.6 @@ -7865,7 +8100,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) '@types/react': 18.3.3 '@types/react-dom': 18.3.0 @@ -7886,7 +8121,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.14)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) @@ -7910,7 +8145,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1) '@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) @@ -7953,7 +8188,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.2.14 react: 18.2.0 dev: false @@ -7967,7 +8202,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.3.3 react: 18.3.1 dev: false @@ -7994,7 +8229,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.2.14 react: 18.2.0 dev: false @@ -8008,7 +8243,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.3.3 react: 18.3.1 dev: false @@ -8073,7 +8308,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.24.8 + '@babel/runtime': 7.24.1 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1) '@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1) @@ -8103,7 +8338,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.2.14 react: 18.2.0 dev: false @@ -8117,7 +8352,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.3.3 react: 18.3.1 dev: false @@ -8148,7 +8383,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) @@ -8173,7 +8408,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) @@ -8221,7 +8456,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.2.14 react: 18.2.0 dev: false @@ -8235,7 +8470,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.3.3 react: 18.3.1 dev: false @@ -8253,7 +8488,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.14)(react@18.2.0) @@ -8276,7 +8511,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.3.1) @@ -8303,7 +8538,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.14)(react@18.2.0) '@types/react': 18.2.14 react: 18.2.0 @@ -8318,7 +8553,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.3)(react@18.3.1) '@types/react': 18.3.3 react: 18.3.1 @@ -8393,7 +8628,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0) @@ -8431,7 +8666,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0) @@ -8461,7 +8696,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@floating-ui/react-dom': 2.0.8(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1) @@ -8491,7 +8726,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) '@types/react': 18.2.14 '@types/react-dom': 18.2.6 @@ -8512,7 +8747,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) '@types/react': 18.3.3 '@types/react-dom': 18.3.0 @@ -8533,7 +8768,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0) '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.14)(react@18.2.0) '@types/react': 18.2.14 @@ -8555,7 +8790,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.3.3)(react@18.3.1) '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.3)(react@18.3.1) '@types/react': 18.3.3 @@ -8598,7 +8833,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-slot': 1.0.2(@types/react@18.2.14)(react@18.2.0) '@types/react': 18.2.14 '@types/react-dom': 18.2.6 @@ -8619,7 +8854,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-slot': 1.0.2(@types/react@18.3.3)(react@18.3.1) '@types/react': 18.3.3 '@types/react-dom': 18.3.0 @@ -8689,7 +8924,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.14)(react@18.2.0) @@ -8995,7 +9230,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.2.14 react: 18.2.0 dev: false @@ -9009,7 +9244,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.3.3 react: 18.3.1 dev: false @@ -9036,7 +9271,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.14)(react@18.2.0) '@types/react': 18.2.14 react: 18.2.0 @@ -9051,7 +9286,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.3.1) '@types/react': 18.3.3 react: 18.3.1 @@ -9080,7 +9315,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.14)(react@18.2.0) '@types/react': 18.2.14 react: 18.2.0 @@ -9095,7 +9330,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.3.3)(react@18.3.1) '@types/react': 18.3.3 react: 18.3.1 @@ -9110,7 +9345,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.2.14 react: 18.2.0 dev: false @@ -9124,7 +9359,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.3.3 react: 18.3.1 dev: false @@ -9151,7 +9386,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.2.14 react: 18.2.0 dev: false @@ -9165,7 +9400,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@types/react': 18.3.3 react: 18.3.1 dev: false @@ -9192,7 +9427,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/rect': 1.0.1 '@types/react': 18.2.14 react: 18.2.0 @@ -9207,7 +9442,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/rect': 1.0.1 '@types/react': 18.3.3 react: 18.3.1 @@ -9222,7 +9457,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.14)(react@18.2.0) '@types/react': 18.2.14 react: 18.2.0 @@ -9237,7 +9472,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.3.3)(react@18.3.1) '@types/react': 18.3.3 react: 18.3.1 @@ -9270,7 +9505,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) '@types/react': 18.2.14 '@types/react-dom': 18.2.6 @@ -9291,7 +9526,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) '@types/react': 18.3.3 '@types/react-dom': 18.3.0 @@ -9302,7 +9537,7 @@ packages: /@radix-ui/rect@1.0.1: resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 dev: false /@react-native-async-storage/async-storage@1.24.0(react-native@0.74.0): @@ -9687,6 +9922,16 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@react-oauth/google@0.12.1(react-dom@18.3.1)(react@18.2.0): + resolution: {integrity: sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + react: 18.2.0 + react-dom: 18.3.1(react@18.2.0) + dev: false + /@rhinestone/module-sdk@0.1.28(viem@2.21.29): resolution: {integrity: sha512-Ob9qMs/scBrk6/X0dvb02kWqiCdP3Vau6PRNr3PP7PGxIIBFunJoVHuLWg4Tb/DHS3dCUXhuTk+XhObH1HjH+w==} peerDependencies: @@ -10111,7 +10356,7 @@ packages: resolution: {integrity: sha512-53fV1noQJDUN9JNydDohyzsFl4+QYoWNkkkAfRzmIgtv+6DR+Dksb0fKmme2WdtA8MPEw/HsRwN3Lr6YC3iF7A==} dependencies: '@noble/curves': 1.6.0 - viem: 2.21.35(typescript@5.1.3) + viem: 2.21.48(typescript@5.1.3) transitivePeerDependencies: - bufferutil - typescript @@ -10123,7 +10368,7 @@ packages: resolution: {integrity: sha512-53fV1noQJDUN9JNydDohyzsFl4+QYoWNkkkAfRzmIgtv+6DR+Dksb0fKmme2WdtA8MPEw/HsRwN3Lr6YC3iF7A==} dependencies: '@noble/curves': 1.6.0 - viem: 2.21.35(typescript@5.1.5) + viem: 2.21.48(typescript@5.1.5) transitivePeerDependencies: - bufferutil - typescript @@ -10229,7 +10474,7 @@ packages: engines: {node: '>= 10'} dependencies: '@solana/buffer-layout': 4.0.1 - '@solana/web3.js': 1.95.3 + '@solana/web3.js': 1.95.4 bigint-buffer: 1.1.5 bignumber.js: 9.1.2 transitivePeerDependencies: @@ -10827,7 +11072,7 @@ packages: '@solana/web3.js': 1.88.1 dev: false - /@solana/wallet-adapter-torus@0.11.28(@babel/runtime@7.25.6)(@solana/web3.js@1.88.1): + /@solana/wallet-adapter-torus@0.11.28(@babel/runtime@7.26.0)(@solana/web3.js@1.88.1): resolution: {integrity: sha512-bu1oJQ+AoIZICxz8J1lVcdL+iBBrdbynnEs5N6dxwoM/cMGLbX7PGYqaH0J1dEXisA+1H5AzGAnW4UU05VBmLA==} engines: {node: '>=16'} peerDependencies: @@ -10835,7 +11080,7 @@ packages: dependencies: '@solana/wallet-adapter-base': 0.9.23(@solana/web3.js@1.88.1) '@solana/web3.js': 1.88.1 - '@toruslabs/solana-embed': 0.3.4(@babel/runtime@7.25.6) + '@toruslabs/solana-embed': 0.3.4(@babel/runtime@7.26.0) assert: 2.1.0 crypto-browserify: 3.12.0 process: 0.11.10 @@ -10923,7 +11168,7 @@ packages: - utf-8-validate dev: false - /@solana/wallet-adapter-wallets@0.19.32(@babel/core@7.24.5)(@babel/runtime@7.25.6)(@solana/web3.js@1.88.1)(bs58@5.0.0)(react-dom@18.3.1)(react-native@0.74.0)(react@18.3.1)(tslib@2.8.1): + /@solana/wallet-adapter-wallets@0.19.32(@babel/core@7.24.5)(@babel/runtime@7.26.0)(@solana/web3.js@1.88.1)(bs58@5.0.0)(react-dom@18.3.1)(react-native@0.74.0)(react@18.3.1)(tslib@2.8.1): resolution: {integrity: sha512-voZYQiIy1yXuKvm7x7YpnQ53eiJC7NpIYSQjzApOUiswiBRVeYcnPO4O/MMPUwsGkS7iZKqKZjo5CnOaN44n+g==} engines: {node: '>=16'} peerDependencies: @@ -10959,7 +11204,7 @@ packages: '@solana/wallet-adapter-spot': 0.1.15(@solana/web3.js@1.88.1) '@solana/wallet-adapter-tokenary': 0.1.12(@solana/web3.js@1.88.1) '@solana/wallet-adapter-tokenpocket': 0.4.19(@solana/web3.js@1.88.1) - '@solana/wallet-adapter-torus': 0.11.28(@babel/runtime@7.25.6)(@solana/web3.js@1.88.1) + '@solana/wallet-adapter-torus': 0.11.28(@babel/runtime@7.26.0)(@solana/web3.js@1.88.1) '@solana/wallet-adapter-trezor': 0.1.2(@babel/core@7.24.5)(@solana/web3.js@1.88.1)(react-native@0.74.0)(tslib@2.8.1) '@solana/wallet-adapter-trust': 0.1.13(@solana/web3.js@1.88.1) '@solana/wallet-adapter-unsafe-burner': 0.1.7(@solana/web3.js@1.88.1) @@ -11149,10 +11394,10 @@ packages: - encoding - utf-8-validate - /@solana/web3.js@1.95.3: - resolution: {integrity: sha512-O6rPUN0w2fkNqx/Z3QJMB9L225Ex10PRDH8bTaIUPZXMPV0QP8ZpPvjQnXK+upUczlRgzHzd6SjKIha1p+I6og==} + /@solana/web3.js@1.95.4: + resolution: {integrity: sha512-sdewnNEA42ZSMxqkzdwEWi6fDgzwtJHaQa5ndUGEJYtoOnM6X5cvPmjoTUp7/k7bRrVAxfBgDnvQQHD6yhlLYw==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@noble/curves': 1.6.0 '@noble/hashes': 1.4.0 '@solana/buffer-layout': 4.0.1 @@ -11399,7 +11644,7 @@ packages: '@ton/core': 0.59.0(@ton/crypto@3.3.0) '@ton/crypto': 3.3.0 axios: 1.7.4 - dataloader: 2.2.2 + dataloader: 2.2.3 symbol.inspect: 1.0.1 teslabot: 1.5.0 zod: 3.23.8 @@ -11412,16 +11657,16 @@ packages: engines: {node: '>= 10'} dev: true - /@toruslabs/base-controllers@2.9.0(@babel/runtime@7.25.6): + /@toruslabs/base-controllers@2.9.0(@babel/runtime@7.26.0): resolution: {integrity: sha512-rKc+bR4QB/wdbH0CxLZC5e2PUZcIgkr9yY7TMd3oIffDklaYBnsuC5ES2/rgK1aRUDRWz+qWbTwLqsY6PlT37Q==} peerDependencies: '@babel/runtime': 7.x dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@ethereumjs/util': 8.1.0 '@toruslabs/broadcast-channel': 6.3.1 - '@toruslabs/http-helpers': 3.4.0(@babel/runtime@7.25.6) - '@toruslabs/openlogin-jrpc': 4.7.2(@babel/runtime@7.25.6) + '@toruslabs/http-helpers': 3.4.0(@babel/runtime@7.26.0) + '@toruslabs/openlogin-jrpc': 4.7.2(@babel/runtime@7.26.0) async-mutex: 0.4.1 bignumber.js: 9.1.2 bowser: 2.11.0 @@ -11439,9 +11684,9 @@ packages: /@toruslabs/broadcast-channel@6.3.1: resolution: {integrity: sha512-BEtJQ+9bMfFoGuCsp5NmxyY+C980Ho+3BZIKSiYwRtl5qymJ+jMX5lsoCppoQblcb34dP6FwEjeFw80Y9QC/rw==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@toruslabs/eccrypto': 2.2.1 - '@toruslabs/metadata-helpers': 3.2.0(@babel/runtime@7.25.6) + '@toruslabs/metadata-helpers': 3.2.0(@babel/runtime@7.26.0) bowser: 2.11.0 loglevel: 1.9.1 oblivious-set: 1.1.1 @@ -11460,7 +11705,7 @@ packages: elliptic: 6.5.7 dev: false - /@toruslabs/http-helpers@3.4.0(@babel/runtime@7.25.6): + /@toruslabs/http-helpers@3.4.0(@babel/runtime@7.26.0): resolution: {integrity: sha512-CoeJSL32mpp0gmYjxv48odu6pfjHk/rbJHDwCtYPcMHAl+qUQ/DTpVOOn9U0fGkD+fYZrQmZbRkXFgLhiT0ajQ==} engines: {node: '>=14.17.0', npm: '>=6.x'} peerDependencies: @@ -11470,20 +11715,20 @@ packages: '@sentry/types': optional: true dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 lodash.merge: 4.6.2 loglevel: 1.9.1 dev: false - /@toruslabs/metadata-helpers@3.2.0(@babel/runtime@7.25.6): + /@toruslabs/metadata-helpers@3.2.0(@babel/runtime@7.26.0): resolution: {integrity: sha512-2bCc6PNKd9y+aWfZQ1FXd47QmfyT4NmmqPGfsqk+sQS2o+MlxIyLuh9uh7deMgXo4b4qBDX+RQGbIKM1zVk56w==} engines: {node: '>=14.17.0', npm: '>=6.x'} peerDependencies: '@babel/runtime': 7.x dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@toruslabs/eccrypto': 2.2.1 - '@toruslabs/http-helpers': 3.4.0(@babel/runtime@7.25.6) + '@toruslabs/http-helpers': 3.4.0(@babel/runtime@7.26.0) elliptic: 6.5.7 ethereum-cryptography: 2.1.3 json-stable-stringify: 1.1.1 @@ -11491,14 +11736,14 @@ packages: - '@sentry/types' dev: false - /@toruslabs/openlogin-jrpc@3.2.0(@babel/runtime@7.25.6): + /@toruslabs/openlogin-jrpc@3.2.0(@babel/runtime@7.26.0): resolution: {integrity: sha512-G+K0EHyVUaAEyeD4xGsnAZRpn/ner8lQ2HC2+pGKg6oGmzKI2wGMDcw2KMH6+HKlfBGVJ5/VR9AQfC/tZlLDmQ==} deprecated: Not supported. Pls upgrade peerDependencies: '@babel/runtime': 7.x dependencies: - '@babel/runtime': 7.25.6 - '@toruslabs/openlogin-utils': 3.0.0(@babel/runtime@7.25.6) + '@babel/runtime': 7.26.0 + '@toruslabs/openlogin-utils': 3.0.0(@babel/runtime@7.26.0) end-of-stream: 1.4.4 eth-rpc-errors: 4.0.3 events: 3.3.0 @@ -11508,15 +11753,15 @@ packages: readable-stream: 3.6.2 dev: false - /@toruslabs/openlogin-jrpc@4.7.2(@babel/runtime@7.25.6): + /@toruslabs/openlogin-jrpc@4.7.2(@babel/runtime@7.26.0): resolution: {integrity: sha512-9Eb0cPc0lPuS6v2YkQlgzfbRnZ6fLez9Ike5wznoHSFA2/JVu1onwuI56EV1HwswdDrOWPPQEyzI1j9NriZ0ew==} engines: {node: '>=16.18.1', npm: '>=8.x'} peerDependencies: '@babel/runtime': 7.x dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 '@metamask/rpc-errors': 5.1.1 - '@toruslabs/openlogin-utils': 4.7.0(@babel/runtime@7.25.6) + '@toruslabs/openlogin-utils': 4.7.0(@babel/runtime@7.26.0) end-of-stream: 1.4.4 events: 3.3.0 fast-safe-stringify: 2.1.1 @@ -11527,39 +11772,39 @@ packages: - supports-color dev: false - /@toruslabs/openlogin-utils@3.0.0(@babel/runtime@7.25.6): + /@toruslabs/openlogin-utils@3.0.0(@babel/runtime@7.26.0): resolution: {integrity: sha512-T5t29/AIFqXc84x4OoAkZWjd0uoP2Lk6iaFndnIIMzCPu+BwwV0spX/jd/3YYNjZ8Po8D+faEnwAhiqemYeK2w==} deprecated: Not supported. Pls upgrade peerDependencies: '@babel/runtime': 7.x dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 base64url: 3.0.1 keccak: 3.0.3 randombytes: 2.1.0 dev: false - /@toruslabs/openlogin-utils@4.7.0(@babel/runtime@7.25.6): + /@toruslabs/openlogin-utils@4.7.0(@babel/runtime@7.26.0): resolution: {integrity: sha512-w6XkHs4WKuufsf/zzteBzs4EJuOknrUmJ+iv5FZ8HzIpMQeL/984CP8HYaFSEYkbGCP4ydAnhY4Uh0QAhpDbPg==} engines: {node: '>=16.18.1', npm: '>=8.x'} peerDependencies: '@babel/runtime': 7.x dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 base64url: 3.0.1 dev: false - /@toruslabs/solana-embed@0.3.4(@babel/runtime@7.25.6): + /@toruslabs/solana-embed@0.3.4(@babel/runtime@7.26.0): resolution: {integrity: sha512-yj+aBJoBAneap7Jlu9/OOp7irWNuC5CqAhyhVcmb0IjWrCUFnioLdL0U7UfGaqVm/5O0leJh7/Z5Ll+3toWJBg==} engines: {node: '>=14.17.0', npm: '>=6.x'} peerDependencies: '@babel/runtime': 7.x dependencies: - '@babel/runtime': 7.25.6 - '@solana/web3.js': 1.95.3 - '@toruslabs/base-controllers': 2.9.0(@babel/runtime@7.25.6) - '@toruslabs/http-helpers': 3.4.0(@babel/runtime@7.25.6) - '@toruslabs/openlogin-jrpc': 3.2.0(@babel/runtime@7.25.6) + '@babel/runtime': 7.26.0 + '@solana/web3.js': 1.95.4 + '@toruslabs/base-controllers': 2.9.0(@babel/runtime@7.26.0) + '@toruslabs/http-helpers': 3.4.0(@babel/runtime@7.26.0) + '@toruslabs/openlogin-jrpc': 3.2.0(@babel/runtime@7.26.0) eth-rpc-errors: 4.0.3 fast-deep-equal: 3.1.3 is-stream: 2.0.1 @@ -11593,7 +11838,7 @@ packages: peerDependencies: tslib: ^2.6.2 dependencies: - '@solana/web3.js': 1.95.3 + '@solana/web3.js': 1.95.4 '@trezor/type-utils': 1.1.0 '@trezor/utxo-lib': 2.1.0(tslib@2.8.1) socks-proxy-agent: 6.1.1 @@ -11611,7 +11856,7 @@ packages: tslib: ^2.6.2 dependencies: '@mobily/ts-belt': 3.13.1 - '@solana/web3.js': 1.95.3 + '@solana/web3.js': 1.95.4 '@trezor/env-utils': 1.1.0(react-native@0.74.0)(tslib@2.8.1) '@trezor/utils': 9.1.0(tslib@2.8.1) tslib: 2.8.1 @@ -11630,7 +11875,7 @@ packages: tslib: ^2.6.2 dependencies: '@solana/buffer-layout': 4.0.1 - '@solana/web3.js': 1.95.3 + '@solana/web3.js': 1.95.4 '@trezor/blockchain-link-types': 1.1.0(tslib@2.8.1) '@trezor/blockchain-link-utils': 1.1.0(react-native@0.74.0)(tslib@2.8.1) '@trezor/utils': 9.1.0(tslib@2.8.1) @@ -11888,8 +12133,8 @@ packages: /@types/babel__core@7.20.0: resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==} dependencies: - '@babel/parser': 7.24.5 - '@babel/types': 7.24.5 + '@babel/parser': 7.24.8 + '@babel/types': 7.24.9 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.18.3 @@ -11897,18 +12142,18 @@ packages: /@types/babel__generator@7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: - '@babel/parser': 7.24.5 - '@babel/types': 7.24.5 + '@babel/parser': 7.24.8 + '@babel/types': 7.24.9 /@types/babel__traverse@7.18.3: resolution: {integrity: sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==} dependencies: - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 /@types/bn.js@4.11.6: resolution: {integrity: sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==} @@ -12019,7 +12264,7 @@ packages: dependencies: '@types/node': 20.3.1 '@types/tough-cookie': 4.0.5 - parse5: 7.2.0 + parse5: 7.2.1 dev: true /@types/json-schema@7.0.15: @@ -12084,8 +12329,8 @@ packages: /@types/node@18.18.2: resolution: {integrity: sha512-u1cis+7wLZMPI62EozwsqvgMZyauczyiqRRu/vcqZKI5N5yidrJHqOFxEg5seT8adc96Q6Yczg1c0DlqGtMJMw==} - /@types/node@20.17.6: - resolution: {integrity: sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==} + /@types/node@20.17.9: + resolution: {integrity: sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==} dependencies: undici-types: 6.19.8 dev: false @@ -12097,6 +12342,10 @@ packages: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: true + /@types/parse-json@4.0.2: + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + dev: false + /@types/pbkdf2@3.1.0: resolution: {integrity: sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==} dependencies: @@ -12113,6 +12362,9 @@ packages: kleur: 3.0.3 dev: true + /@types/prop-types@15.7.13: + resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} + /@types/prop-types@15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} @@ -12134,12 +12386,18 @@ packages: dependencies: '@types/react': 18.3.3 + /@types/react-transition-group@4.4.11: + resolution: {integrity: sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==} + dependencies: + '@types/react': 18.3.3 + dev: false + /@types/react@18.2.14: resolution: {integrity: sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g==} dependencies: - '@types/prop-types': 15.7.5 + '@types/prop-types': 15.7.13 '@types/scheduler': 0.16.3 - csstype: 3.1.2 + csstype: 3.1.3 /@types/react@18.2.75: resolution: {integrity: sha512-+DNnF7yc5y0bHkBTiLKqXFe+L4B3nvOphiMY3tuA5X10esmjqk7smyBZzbGTy2vsiy/Bnzj8yFIBL8xhRacoOg==} @@ -13304,7 +13562,6 @@ packages: /aptos@1.21.0: resolution: {integrity: sha512-PRKjoFgL8tVEc9+oS7eJUv8GNxx8n3+0byH2+m7CP3raYOD6yFKOecuwjVMIJmgfpjp6xH0P0HDMGZAXmSyU0Q==} engines: {node: '>=11.0.0'} - deprecated: Package aptos is no longer supported, please migrate to https://www.npmjs.com/package/@aptos-labs/ts-sdk dependencies: '@aptos-labs/aptos-client': 0.1.1 '@noble/hashes': 1.3.3 @@ -13637,8 +13894,8 @@ packages: resolution: {integrity: sha512-mB6q2q3oahKphy5V7CpnNqZOCkxxZ9aokf1eh82Dy3jQmg4xvM1tGrh5y6BQUJh4a3Pj9+eLfwvAZ7VNKg7H8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@babel/template': 7.24.0 - '@babel/types': 7.24.5 + '@babel/template': 7.24.7 + '@babel/types': 7.24.9 '@types/babel__core': 7.20.0 '@types/babel__traverse': 7.18.3 dev: false @@ -13653,6 +13910,15 @@ packages: '@types/babel__traverse': 7.18.3 dev: true + /babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + dependencies: + '@babel/runtime': 7.26.0 + cosmiconfig: 7.1.0 + resolve: 1.22.8 + dev: false + /babel-plugin-polyfill-corejs2@0.3.3(@babel/core@7.20.12): resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==} peerDependencies: @@ -14398,6 +14664,7 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 + dev: false /chrome-launcher@0.15.2: resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} @@ -14553,6 +14820,11 @@ packages: engines: {node: '>=6'} dev: false + /clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + dev: false + /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -14751,6 +15023,17 @@ packages: parse-json: 4.0.0 dev: false + /cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + dev: false + /cosmjs-types@0.8.0: resolution: {integrity: sha512-Q2Mj95Fl0PYMWEhA2LuGEIhipF7mQwd9gTQ85DdP9jjjopeoGaDxvmPa5nakNzsq7FnO1DMTatXTAx6bxMH7Lg==} dependencies: @@ -14979,9 +15262,27 @@ packages: css-tree: 1.1.3 dev: true + /cssom@0.3.8: + resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} + dev: true + + /cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + dev: true + + /cssstyle@2.3.0: + resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} + engines: {node: '>=8'} + dependencies: + cssom: 0.3.8 + dev: true + /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + /csv-generate@3.4.3: resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} dev: true @@ -15040,8 +15341,8 @@ packages: es-errors: 1.3.0 is-data-view: 1.0.1 - /dataloader@2.2.2: - resolution: {integrity: sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==} + /dataloader@2.2.3: + resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==} dev: false /dateformat@4.6.3: @@ -15300,12 +15601,13 @@ packages: dependencies: esutils: 2.0.3 - /domexception@4.0.0: - resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} - engines: {node: '>=12'} - deprecated: Use your platform's native DOMException instead + /dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: - webidl-conversions: 7.0.0 + '@babel/runtime': 7.26.0 + csstype: 3.1.3 + dev: false + /dom-serializer@1.4.1: resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} dependencies: @@ -15318,6 +15620,14 @@ packages: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} dev: true + /domexception@4.0.0: + resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} + engines: {node: '>=12'} + deprecated: Use your platform's native DOMException instead + dependencies: + webidl-conversions: 7.0.0 + dev: true + /domhandler@4.3.1: resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} engines: {node: '>= 4'} @@ -15405,6 +15715,7 @@ packages: inherits: 2.0.4 minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + dev: false /elliptic@6.5.7: resolution: {integrity: sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==} @@ -15493,14 +15804,13 @@ packages: ansi-colors: 4.1.3 strip-ansi: 6.0.1 -<<<<<<< HEAD + /entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + dev: true + /entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} -======= - /entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} ->>>>>>> ddafbe76 (init commit - very messy) dev: true /env-paths@2.2.1: @@ -15973,7 +16283,7 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 aria-query: 5.3.0 array-includes: 3.1.8 array.prototype.flatmap: 1.3.2 @@ -16039,6 +16349,7 @@ packages: /eslint@8.43.0: resolution: {integrity: sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.43.0) @@ -16188,7 +16499,7 @@ packages: '@types/bn.js': 4.11.6 bn.js: 4.12.0 create-hash: 1.2.0 - elliptic: 6.5.5 + elliptic: 6.5.7 ethereum-cryptography: 0.1.3 ethjs-util: 0.1.6 rlp: 2.2.7 @@ -16506,6 +16817,10 @@ packages: array-back: 3.1.0 dev: true + /find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + dev: false + /find-up@2.1.0: resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} engines: {node: '>=4'} @@ -16916,7 +17231,7 @@ packages: peerDependencies: hardhat: ^2.0.0 dependencies: - chokidar: 3.6.0 + chokidar: 3.5.3 hardhat: 2.12.7(typescript@5.1.5) dev: false @@ -16925,7 +17240,7 @@ packages: peerDependencies: hardhat: ^2.0.0 dependencies: - chokidar: 3.6.0 + chokidar: 3.5.3 hardhat: 2.22.2(typescript@5.1.5) dev: false @@ -17234,6 +17549,12 @@ packages: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + /hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + dependencies: + react-is: 16.13.1 + dev: false + /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true @@ -17838,7 +18159,7 @@ packages: engines: {node: '>=8'} dependencies: '@babel/core': 7.24.5 - '@babel/parser': 7.24.5 + '@babel/parser': 7.24.8 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 7.5.4 @@ -18367,7 +18688,7 @@ packages: '@babel/generator': 7.24.5 '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.24.5) '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.24.5) - '@babel/types': 7.24.5 + '@babel/types': 7.24.9 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 @@ -18593,8 +18914,8 @@ packages: http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.13 - parse5: 7.2.0 + nwsapi: 2.2.16 + parse5: 7.2.1 saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 4.1.4 @@ -18631,7 +18952,6 @@ packages: /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - dev: true /json-rpc-random-id@1.0.1: resolution: {integrity: sha512-RJ9YYNCkhVDBuP4zN5BBtYAzEl03yq/jIIsyif0JY9qyJuQQZNeDK7anAPKKlyEtLSj2s8h6hNh2F8zO5q7ScA==} @@ -19195,7 +19515,7 @@ packages: resolution: {integrity: sha512-L0syTWJUdWzfUmKgkScr6fSBVTh6QDr8eKEkRtn40OBd8LPagrJGySBboWSgbyn9eIb4ayW3Y347HxgXBSAjmg==} engines: {node: '>=18'} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 dev: false /metro-source-map@0.80.5: @@ -19860,8 +20180,8 @@ packages: resolution: {integrity: sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg==} dev: true - /nwsapi@2.2.13: - resolution: {integrity: sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==} + /nwsapi@2.2.16: + resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} dev: true /ob1@0.80.5: @@ -20064,6 +20384,46 @@ packages: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} dev: true + /ox@0.1.2(typescript@5.1.3): + resolution: {integrity: sha512-ak/8K0Rtphg9vnRJlbOdaX9R7cmxD2MiSthjWGaQdMk3D7hrAlDoM+6Lxn7hN52Za3vrXfZ7enfke/5WjolDww==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@adraffy/ens-normalize': 1.11.0 + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + '@scure/bip32': 1.5.0 + '@scure/bip39': 1.4.0 + abitype: 1.0.6(typescript@5.1.3) + eventemitter3: 5.0.1 + typescript: 5.1.3 + transitivePeerDependencies: + - zod + dev: false + + /ox@0.1.2(typescript@5.1.5): + resolution: {integrity: sha512-ak/8K0Rtphg9vnRJlbOdaX9R7cmxD2MiSthjWGaQdMk3D7hrAlDoM+6Lxn7hN52Za3vrXfZ7enfke/5WjolDww==} + peerDependencies: + typescript: '>=5.4.0' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@adraffy/ens-normalize': 1.11.0 + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + '@scure/bip32': 1.5.0 + '@scure/bip39': 1.4.0 + abitype: 1.0.6(typescript@5.1.5) + eventemitter3: 5.0.1 + typescript: 5.1.5 + transitivePeerDependencies: + - zod + dev: false + /p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} @@ -20196,10 +20556,9 @@ packages: error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - dev: true - /parse5@7.2.0: - resolution: {integrity: sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA==} + /parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} dependencies: entities: 4.5.0 dev: true @@ -20866,7 +21225,7 @@ packages: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 - react-is: 18.2.0 + react-is: 18.3.1 dev: true /pretty-format@29.7.0: @@ -20875,7 +21234,7 @@ packages: dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 - react-is: 18.2.0 + react-is: 18.3.1 /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -20956,8 +21315,8 @@ packages: end-of-stream: 1.4.4 once: 1.4.0 - /punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + /punycode@2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} /pure-rand@6.1.0: @@ -21131,6 +21490,16 @@ packages: scheduler: 0.23.2 dev: false + /react-dom@18.3.1(react@18.2.0): + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.2 + dev: false + /react-dom@18.3.1(react@18.3.1): resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: @@ -21165,8 +21534,8 @@ packages: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} dev: false - /react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + /react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} /react-lifecycles-compat@3.0.4: resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} @@ -21351,7 +21720,7 @@ packages: dependencies: object-assign: 4.1.1 react: 18.3.1 - react-is: 18.2.0 + react-is: 18.3.1 dev: false /react-style-singleton@2.2.1(@types/react@18.2.14)(react@18.2.0): @@ -21388,6 +21757,20 @@ packages: tslib: 2.8.1 dev: false + /react-transition-group@4.4.5(react-dom@18.3.1)(react@18.2.0): + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + dependencies: + '@babel/runtime': 7.26.0 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.3.1(react@18.2.0) + dev: false + /react@16.13.1: resolution: {integrity: sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==} engines: {node: '>=0.10.0'} @@ -21549,7 +21932,7 @@ packages: /regenerator-transform@0.15.1: resolution: {integrity: sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 dev: false /regexp.prototype.flags@1.5.2: @@ -21842,7 +22225,7 @@ packages: /rpc-websockets@7.5.1: resolution: {integrity: sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w==} dependencies: - '@babel/runtime': 7.25.6 + '@babel/runtime': 7.26.0 eventemitter3: 4.0.7 uuid: 8.3.2 ws: 8.17.1(bufferutil@4.0.7)(utf-8-validate@5.0.10) @@ -22621,6 +23004,10 @@ packages: postcss-selector-parser: 6.1.1 dev: true + /stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + dev: false + /sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -22789,7 +23176,7 @@ packages: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 - chokidar: 3.6.0 + chokidar: 3.5.3 didyoumean: 1.2.2 dlv: 1.1.3 fast-glob: 3.3.2 @@ -22806,7 +23193,7 @@ packages: postcss-js: 4.0.1(postcss@8.4.38) postcss-load-config: 4.0.2(postcss@8.4.38) postcss-nested: 6.2.0(postcss@8.4.38) - postcss-selector-parser: 6.1.1 + postcss-selector-parser: 6.0.16 resolve: 1.22.8 sucrase: 3.35.0 transitivePeerDependencies: @@ -22964,7 +23351,7 @@ packages: engines: {node: '>=6'} dependencies: psl: 1.9.0 - punycode: 2.3.1 + punycode: 2.3.0 universalify: 0.2.0 url-parse: 1.5.10 dev: true @@ -22976,7 +23363,7 @@ packages: resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} engines: {node: '>=12'} dependencies: - punycode: 2.3.1 + punycode: 2.3.0 dev: true /treeify@1.1.0: @@ -23435,7 +23822,7 @@ packages: /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: - punycode: 2.3.1 + punycode: 2.3.0 /url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} @@ -23664,30 +24051,6 @@ packages: - zod dev: false - /viem@2.21.29(typescript@5.1.3): - resolution: {integrity: sha512-n9LoCJjmI1XsE33nl+M4p3Wy5hczv7YC682RpX4Qk9cw8s9HJU+hUi5eDcNDPBcAwIHGCPKsf8yFBEYnE2XYVg==} - peerDependencies: - typescript: '>=5.0.4' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@adraffy/ens-normalize': 1.11.0 - '@noble/curves': 1.6.0 - '@noble/hashes': 1.5.0 - '@scure/bip32': 1.5.0 - '@scure/bip39': 1.4.0 - abitype: 1.0.6(typescript@5.1.3) - isows: 1.0.6(ws@8.17.1) - typescript: 5.1.3 - webauthn-p256: 0.0.10 - ws: 8.17.1(bufferutil@4.0.7)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - zod - dev: false - /viem@2.21.29(typescript@5.1.5): resolution: {integrity: sha512-n9LoCJjmI1XsE33nl+M4p3Wy5hczv7YC682RpX4Qk9cw8s9HJU+hUi5eDcNDPBcAwIHGCPKsf8yFBEYnE2XYVg==} peerDependencies: @@ -23736,21 +24099,21 @@ packages: - zod dev: false - /viem@2.21.35(typescript@5.1.3): - resolution: {integrity: sha512-f3EFc5JILeA9veuNymUN8HG/nKP9ykC0NCgwFrZWuxcCc822GaP0IEnkRBsHGqmjwbz//FxJFmvtx7TBcdVs0A==} + /viem@2.21.48(typescript@5.1.3): + resolution: {integrity: sha512-/hBHyG1gdIIuiQv0z9YmzXl5eWJa0UCZGwkeuQzH2Bmg6FIEwZeEcxgiytXZydip+p2wMBFa1jdr7o5O1+mrIg==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: typescript: optional: true dependencies: - '@adraffy/ens-normalize': 1.11.0 '@noble/curves': 1.6.0 '@noble/hashes': 1.5.0 '@scure/bip32': 1.5.0 '@scure/bip39': 1.4.0 abitype: 1.0.6(typescript@5.1.3) isows: 1.0.6(ws@8.17.1) + ox: 0.1.2(typescript@5.1.3) typescript: 5.1.3 webauthn-p256: 0.0.10 ws: 8.17.1(bufferutil@4.0.7)(utf-8-validate@5.0.10) @@ -23760,21 +24123,21 @@ packages: - zod dev: false - /viem@2.21.35(typescript@5.1.5): - resolution: {integrity: sha512-f3EFc5JILeA9veuNymUN8HG/nKP9ykC0NCgwFrZWuxcCc822GaP0IEnkRBsHGqmjwbz//FxJFmvtx7TBcdVs0A==} + /viem@2.21.48(typescript@5.1.5): + resolution: {integrity: sha512-/hBHyG1gdIIuiQv0z9YmzXl5eWJa0UCZGwkeuQzH2Bmg6FIEwZeEcxgiytXZydip+p2wMBFa1jdr7o5O1+mrIg==} peerDependencies: typescript: '>=5.0.4' peerDependenciesMeta: typescript: optional: true dependencies: - '@adraffy/ens-normalize': 1.11.0 '@noble/curves': 1.6.0 '@noble/hashes': 1.5.0 '@scure/bip32': 1.5.0 '@scure/bip39': 1.4.0 abitype: 1.0.6(typescript@5.1.5) isows: 1.0.6(ws@8.17.1) + ox: 0.1.2(typescript@5.1.5) typescript: 5.1.5 webauthn-p256: 0.0.10 ws: 8.17.1(bufferutil@4.0.7)(utf-8-validate@5.0.10) From f072e536ded105d5b94a5230bbab2ce539fbada0 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Tue, 29 Oct 2024 15:17:45 -0400 Subject: [PATCH 03/73] working otp --- examples/oauth/src/pages/index.tsx | 4 +- .../src/components/auth/Auth.module.css | 21 ++ .../sdk-react/src/components/auth/Auth.tsx | 254 ++++++++++-------- .../src/components/auth/OTPInput.tsx | 62 +++++ 4 files changed, 231 insertions(+), 110 deletions(-) create mode 100644 packages/sdk-react/src/components/auth/OTPInput.tsx diff --git a/examples/oauth/src/pages/index.tsx b/examples/oauth/src/pages/index.tsx index da5217d34..c7f6d3e39 100644 --- a/examples/oauth/src/pages/index.tsx +++ b/examples/oauth/src/pages/index.tsx @@ -34,8 +34,8 @@ export default function AuthPage() { console.log(process.env.API_PUBLIC_KEY!) const turnkeyClient = new TurnkeySDKClient({ apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, - apiPublicKey: "02fa9dbc9eeb32897675eab279687e0e73319791c9754c03a4d66a160e69703c47", - apiPrivateKey: "72d77ee664c821a9ff285373c751c3205e82b33f3763cd8219114e455b6971a9", + apiPublicKey: "02a1ba23e1b703fb3424294142807b89032d20d4f8be46ceb4fb0d1bf34ed763c3", + apiPrivateKey: "91d9f379bf8e111ff9ec6200a4b336029a3a3c691bcfe1936fc79605aef20093", defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, }); diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index 25f7dafe0..5c7ce8822 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -114,6 +114,27 @@ color: var(--Greyscale-600, #6C727E); background: var(--Greyscale-100, #EBEDF2); z-index: 0; } +.verification { + font-family: Inter; + font-size: 18px; + font-weight: 400; + line-height: 16px; + letter-spacing: -0.01em; + text-align: center; + color: var(--Greyscale-500, #868C95); + margin-top: 16px; +} + +.verificationBold { + color: var(--Greyscale-900, #2B2F33); + font-weight: 700; + margin-top: 8px; +} +.verificationIcon { + display: flex; + justify-content: center; + margin-bottom: 8px; +} .tos { font-family: Inter; font-size: 12px; diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index d06584215..7577cd213 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -1,38 +1,32 @@ import styles from "./Auth.module.css"; -import { SetStateAction, useEffect, useState } from "react"; -import { useForm } from "react-hook-form"; +import { useEffect, useState } from "react"; import { useTurnkey } from "../../hooks/useTurnkey"; import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; import { initAuth } from "../../api/initAuth"; +import { auth } from "../../api/auth"; import { getSuborgs } from "../../api/getSuborgs"; import { MuiPhone } from "./PhoneInput"; -import type { SetStateAction } from "react"; import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google"; import { sha256 } from "@noble/hashes/sha2"; import { bytesToHex } from "@noble/hashes/utils"; +import OTPInput from "./OtpInput"; + interface AuthProps { turnkeyClient: TurnkeySDKClient; } -const emailSchema = { - email: "", -}; - -const phoneSchema = { - phone: "", -}; - -const otpSchema = { - otp: "", -}; - const Auth: React.FC = ({ turnkeyClient }) => { const { turnkey, passkeyClient, authIframeClient } = useTurnkey(); const [loadingAction, setLoadingAction] = useState(null); const [error, setError] = useState(null); - const [phone, setPhone] = useState(""); + const [email, setEmail] = useState(""); + const [phone, setPhone] = useState(""); const [otpId, setOtpId] = useState(null); - const [authConfig, setAuthConfig] = useState({ + const [step, setStep] = useState("auth"); + const [suborgId, setSuborgId] = useState(""); + const [resendText, setResendText] = useState("Re-send Code"); + + const authConfig = { email: true, passkey: true, phone: true, @@ -41,19 +35,7 @@ const Auth: React.FC = ({ turnkeyClient }) => { facebook: true, apple: true, }, - }); - - const emailForm = useForm({ - defaultValues: emailSchema, - }); - - const phoneForm = useForm({ - defaultValues: phoneSchema, - }); - - const otpForm = useForm({ - defaultValues: otpSchema, - }); + }; useEffect(() => { if (error) { @@ -61,16 +43,8 @@ const Auth: React.FC = ({ turnkeyClient }) => { } }, [error]); - const handlePasskeyLogin = async (email: string) => { - setLoadingAction("passkey"); - setLoadingAction(null); - }; - - const handleGoogleLogin = async() => { - setLoadingAction("google"); - setLoadingAction(null); - } - const handleEmailLogin = async (email: string) => { + const handleEmailLogin = async () => { + setLoadingAction("email"); const getSuborgsRequest = { filterType: "EMAIL", filterValue: email, @@ -83,121 +57,184 @@ const Auth: React.FC = ({ turnkeyClient }) => { contact: email, }; const initAuthResponse = await initAuth(initAuthRequest, turnkeyClient); - + setSuborgId(getSuborgsResponse!.organizationIds[0]!); setOtpId(initAuthResponse!.otpId); - setLoadingAction("email"); + setStep("otpEmail"); setLoadingAction(null); }; const handlePhoneLogin = async () => { - if (!phone) return; setLoadingAction("phone"); + const getSuborgsRequest = { + filterType: "PHONE", + filterValue: phone, + }; + const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); + + const initAuthRequest = { + suborgID: getSuborgsResponse!.organizationIds[0]!, + otpType: "OTP_TYPE_SMS", + contact: phone, + }; + const initAuthResponse = await initAuth(initAuthRequest, turnkeyClient); + setSuborgId(getSuborgsResponse!.organizationIds[0]!); + setOtpId(initAuthResponse!.otpId); + setStep("otpPhone"); setLoadingAction(null); }; const handleEnterOtp = async (otp: string) => { setLoadingAction("otp"); - // Here you would add your logic to verify the OTP using the otpId console.log(`Verifying OTP: ${otp} with otpId: ${otpId}`); - setLoadingAction(null); - }; + // Add OTP verification logic here + const authRequest = { + suborgID: suborgId, + otpId: otpId, + otpCode: otp, + targetPublicKey: authIframeClient!.iframePublicKey!, + }; - const onSubmitEmail = async (values: any) => { - const email = values.email; - await handleEmailLogin(email); + const authResponse = await auth(authRequest, turnkeyClient); + console.log(authResponse) + setLoadingAction(null); }; - const onSubmitPhone = async (values: any) => { - // TODO: Handle phone login + const handleGoogleLogin = async () => { + setLoadingAction("google"); + setLoadingAction(null); }; - const onSubmitOtp = async (values: any) => { - const otp = values.otp; - await handleEnterOtp(otp); + const handleResendCode = async () => { + if (step === "otpEmail") { + await handleEmailLogin(); + } else if (step === "otpPhone") { + await handlePhoneLogin(); + } + setResendText("Code Sent ✓"); }; return (
-

Log in or sign up

+

{otpId ? "Enter verification code" : "Log in or sign up"}

{authConfig.email && !otpId && ( -
+
setEmail(e.target.value)} />
- - +
)} -{authConfig.passkey && !otpId && ( -
- -
+ {authConfig.passkey && !otpId && ( + )} - {authConfig.phone && ( + {authConfig.phone && !otpId && (
-
- OR -
-
-
- ) => setPhone(phone)}/> +
+ OR
- -
)} {otpId && ( -
-
- -
- -
+
+
+{step === "otpEmail" +? + + + +: + + + + +} + +
+ + + We've sent a verification code to{" "} +
{step === "otpEmail" ? email : phone}
+
+ +
)}
-
- OR + + {!otpId && authConfig.socials.google && authIframeClient && ( +
+
+ OR +
+ + + +
+ )} + +
+ {!otpId ? ( + + By logging in you agree to our{" "} + Terms of Service &{" "} + Privacy Policy + + ) : ( + + Did not receive your code?{" "} + + {resendText} + + + )}
- {authConfig.socials.google && authIframeClient && - - - - - } - {/* {authConfig.socials.facebook && } - {authConfig.socials.apple && } */} -
-By logging in you agree to our Terms of Service & Privacy Policy -
-
- Powered by +
+ Powered by + + @@ -209,7 +246,8 @@ const Auth: React.FC = ({ turnkeyClient }) => { -
+ +
); }; diff --git a/packages/sdk-react/src/components/auth/OTPInput.tsx b/packages/sdk-react/src/components/auth/OTPInput.tsx new file mode 100644 index 000000000..c69fb36e0 --- /dev/null +++ b/packages/sdk-react/src/components/auth/OTPInput.tsx @@ -0,0 +1,62 @@ +import React, { useState } from "react"; +import { TextField, Box } from "@mui/material"; + +interface OTPInputProps { + onComplete: (otp: string) => void; +} + +const OTPInput: React.FC = ({ onComplete }) => { + const [otp, setOtp] = useState(Array(6).fill("")); + + const handleChange = (value: string, index: number) => { + if (/^\d*$/.test(value)) { + const newOtp = [...otp]; + newOtp[index] = value; + setOtp(newOtp); + + // If all boxes are filled, call onComplete with the OTP + if (newOtp.every((digit) => digit !== "")) { + onComplete(newOtp.join("")); + } + + // Move focus to the next box if current is filled + if (value && index < 5) { + const nextInput = document.getElementById(`otp-input-${index + 1}`); + if (nextInput) (nextInput as HTMLInputElement).focus(); + } + } + }; + + const handleKeyDown = (event: React.KeyboardEvent, index: number) => { + if (event.key === "Backspace" && otp[index] === "" && index > 0) { + const prevInput = document.getElementById(`otp-input-${index - 1}`); + if (prevInput) (prevInput as HTMLInputElement).focus(); + } + }; + + return ( + + {otp.map((digit, index) => ( + handleChange(e.target.value, index)} + onKeyDown={(e) => handleKeyDown(e, index)} + inputProps={{ + maxLength: 1, + style: { + textAlign: "center", + fontSize: "1.5rem", + width: "60px", + background: "white", + }, + }} + variant="outlined" + /> + ))} + + ); +}; + +export default OTPInput; From 243e6e54ad437709e7d6d4471344630ee395939c Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Wed, 30 Oct 2024 12:28:38 -0400 Subject: [PATCH 04/73] error handling otp codes, pastabled otp codes, more cleanup --- examples/oauth/src/pages/index.tsx | 22 +++++- .../sdk-react/src/components/auth/Auth.tsx | 76 ++++++++++++++++++- .../src/components/auth/OTPInput.tsx | 16 ++++ 3 files changed, 108 insertions(+), 6 deletions(-) diff --git a/examples/oauth/src/pages/index.tsx b/examples/oauth/src/pages/index.tsx index c7f6d3e39..b32994a73 100644 --- a/examples/oauth/src/pages/index.tsx +++ b/examples/oauth/src/pages/index.tsx @@ -38,7 +38,7 @@ export default function AuthPage() { apiPrivateKey: "91d9f379bf8e111ff9ec6200a4b336029a3a3c691bcfe1936fc79605aef20093", defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, }); - + console.log(authIframeClient?.iframePublicKey!) const { register: authFormRegister, handleSubmit: authFormSubmit } = useForm(); const { @@ -125,7 +125,20 @@ export default function AuthPage() { alert(`SUCCESS! Wallet and new address created: ${address} `); }; - + const getWhoAmI = async () => { + if (authIframeClient) { + try { + const whoamiResponse = await authIframeClient.getWhoami({ + organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, + }); + console.log("WhoAmI Response:", whoamiResponse); + } catch (error) { + console.error("Error fetching WhoAmI:", error); + } + } else { + console.error("Auth iframe client is null"); + } + }; return (
)} + {authIframeClient && ( + + )}
); } diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 7577cd213..975d33290 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -19,13 +19,14 @@ const Auth: React.FC = ({ turnkeyClient }) => { const { turnkey, passkeyClient, authIframeClient } = useTurnkey(); const [loadingAction, setLoadingAction] = useState(null); const [error, setError] = useState(null); + const [otpError, setOtpError] = useState(null); const [email, setEmail] = useState(""); const [phone, setPhone] = useState(""); const [otpId, setOtpId] = useState(null); const [step, setStep] = useState("auth"); const [suborgId, setSuborgId] = useState(""); const [resendText, setResendText] = useState("Re-send Code"); - + console.log(authIframeClient?.iframePublicKey!) const authConfig = { email: true, passkey: true, @@ -43,6 +44,63 @@ const Auth: React.FC = ({ turnkeyClient }) => { } }, [error]); + const handleLoginWithPasskey = async () => { + setLoadingAction("email"); + const getSuborgsRequest = { + filterType: "EMAIL", + filterValue: email, + }; + const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); + if (getSuborgsResponse!.organizationIds.length > 0){ + const loginResponse = await passkeyClient?.login() + if (loginResponse?.organizationId) { + console.log("ACCOUNT FOUND") + } + } + else { + // User either does not have an account with a sub organization + // or does not have a passkey + // Create a new passkey for the user + const { encodedChallenge, attestation } = + (await passkeyClient?.createUserPasskey({ + publicKey: { + user: { + name: email, + displayName: email, + }, + }, + })) || {} + + // Create a new sub organization for the user + if (encodedChallenge && attestation) { + //TODO + // const { subOrg, user } = await createUserSubOrg({ + // email: email as Email, + // passkey: { + // challenge: encodedChallenge, + // attestation, + // }, + // }) + + // if (subOrg && user) { + // const org = { + // organizationId: subOrg.subOrganizationId, + // organizationName: "", + // } + // const currentUser = { + // userId: user.userId, + // username: user.userName, + // organization: org, + // } + // localStorage.setItem( + // "@turnkey/current_user", + // JSON.stringify(currentUser) + // ) + // } + } + } + } + const handleEmailLogin = async () => { setLoadingAction("email"); const getSuborgsRequest = { @@ -66,7 +124,7 @@ const Auth: React.FC = ({ turnkeyClient }) => { const handlePhoneLogin = async () => { setLoadingAction("phone"); const getSuborgsRequest = { - filterType: "PHONE", + filterType: "PHONE_NUMBER", filterValue: phone, }; const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); @@ -89,13 +147,18 @@ const Auth: React.FC = ({ turnkeyClient }) => { // Add OTP verification logic here const authRequest = { suborgID: suborgId, - otpId: otpId, + otpId: otpId!, otpCode: otp, targetPublicKey: authIframeClient!.iframePublicKey!, }; const authResponse = await auth(authRequest, turnkeyClient); - console.log(authResponse) + if (authResponse?.credentialBundle) { + await authIframeClient!.injectCredentialBundle(authResponse.credentialBundle); + setOtpError(null); + } else { + setOtpError("Invalid OTP code, please try again"); + } setLoadingAction(null); }; @@ -105,6 +168,7 @@ const Auth: React.FC = ({ turnkeyClient }) => { }; const handleResendCode = async () => { + setOtpError(null); if (step === "otpEmail") { await handleEmailLogin(); } else if (step === "otpPhone") { @@ -189,7 +253,11 @@ const Auth: React.FC = ({ turnkeyClient }) => {
+ )} + {otpError && ( +
{otpError}
+ )}
diff --git a/packages/sdk-react/src/components/auth/OTPInput.tsx b/packages/sdk-react/src/components/auth/OTPInput.tsx index c69fb36e0..2d74998ca 100644 --- a/packages/sdk-react/src/components/auth/OTPInput.tsx +++ b/packages/sdk-react/src/components/auth/OTPInput.tsx @@ -34,6 +34,21 @@ const OTPInput: React.FC = ({ onComplete }) => { } }; + const handlePaste = (event: React.ClipboardEvent) => { + const pasteData = event.clipboardData.getData("Text"); + if (/^\d{6}$/.test(pasteData)) { + const newOtp = pasteData.split(""); + setOtp(newOtp); + onComplete(newOtp.join("")); + + // Automatically move focus to the last input box + const lastInput = document.getElementById(`otp-input-5`); + if (lastInput) (lastInput as HTMLInputElement).focus(); + + event.preventDefault(); + } + }; + return ( {otp.map((digit, index) => ( @@ -43,6 +58,7 @@ const OTPInput: React.FC = ({ onComplete }) => { value={digit} onChange={(e) => handleChange(e.target.value, index)} onKeyDown={(e) => handleKeyDown(e, index)} + onPaste={index === 0 ? handlePaste : undefined} // Handle paste on the first input only inputProps={{ maxLength: 1, style: { From ce2a3aa77dd7b5fd31788b68881cd51546203891 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Wed, 30 Oct 2024 17:07:40 -0400 Subject: [PATCH 05/73] all e2e flows - need some styling cleanup, code cleanup, server actions, onauthsuccess handle --- packages/sdk-react/src/api/createSuborg.ts | 56 ++++++++ .../src/api/{initAuth.ts => initOtpAuth.ts} | 10 +- packages/sdk-react/src/api/oauth.ts | 36 +++++ .../sdk-react/src/api/{auth.ts => otpAuth.ts} | 10 +- .../src/components/auth/Auth.module.css | 7 + .../sdk-react/src/components/auth/Auth.tsx | 124 ++++++++++++------ 6 files changed, 190 insertions(+), 53 deletions(-) create mode 100644 packages/sdk-react/src/api/createSuborg.ts rename packages/sdk-react/src/api/{initAuth.ts => initOtpAuth.ts} (76%) create mode 100644 packages/sdk-react/src/api/oauth.ts rename packages/sdk-react/src/api/{auth.ts => otpAuth.ts} (83%) diff --git a/packages/sdk-react/src/api/createSuborg.ts b/packages/sdk-react/src/api/createSuborg.ts new file mode 100644 index 000000000..1059ff089 --- /dev/null +++ b/packages/sdk-react/src/api/createSuborg.ts @@ -0,0 +1,56 @@ +import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; + +type CreateSuborgRequest = { + oauthProviders?: Provider[]; + email?: string; + phoneNumber?: string; + passkey?: Passkey; + +}; + +type Passkey = { + authenticatorName: string, + challenge: any, + attestation: any, + }; + +type Provider = { + providerName: string; + oidcToken: string; +}; + +type CreateSuborgResponse = { + subOrganizationId: string; +}; + +export async function createSuborg( + request: CreateSuborgRequest, + turnkeyClient: TurnkeySDKClient +): Promise { + try { + const suborgResponse = await turnkeyClient.apiClient().createSubOrganization({ + subOrganizationName: `suborg-${String(Date.now())}`, + rootQuorumThreshold: 1, + rootUsers: [ + { + userName: request.email ?? "", + userEmail: request.email ?? "", + userPhoneNumber: request.phoneNumber ?? "", + apiKeys: [], + authenticators: request.passkey ? [request.passkey] : [], + oauthProviders: request.oauthProviders ?? [], + }, + ], + }); + + const { subOrganizationId } = suborgResponse; + if (!subOrganizationId) { + throw new Error("Expected a non-null subOrganizationId."); + } + + return { subOrganizationId }; + } catch (error) { + console.error(error); + return undefined; + } +} diff --git a/packages/sdk-react/src/api/initAuth.ts b/packages/sdk-react/src/api/initOtpAuth.ts similarity index 76% rename from packages/sdk-react/src/api/initAuth.ts rename to packages/sdk-react/src/api/initOtpAuth.ts index 09d14b229..b5b53d479 100644 --- a/packages/sdk-react/src/api/initAuth.ts +++ b/packages/sdk-react/src/api/initOtpAuth.ts @@ -1,20 +1,20 @@ import type {Turnkey as TurnkeySDKClient} from "@turnkey/sdk-server"; -type InitAuthRequest = { +type InitOtpAuthRequest = { suborgID: string; otpType: string; contact: string; }; -type InitAuthResponse = { +type InitOtpAuthResponse = { otpId: string; }; -export async function initAuth( - request: InitAuthRequest, +export async function initOtpAuth( + request: InitOtpAuthRequest, turnkeyClient: TurnkeySDKClient -): Promise { +): Promise { try { const initOtpAuthResponse = await turnkeyClient.apiClient().initOtpAuth({ contact: request.contact, diff --git a/packages/sdk-react/src/api/oauth.ts b/packages/sdk-react/src/api/oauth.ts new file mode 100644 index 000000000..c25b4b118 --- /dev/null +++ b/packages/sdk-react/src/api/oauth.ts @@ -0,0 +1,36 @@ +import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; + +type OauthRequest = { + suborgID: string; + oidcToken: string; + targetPublicKey: string; +}; + +type OauthResponse = { + userId: string; + apiKeyId: string; + credentialBundle: string; +}; + +export async function oauth( + request: OauthRequest, + turnkeyClient: TurnkeySDKClient +): Promise { + try { + const oauthResponse = await turnkeyClient.apiClient().oauth({ + oidcToken: request.oidcToken, + targetPublicKey: request.targetPublicKey, + organizationId: request.suborgID, + }); + + const { credentialBundle, apiKeyId, userId } = oauthResponse; + if (!credentialBundle || !apiKeyId || !userId) { + throw new Error("Expected non-null values for credentialBundle, apiKeyId, and userId."); + } + + return { credentialBundle, apiKeyId, userId }; + } catch (error) { + console.error(error); + return undefined; + } +} diff --git a/packages/sdk-react/src/api/auth.ts b/packages/sdk-react/src/api/otpAuth.ts similarity index 83% rename from packages/sdk-react/src/api/auth.ts rename to packages/sdk-react/src/api/otpAuth.ts index 9780e509c..2d8538efa 100644 --- a/packages/sdk-react/src/api/auth.ts +++ b/packages/sdk-react/src/api/otpAuth.ts @@ -1,22 +1,22 @@ import type {Turnkey as TurnkeySDKClient} from "@turnkey/sdk-server"; -type AuthRequest = { +type OtpAuthRequest = { suborgID: string; otpId: string; otpCode: string; targetPublicKey: string; }; -type AuthResponse = { +type OtpAuthResponse = { userId: string; apiKeyId: string; credentialBundle: string; }; -export async function auth( - request: AuthRequest, +export async function otpAuth( + request: OtpAuthRequest, turnkeyClient: TurnkeySDKClient -): Promise { +): Promise { try { const otpAuthResponse = await turnkeyClient.apiClient().otpAuth({ otpId: request.otpId, diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index 5c7ce8822..49218dabe 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -201,4 +201,11 @@ color: var(--Greyscale-600, #6C727E); .phoneInput { margin-top: 12px; margin-bottom: 12px; +} + +.errorText { + text-align: center; + margin-top: 12px; + color: #FF4C4C; + font-weight: 550; } \ No newline at end of file diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 975d33290..246de656f 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -2,14 +2,16 @@ import styles from "./Auth.module.css"; import { useEffect, useState } from "react"; import { useTurnkey } from "../../hooks/useTurnkey"; import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; -import { initAuth } from "../../api/initAuth"; -import { auth } from "../../api/auth"; +import { initOtpAuth } from "../../api/initOtpAuth"; +import { otpAuth } from "../../api/otpAuth"; import { getSuborgs } from "../../api/getSuborgs"; import { MuiPhone } from "./PhoneInput"; import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google"; import { sha256 } from "@noble/hashes/sha2"; import { bytesToHex } from "@noble/hashes/utils"; import OTPInput from "./OtpInput"; +import { createSuborg } from "../../api/createSuborg"; +import { oauth } from "../../api/oauth"; interface AuthProps { turnkeyClient: TurnkeySDKClient; @@ -52,10 +54,10 @@ const Auth: React.FC = ({ turnkeyClient }) => { }; const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); if (getSuborgsResponse!.organizationIds.length > 0){ - const loginResponse = await passkeyClient?.login() - if (loginResponse?.organizationId) { - console.log("ACCOUNT FOUND") - } + const createReadWriteSessionResponse = await passkeyClient?.createReadWriteSession({organizationId: getSuborgsResponse!.organizationIds[0]!, targetPublicKey: authIframeClient?.iframePublicKey!}) + if (createReadWriteSessionResponse!.credentialBundle) { + await authIframeClient!.injectCredentialBundle(createReadWriteSessionResponse!.credentialBundle); + } } else { // User either does not have an account with a sub organization @@ -73,30 +75,22 @@ const Auth: React.FC = ({ turnkeyClient }) => { // Create a new sub organization for the user if (encodedChallenge && attestation) { - //TODO - // const { subOrg, user } = await createUserSubOrg({ - // email: email as Email, - // passkey: { - // challenge: encodedChallenge, - // attestation, - // }, - // }) + const createSuborgRequest = { + email, + passkey: { + authenticatorName: "First Passkey", + challenge: encodedChallenge, + attestation - // if (subOrg && user) { - // const org = { - // organizationId: subOrg.subOrganizationId, - // organizationName: "", - // } - // const currentUser = { - // userId: user.userId, - // username: user.userName, - // organization: org, - // } - // localStorage.setItem( - // "@turnkey/current_user", - // JSON.stringify(currentUser) - // ) - // } + } + }; + const createSuborgResponse = await createSuborg(createSuborgRequest, turnkeyClient); + if (createSuborgResponse?.subOrganizationId) { + const createReadWriteSessionResponse = await passkeyClient?.createReadWriteSession({organizationId: createSuborgResponse?.subOrganizationId, targetPublicKey: authIframeClient?.iframePublicKey!}) + if (createReadWriteSessionResponse!.credentialBundle) { + await authIframeClient!.injectCredentialBundle(createReadWriteSessionResponse!.credentialBundle); + } + } } } } @@ -108,14 +102,21 @@ const Auth: React.FC = ({ turnkeyClient }) => { filterValue: email, }; const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); - + let suborgId = getSuborgsResponse!.organizationIds[0]! + if (!suborgId){ + const createSuborgRequest = { + email, + }; + const createSuborgResponse = await createSuborg(createSuborgRequest, turnkeyClient); + suborgId = createSuborgResponse?.subOrganizationId! + } const initAuthRequest = { - suborgID: getSuborgsResponse!.organizationIds[0]!, + suborgID: suborgId, otpType: "OTP_TYPE_EMAIL", contact: email, }; - const initAuthResponse = await initAuth(initAuthRequest, turnkeyClient); - setSuborgId(getSuborgsResponse!.organizationIds[0]!); + const initAuthResponse = await initOtpAuth(initAuthRequest, turnkeyClient); + setSuborgId(suborgId); setOtpId(initAuthResponse!.otpId); setStep("otpEmail"); setLoadingAction(null); @@ -128,14 +129,21 @@ const Auth: React.FC = ({ turnkeyClient }) => { filterValue: phone, }; const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); - + let suborgId = getSuborgsResponse!.organizationIds[0]! + if (!suborgId){ + const createSuborgRequest = { + phoneNumber: phone, + }; + const createSuborgResponse = await createSuborg(createSuborgRequest, turnkeyClient); + suborgId = createSuborgResponse?.subOrganizationId! + } const initAuthRequest = { - suborgID: getSuborgsResponse!.organizationIds[0]!, + suborgID: suborgId, otpType: "OTP_TYPE_SMS", contact: phone, }; - const initAuthResponse = await initAuth(initAuthRequest, turnkeyClient); - setSuborgId(getSuborgsResponse!.organizationIds[0]!); + const initAuthResponse = await initOtpAuth(initAuthRequest, turnkeyClient); + setSuborgId(suborgId); setOtpId(initAuthResponse!.otpId); setStep("otpPhone"); setLoadingAction(null); @@ -152,18 +160,48 @@ const Auth: React.FC = ({ turnkeyClient }) => { targetPublicKey: authIframeClient!.iframePublicKey!, }; - const authResponse = await auth(authRequest, turnkeyClient); + const authResponse = await otpAuth(authRequest, turnkeyClient); if (authResponse?.credentialBundle) { await authIframeClient!.injectCredentialBundle(authResponse.credentialBundle); setOtpError(null); } else { - setOtpError("Invalid OTP code, please try again"); + setOtpError("Invalid code, please try again"); } setLoadingAction(null); }; - const handleGoogleLogin = async () => { - setLoadingAction("google"); + const handleGoogleLogin = async (response: any) => { + setLoadingAction("oauth"); + + const getSuborgsRequest = { + filterType: "OIDC_TOKEN", + filterValue: response.credential, + }; + const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); + let suborgId = getSuborgsResponse!.organizationIds[0]! + if (!suborgId){ + const createSuborgRequest = { + oauthProviders : [{ + providerName: "Google OIDC", + oidcToken: response.credential + }] + }; + const createSuborgResponse = await createSuborg(createSuborgRequest, turnkeyClient); + suborgId = createSuborgResponse?.subOrganizationId! + } + const oauthRequest = { + suborgID: suborgId, + oidcToken: response.credential, + targetPublicKey: authIframeClient?.iframePublicKey!, + }; + const oauthResponse = await oauth(oauthRequest, turnkeyClient); + console.log(response.credential) + console.log(oauthResponse) + setSuborgId(suborgId); + if (oauthResponse!.credentialBundle) { + console.log(oauthResponse!.credentialBundle) + await authIframeClient!.injectCredentialBundle(oauthResponse!.credentialBundle); + } setLoadingAction(null); }; @@ -205,7 +243,7 @@ const Auth: React.FC = ({ turnkeyClient }) => { + )} + + ); +} + +function refineNonNull( + input: T | null | undefined, + errorMessage?: string +): T { + if (input == null) { + throw new Error(errorMessage ?? `Unexpected ${JSON.stringify(input)}`); + } + + return input; +} diff --git a/examples/react-components/tsconfig.json b/examples/react-components/tsconfig.json new file mode 100644 index 000000000..06f3d9aad --- /dev/null +++ b/examples/react-components/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"], + "react": ["./node_modules/@types/react"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 246de656f..999f73dbf 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -15,9 +15,10 @@ import { oauth } from "../../api/oauth"; interface AuthProps { turnkeyClient: TurnkeySDKClient; + onHandleAuthSuccess: () => Promise; } -const Auth: React.FC = ({ turnkeyClient }) => { +const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess }) => { const { turnkey, passkeyClient, authIframeClient } = useTurnkey(); const [loadingAction, setLoadingAction] = useState(null); const [error, setError] = useState(null); @@ -57,6 +58,7 @@ const Auth: React.FC = ({ turnkeyClient }) => { const createReadWriteSessionResponse = await passkeyClient?.createReadWriteSession({organizationId: getSuborgsResponse!.organizationIds[0]!, targetPublicKey: authIframeClient?.iframePublicKey!}) if (createReadWriteSessionResponse!.credentialBundle) { await authIframeClient!.injectCredentialBundle(createReadWriteSessionResponse!.credentialBundle); + await onHandleAuthSuccess(); } } else { @@ -89,6 +91,7 @@ const Auth: React.FC = ({ turnkeyClient }) => { const createReadWriteSessionResponse = await passkeyClient?.createReadWriteSession({organizationId: createSuborgResponse?.subOrganizationId, targetPublicKey: authIframeClient?.iframePublicKey!}) if (createReadWriteSessionResponse!.credentialBundle) { await authIframeClient!.injectCredentialBundle(createReadWriteSessionResponse!.credentialBundle); + await onHandleAuthSuccess(); } } } @@ -163,6 +166,7 @@ const Auth: React.FC = ({ turnkeyClient }) => { const authResponse = await otpAuth(authRequest, turnkeyClient); if (authResponse?.credentialBundle) { await authIframeClient!.injectCredentialBundle(authResponse.credentialBundle); + await onHandleAuthSuccess(); setOtpError(null); } else { setOtpError("Invalid code, please try again"); @@ -201,6 +205,7 @@ const Auth: React.FC = ({ turnkeyClient }) => { if (oauthResponse!.credentialBundle) { console.log(oauthResponse!.credentialBundle) await authIframeClient!.injectCredentialBundle(oauthResponse!.credentialBundle); + await onHandleAuthSuccess(); } setLoadingAction(null); }; From 7fb78bfc881bded702032a4f3995ce33fad2bc9f Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Thu, 31 Oct 2024 11:56:10 -0400 Subject: [PATCH 07/73] reset oauth --- examples/oauth/src/pages/_app.tsx | 2 +- examples/oauth/src/pages/index.tsx | 37 +++++------------------------- 2 files changed, 7 insertions(+), 32 deletions(-) diff --git a/examples/oauth/src/pages/_app.tsx b/examples/oauth/src/pages/_app.tsx index 8a159d5cf..a1268bf86 100644 --- a/examples/oauth/src/pages/_app.tsx +++ b/examples/oauth/src/pages/_app.tsx @@ -1,6 +1,6 @@ import { AppProps } from "next/app"; import Head from "next/head"; -import '@turnkey/sdk-react/styles'; + import { TurnkeyProvider } from "@turnkey/sdk-react"; const turnkeyConfig = { diff --git a/examples/oauth/src/pages/index.tsx b/examples/oauth/src/pages/index.tsx index 65bae446d..3be98ac90 100644 --- a/examples/oauth/src/pages/index.tsx +++ b/examples/oauth/src/pages/index.tsx @@ -1,14 +1,14 @@ import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google"; import Image from "next/image"; import styles from "./index.module.css"; +import { useTurnkey } from "@turnkey/sdk-react"; import { useForm } from "react-hook-form"; import axios from "axios"; import * as React from "react"; import { useState } from "react"; import { sha256 } from "@noble/hashes/sha2"; import { bytesToHex } from "@noble/hashes/utils"; -import { useTurnkey, Auth} from "@turnkey/sdk-react"; -import { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; + /** * Type definition for the server response coming back from `/api/auth` */ @@ -31,20 +31,13 @@ type AuthFormData = { export default function AuthPage() { const [authResponse, setAuthResponse] = useState(null); const { authIframeClient } = useTurnkey(); - console.log(process.env.API_PUBLIC_KEY!) - const turnkeyClient = new TurnkeySDKClient({ - apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, - apiPublicKey: "02a1ba23e1b703fb3424294142807b89032d20d4f8be46ceb4fb0d1bf34ed763c3", - apiPrivateKey: "91d9f379bf8e111ff9ec6200a4b336029a3a3c691bcfe1936fc79605aef20093", - defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, - }); - console.log(authIframeClient?.iframePublicKey!) const { register: authFormRegister, handleSubmit: authFormSubmit } = useForm(); const { register: injectCredentialsFormRegister, handleSubmit: injectCredentialsFormSubmit, - } = useForm() + } = useForm(); + const handleGoogleLogin = async (response: any) => { let targetSubOrgId: string; const getSuborgsResponse = await axios.post("api/getSuborgs", { @@ -126,20 +119,6 @@ export default function AuthPage() { alert(`SUCCESS! Wallet and new address created: ${address} `); }; - const getWhoAmI = async () => { - if (authIframeClient) { - try { - const whoamiResponse = await authIframeClient.getWhoami({ - organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, - }); - console.log("WhoAmI Response:", whoamiResponse); - } catch (error) { - console.error("Error fetching WhoAmI:", error); - } - } else { - console.error("Auth iframe client is null"); - } - }; return (
+ {!authIframeClient &&

Loading...

} - + {authIframeClient && authIframeClient.iframePublicKey && authResponse === null && ( @@ -213,11 +193,6 @@ export default function AuthPage() { /> )} - {authIframeClient && ( - - )}
); } From 920521b213c53c76cdb7ba9de62a5bb2a4e4eb40 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Thu, 31 Oct 2024 15:15:08 -0400 Subject: [PATCH 08/73] everything working e2e except apple and facebook --- examples/react-components/next-env.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/react-components/next-env.d.ts b/examples/react-components/next-env.d.ts index 40c3d6809..a4a7b3f5c 100644 --- a/examples/react-components/next-env.d.ts +++ b/examples/react-components/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. From ff5658e0a76c5d8dd510033399d257307e4c584c Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Thu, 31 Oct 2024 15:15:08 -0400 Subject: [PATCH 09/73] everything working e2e except apple and facebook --- examples/react-components/package.json | 9 +- .../src/pages/index.module.css | 100 ++-- examples/react-components/src/pages/index.tsx | 444 +++++++++--------- packages/sdk-react/package.json | 1 + .../src/components/auth/Auth.module.css | 19 +- .../sdk-react/src/components/auth/Auth.tsx | 134 ++++-- pnpm-lock.yaml | 277 ++++++++++- 7 files changed, 689 insertions(+), 295 deletions(-) diff --git a/examples/react-components/package.json b/examples/react-components/package.json index 42c4aef91..dd9490b8b 100644 --- a/examples/react-components/package.json +++ b/examples/react-components/package.json @@ -10,10 +10,12 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@noble/hashes": "1.4.0", - "@react-oauth/google": "^0.12.1", - "@turnkey/sdk-server": "workspace:*", + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", + "@mui/icons-material": "^6.1.5", + "@mui/material": "^6.1.5", "@turnkey/sdk-react": "workspace:*", + "@turnkey/sdk-server": "workspace:*", "@types/node": "20.3.1", "@types/react": "18.2.14", "@types/react-dom": "18.2.6", @@ -27,7 +29,6 @@ "npm": "^9.7.2", "react": "18.2.0", "react-dom": "18.2.0", - "react-hook-form": "^7.45.1", "typescript": "5.1.3" } } diff --git a/examples/react-components/src/pages/index.module.css b/examples/react-components/src/pages/index.module.css index 9e52fa65f..78612df85 100644 --- a/examples/react-components/src/pages/index.module.css +++ b/examples/react-components/src/pages/index.module.css @@ -18,69 +18,85 @@ .main { display: flex; - flex-direction: column; - justify-content: space-between; + justify-content: center; /* Centers the authComponent */ align-items: center; padding: 6rem; gap: 60px; + position: relative; /* Allows absolute positioning for left-aligned card */ } -.input { - display: block; - width: 240px; - margin: 0; - padding: 10px 16px; - border-radius: 8px; - border-width: 1px; - border-style: solid; - border-color: rgba(216, 219, 227, 1); - font-family: "Inter"; - margin: 0 auto; +.authConfigContainer { + position: absolute; + left: 0; /* Positions the card to the far left */ + display: flex; + justify-content: flex-start; } -.input_checkbox { - display: block; - margin: 0 auto; +.authConfigCard { + background: var(--Greyscale-20, #F5F7FB); + padding: 16px; + border-radius: 8px; + width: 552px; + height: 768px; + border: 1px solid var(--Greyscale-100, #EBEDF2); + font-family: Arial, sans-serif; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + display: flex; + flex-direction: column; + justify-content: space-between; } - -.label { - font-family: "Inter"; - display: block; +.configTitle { + font-family: Inter; + font-size: 24px; + font-weight: 700; + line-height: 26px; + letter-spacing: -0.01em; text-align: center; + margin-bottom: 16px; + margin-top: 16px; } -.prompt { - font-family: "Inter"; +.toggleContainer { + display: flex; + flex-direction: column; + gap: 12px; } -.base { +.labelContainer { display: flex; - flex-direction: column; align-items: center; - justify-content: center; + gap: 8px; } -.button { - display: inline-flex; - align-items: center; - justify-content: center; - margin: 0; - padding: 10px 16px; +.toggleRow { + background-color: #ffffff; border-radius: 8px; - border-width: 1px; - border-style: solid; + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 16px; +} + +.copyConfigButton { + display: flex; + align-items: center; cursor: pointer; - color: white; - background-color: rgba(43, 47, 51, 1); - border-color: rgba(63, 70, 75, 1); - font-family: "Inter"; + color: #666; + font-size: 14px; + margin-top: auto; + padding: 8px 16px; +} +.copyConfigText { + padding-left: 4px; } -.form { +.authComponent { display: flex; - flex-direction: column; - justify-content: space-between; + justify-content: center; align-items: center; - gap: 20px; - min-width: 400px; + flex: 1; +} + +.success { + justify-items: center; } diff --git a/examples/react-components/src/pages/index.tsx b/examples/react-components/src/pages/index.tsx index 65bae446d..308ad7d77 100644 --- a/examples/react-components/src/pages/index.tsx +++ b/examples/react-components/src/pages/index.tsx @@ -1,234 +1,260 @@ -import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google"; -import Image from "next/image"; import styles from "./index.module.css"; -import { useForm } from "react-hook-form"; -import axios from "axios"; import * as React from "react"; import { useState } from "react"; -import { sha256 } from "@noble/hashes/sha2"; -import { bytesToHex } from "@noble/hashes/utils"; -import { useTurnkey, Auth} from "@turnkey/sdk-react"; +import { useTurnkey, Auth } from "@turnkey/sdk-react"; import { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; -/** - * Type definition for the server response coming back from `/api/auth` - */ -type AuthResponse = { - userId: string; - apiKeyId: string; - credentialBundle: string; -}; - -/** - * Type definitions for the form data (client-side forms) - */ -type InjectCredentialsFormData = { - walletName: string; -}; -type AuthFormData = { - invalidateExisting: boolean; -}; +import { Switch, Typography, IconButton } from '@mui/material'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import AppsIcon from '@mui/icons-material/Apps'; + +// Define types for config and socials +interface SocialConfig { + enabled: boolean; + google: boolean; + apple: boolean; +} + +interface Config { + email: boolean; + passkey: boolean; + phone: boolean; + socials: SocialConfig; +} export default function AuthPage() { - const [authResponse, setAuthResponse] = useState(null); const { authIframeClient } = useTurnkey(); - console.log(process.env.API_PUBLIC_KEY!) + const [orgData, setOrgData] = useState(); + const turnkeyClient = new TurnkeySDKClient({ - apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, - apiPublicKey: "02a1ba23e1b703fb3424294142807b89032d20d4f8be46ceb4fb0d1bf34ed763c3", - apiPrivateKey: "91d9f379bf8e111ff9ec6200a4b336029a3a3c691bcfe1936fc79605aef20093", - defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, + apiBaseUrl: "https://api.preprod.turnkey.engineering", + apiPublicKey: "03352f813eadd3efa85a2dad1a0c39391e5544de074665f3986185c92566b39885", + apiPrivateKey: "954aa8fdef994a555952f1ee1ea4fb554cb422e5fc896939412a954c4e87c603", + defaultOrganizationId: "51fb75bf-c044-4f9f-903b-4fba6bfedab9", }); - console.log(authIframeClient?.iframePublicKey!) - const { register: authFormRegister, handleSubmit: authFormSubmit } = - useForm(); - const { - register: injectCredentialsFormRegister, - handleSubmit: injectCredentialsFormSubmit, - } = useForm() - const handleGoogleLogin = async (response: any) => { - let targetSubOrgId: string; - const getSuborgsResponse = await axios.post("api/getSuborgs", { - filterType: "OIDC_TOKEN", - filterValue: response.credential, - }); - targetSubOrgId = getSuborgsResponse.data.organizationIds[0]; // If you don't have a 1:1 relationship for suborgs:oauthProviders you will need to manage this yourself in a database - - if (getSuborgsResponse.data.organizationIds.length == 0) { - const createSuborgResponse = await axios.post("api/createSuborg", { - oauthProviders: [ - { providerName: "Google-Test", oidcToken: response.credential }, - ], - }); - targetSubOrgId = createSuborgResponse.data.subOrganizationId; - } - authFormSubmit((data) => auth(data, response.credential, targetSubOrgId))(); - }; - const auth = async ( - data: AuthFormData, - oidcCredential: string, - suborgID: string - ) => { - if (authIframeClient === null) { - throw new Error("cannot initialize auth without an iframe"); - } - const response = await axios.post("/api/auth", { - suborgID, - targetPublicKey: authIframeClient!.iframePublicKey!, - oidcToken: oidcCredential, - invalidateExisting: data.invalidateExisting, + const handleAuthSuccess = async () => { + const whoamiResponse = await authIframeClient!.getWhoami({ + organizationId: "51fb75bf-c044-4f9f-903b-4fba6bfedab9", }); + setOrgData(whoamiResponse as any) - setAuthResponse(response.data); - }; + } - const injectCredentials = async (data: InjectCredentialsFormData) => { - if (authIframeClient === null) { - throw new Error("iframe client is null"); - } - if (authResponse === null) { - throw new Error("authResponse is null"); - } - - try { - await authIframeClient!.injectCredentialBundle( - authResponse.credentialBundle - ); - } catch (e) { - const msg = `error while injecting bundle: ${e}`; - console.error(msg); - alert(msg); - return; - } - - // get whoami for suborg - const whoamiResponse = await authIframeClient!.getWhoami({ - organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, - }); + const [config, setConfig] = useState({ + email: true, + passkey: true, + phone: true, + socials: { + enabled: true, + google: true, + apple: true, + }, + }); - const orgID = whoamiResponse.organizationId; - - const createWalletResponse = await authIframeClient!.createWallet({ - organizationId: orgID, - walletName: data.walletName, - accounts: [ - { - curve: "CURVE_SECP256K1", - pathFormat: "PATH_FORMAT_BIP32", - path: "m/44'/60'/0'/0/0", - addressFormat: "ADDRESS_FORMAT_ETHEREUM", - }, - ], + const toggleConfig = (key: keyof Config) => { + setConfig((prev) => { + const newConfig = { ...prev }; + + if (key === "email") { + newConfig.email = !prev.email; + if (!newConfig.email) { + newConfig.passkey = false; // Ensure passkey is off if email is off + } + } else if (key === "passkey") { + newConfig.passkey = !prev.passkey; + newConfig.email = newConfig.passkey; // Sync email with passkey's state + } else if (key !== "socials") { + newConfig[key] = !prev[key]; + } + + return newConfig; }); + }; + + const toggleSocials = (key: keyof SocialConfig) => { + setConfig((prev) => { + if (key === 'enabled') { + const isEnabled = !prev.socials.enabled; + return { + ...prev, + socials: { + enabled: isEnabled, + google: isEnabled, + apple: isEnabled, + }, + }; + } + + if (prev.socials.enabled) { + return { + ...prev, + socials: { + ...prev.socials, + [key]: !prev.socials[key], + }, + }; + } - const address = refineNonNull(createWalletResponse.addresses[0]); + return prev; + }); + }; - alert(`SUCCESS! Wallet and new address created: ${address} `); + const handleCopyConfig = () => { + const authConfig = { + emailEnabled: config.email, + passkeyEnabled: config.passkey, + phoneEnabled: config.phone, + appleEnabled: config.socials.apple, + googleEnabled: config.socials.google, + }; + navigator.clipboard.writeText(JSON.stringify(authConfig, null, 2)); + alert('Auth config copied to clipboard!'); }; - const getWhoAmI = async () => { - if (authIframeClient) { - try { - const whoamiResponse = await authIframeClient.getWhoami({ - organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, - }); - console.log("WhoAmI Response:", whoamiResponse); - } catch (error) { - console.error("Error fetching WhoAmI:", error); - } - } else { - console.error("Auth iframe client is null"); - } + const authConfig = { + emailEnabled: config.email, + passkeyEnabled: config.passkey, + phoneEnabled: config.phone, + appleEnabled: config.socials.apple, + googleEnabled: config.socials.google, }; + return (
- - Turnkey Logo - - {!authIframeClient &&

Loading...

} - - {authIframeClient && - authIframeClient.iframePublicKey && - authResponse === null && ( -
- - - - - - -
- )} - - {authIframeClient && - authIframeClient.iframePublicKey && - authResponse !== null && ( -
- - - -
- )} - {authIframeClient && ( - - )} -
- ); -} + {!orgData && +
+ Authentication config + +
+
+
+ + Email +
+ toggleConfig('email')} /> +
-function refineNonNull( - input: T | null | undefined, - errorMessage?: string -): T { - if (input == null) { - throw new Error(errorMessage ?? `Unexpected ${JSON.stringify(input)}`); - } +
+
+ + Passkey +
+ toggleConfig('passkey')} /> +
- return input; +
+
+ + Phone +
+ toggleConfig('phone')} /> +
+ +
+
+ + Socials +
+ toggleSocials('enabled')} /> +
+ + {config.socials.enabled && ( + <> +
+
+ Google +
+ toggleSocials('google')} /> +
+
+
+ Apple +
+ toggleSocials('apple')} /> +
+ + )} +
+ +
+ +
+ Copy config +
+
+
+} +{orgData ? + +
+ YOU ARE AUTHENTICATED ON TURNKEY! +
+ Organization Id: {orgData.organizationId} +
+
+ User Id: {orgData.userId} +
+
+ Username: {orgData.username} +
+
+ Organization Name: {orgData.organizationName} +
+
+: +
+ + +
+} + + ); } diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index ca65c0bf2..2dae1b733 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -58,6 +58,7 @@ "@turnkey/wallet-stamper": "workspace:*", "usehooks-ts": "^3.1.0", "@turnkey/sdk-server": "workspace:*", + "react-apple-login": "^1.1.6", "react-hook-form": "^7.45.1", "react-international-phone": "^4.3.0" }, diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index 49218dabe..f8ec5b43f 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -7,7 +7,7 @@ padding: 20px; background: var(--Greyscale-20, #F5F7FB); border: 1px solid var(--Greyscale-100, #EBEDF2); - border-radius: 8px; + border-radius: 15px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } @@ -152,6 +152,23 @@ color: var(--Greyscale-600, #6C727E); cursor: pointer; } /* Auth Button Styles */ + +.authButton { + display: flex; + justify-content: center; + width: 100%; + margin-bottom: 12px; +} + +.appleButtonContainer { + width: 100%; + max-width: 180px; /* Optional max-width */ + height: 40px; /* Adjust height as desired */ + display: flex; + cursor: pointer; + justify-content: center; +} + .google, .facebook, .apple { diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 999f73dbf..8e1958141 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -12,13 +12,21 @@ import { bytesToHex } from "@noble/hashes/utils"; import OTPInput from "./OtpInput"; import { createSuborg } from "../../api/createSuborg"; import { oauth } from "../../api/oauth"; +import AppleLogin from "react-apple-login"; interface AuthProps { turnkeyClient: TurnkeySDKClient; onHandleAuthSuccess: () => Promise; + authConfig: { + emailEnabled: boolean; + passkeyEnabled: boolean; + phoneEnabled: boolean; + appleEnabled: boolean; + googleEnabled: boolean; + }; } -const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess }) => { +const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authConfig }) => { const { turnkey, passkeyClient, authIframeClient } = useTurnkey(); const [loadingAction, setLoadingAction] = useState(null); const [error, setError] = useState(null); @@ -29,17 +37,6 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess }) => { const [step, setStep] = useState("auth"); const [suborgId, setSuborgId] = useState(""); const [resendText, setResendText] = useState("Re-send Code"); - console.log(authIframeClient?.iframePublicKey!) - const authConfig = { - email: true, - passkey: true, - phone: true, - socials: { - google: true, - facebook: true, - apple: true, - }, - }; useEffect(() => { if (error) { @@ -154,8 +151,6 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess }) => { const handleEnterOtp = async (otp: string) => { setLoadingAction("otp"); - console.log(`Verifying OTP: ${otp} with otpId: ${otpId}`); - // Add OTP verification logic here const authRequest = { suborgID: suborgId, otpId: otpId!, @@ -199,17 +194,51 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess }) => { targetPublicKey: authIframeClient?.iframePublicKey!, }; const oauthResponse = await oauth(oauthRequest, turnkeyClient); - console.log(response.credential) - console.log(oauthResponse) setSuborgId(suborgId); if (oauthResponse!.credentialBundle) { - console.log(oauthResponse!.credentialBundle) await authIframeClient!.injectCredentialBundle(oauthResponse!.credentialBundle); await onHandleAuthSuccess(); } setLoadingAction(null); }; + const handleAppleLogin = async (response: any) => { + setLoadingAction("oauth"); + // Implement Apple OAuth with the response received from react-apple-login + const appleToken = response.authorization?.id_token; + + if (appleToken) { + const getSuborgsRequest = { + filterType: "OIDC_TOKEN", + filterValue: appleToken, + }; + const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); + let suborgId = getSuborgsResponse!.organizationIds[0]! + if (!suborgId){ + const createSuborgRequest = { + oauthProviders : [{ + providerName: "Apple OIDC", + oidcToken: response.credential + }] + }; + const createSuborgResponse = await createSuborg(createSuborgRequest, turnkeyClient); + suborgId = createSuborgResponse?.subOrganizationId! + } + + const oauthResponse = await oauth({ + suborgID: suborgId, + oidcToken: appleToken, + targetPublicKey: authIframeClient?.iframePublicKey!, + }, turnkeyClient); + + if (oauthResponse!.credentialBundle) { + await authIframeClient!.injectCredentialBundle(oauthResponse!.credentialBundle); + await onHandleAuthSuccess(); + } + } + setLoadingAction(null); + }; + const handleResendCode = async () => { setOtpError(null); if (step === "otpEmail") { @@ -224,7 +253,7 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess }) => {

{otpId ? "Enter verification code" : "Log in or sign up"}

- {authConfig.email && !otpId && ( + {authConfig.emailEnabled && !otpId && (
= ({ turnkeyClient, onHandleAuthSuccess }) => {
)} - {authConfig.passkey && !otpId && ( + {authConfig.passkeyEnabled && !otpId && ( +
+
)} - - {authConfig.phone && !otpId && ( +{!otpId && (authConfig.passkeyEnabled || authConfig.emailEnabled) && (authConfig.googleEnabled || authConfig.appleEnabled || authConfig.phoneEnabled) && +
+ OR +
+} + {authConfig.phoneEnabled && !otpId && (
-
- OR -
setPhone(value)} value={phone} />
@@ -302,22 +334,50 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess }) => {
{otpError}
)}
+ {!otpId && (authConfig.googleEnabled || authConfig.appleEnabled) && authConfig.phoneEnabled && - - {!otpId && authConfig.socials.google && authIframeClient && ( -
-
+
OR
- - - -
- )} +} +{!otpId && authConfig.googleEnabled && authIframeClient && ( +
+
+ + + +
+
+)} + + +{!otpId && authConfig.appleEnabled && authIframeClient && ( +
+
+ { + if (response.error) { + console.error("Apple login error:", response.error); + } else { + handleAppleLogin(response); + } + }} + usePopup + /> +
+
+)} + +
{!otpId ? ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86411ea08..e4caf8514 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -461,6 +461,69 @@ importers: specifier: 5.1.3 version: 5.1.3 + examples/react-components: + dependencies: + '@emotion/react': + specifier: ^11.13.3 + version: 11.13.3(@types/react@18.2.14)(react@18.2.0) + '@emotion/styled': + specifier: ^11.13.0 + version: 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.14)(react@18.2.0) + '@mui/icons-material': + specifier: ^6.1.5 + version: 6.1.5(@mui/material@6.1.5)(@types/react@18.2.14)(react@18.2.0) + '@mui/material': + specifier: ^6.1.5 + version: 6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + '@turnkey/sdk-react': + specifier: workspace:* + version: link:../../packages/sdk-react + '@turnkey/sdk-server': + specifier: workspace:* + version: link:../../packages/sdk-server + '@types/node': + specifier: 20.3.1 + version: 20.3.1 + '@types/react': + specifier: 18.2.14 + version: 18.2.14 + '@types/react-dom': + specifier: 18.2.6 + version: 18.2.6 + axios: + specifier: ^1.7.4 + version: 1.7.4 + encoding: + specifier: ^0.1.13 + version: 0.1.13 + eslint: + specifier: 8.43.0 + version: 8.43.0 + eslint-config-next: + specifier: 14.2.10 + version: 14.2.10(eslint@8.43.0)(typescript@5.1.3) + esm: + specifier: ^3.2.25 + version: 3.2.25 + install: + specifier: ^0.13.0 + version: 0.13.0 + next: + specifier: ^14.2.10 + version: 14.2.10(@babel/core@7.24.5)(react-dom@18.2.0)(react@18.2.0) + npm: + specifier: ^9.7.2 + version: 9.7.2 + react: + specifier: 18.2.0 + version: 18.2.0 + react-dom: + specifier: 18.2.0 + version: 18.2.0(react@18.2.0) + typescript: + specifier: 5.1.3 + version: 5.1.3 + examples/rebalancer: dependencies: '@turnkey/ethers': @@ -1747,6 +1810,9 @@ importers: '@turnkey/wallet-stamper': specifier: workspace:* version: link:../wallet-stamper + react-apple-login: + specifier: ^1.1.6 + version: 1.1.6(prop-types@15.8.1)(react-dom@18.3.1)(react@18.2.0) react-hook-form: specifier: ^7.45.1 version: 7.45.1(react@18.2.0) @@ -5180,6 +5246,29 @@ packages: resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} dev: false + /@emotion/react@11.13.3(@types/react@18.2.14)(react@18.2.0): + resolution: {integrity: sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/babel-plugin': 11.12.0 + '@emotion/cache': 11.13.1 + '@emotion/serialize': 1.3.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.2.0) + '@emotion/utils': 1.4.1 + '@emotion/weak-memoize': 0.4.0 + '@types/react': 18.2.14 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + transitivePeerDependencies: + - supports-color + dev: false + /@emotion/react@11.13.3(@types/react@18.2.75)(react@18.2.0): resolution: {integrity: sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==} peerDependencies: @@ -5217,6 +5306,29 @@ packages: resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} dev: false + /@emotion/styled@11.13.0(@emotion/react@11.13.3)(@types/react@18.2.14)(react@18.2.0): + resolution: {integrity: sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/babel-plugin': 11.12.0 + '@emotion/is-prop-valid': 1.3.1 + '@emotion/react': 11.13.3(@types/react@18.2.14)(react@18.2.0) + '@emotion/serialize': 1.3.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.1.0(react@18.2.0) + '@emotion/utils': 1.4.1 + '@types/react': 18.2.14 + react: 18.2.0 + transitivePeerDependencies: + - supports-color + dev: false + /@emotion/styled@11.13.0(@emotion/react@11.13.3)(@types/react@18.2.75)(react@18.2.0): resolution: {integrity: sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==} peerDependencies: @@ -6703,6 +6815,23 @@ packages: resolution: {integrity: sha512-3J96098GrC95XsLw/TpGNMxhUOnoG9NZ/17Pfk1CrJj+4rcuolsF2RdF3XAFTu/3a/A+5ouxlSIykzYz6Ee87g==} dev: false + /@mui/icons-material@6.1.5(@mui/material@6.1.5)(@types/react@18.2.14)(react@18.2.0): + resolution: {integrity: sha512-SbxFtO5I4cXfvhjAMgGib/t2lQUzcEzcDFYiRHRufZUeMMeXuoKaGsptfwAHTepYkv0VqcCwvxtvtWbpZLAbjQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@mui/material': ^6.1.5 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@mui/material': 6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.14 + react: 18.2.0 + dev: false + /@mui/icons-material@6.1.5(@mui/material@6.1.5)(@types/react@18.2.75)(react@18.2.0): resolution: {integrity: sha512-SbxFtO5I4cXfvhjAMgGib/t2lQUzcEzcDFYiRHRufZUeMMeXuoKaGsptfwAHTepYkv0VqcCwvxtvtWbpZLAbjQ==} engines: {node: '>=14.0.0'} @@ -6720,6 +6849,45 @@ packages: react: 18.2.0 dev: false + /@mui/material@6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-rhaxC7LnlOG8zIVYv7BycNbWkC5dlm9A/tcDUp0CuwA7Zf9B9JP6M3rr50cNKxI7Z0GIUesAT86ceVm44quwnQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@mui/material-pigment-css': ^6.1.5 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@mui/material-pigment-css': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/react': 11.13.3(@types/react@18.2.14)(react@18.2.0) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.14)(react@18.2.0) + '@mui/core-downloads-tracker': 6.1.5 + '@mui/system': 6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.14)(react@18.2.0) + '@mui/types': 7.2.18(@types/react@18.2.14) + '@mui/utils': 6.1.5(@types/react@18.2.14)(react@18.2.0) + '@popperjs/core': 2.11.8 + '@types/react': 18.2.14 + '@types/react-transition-group': 4.4.11 + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.3.1 + react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + dev: false + /@mui/material@6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.75)(react-dom@18.3.1)(react@18.2.0): resolution: {integrity: sha512-rhaxC7LnlOG8zIVYv7BycNbWkC5dlm9A/tcDUp0CuwA7Zf9B9JP6M3rr50cNKxI7Z0GIUesAT86ceVm44quwnQ==} engines: {node: '>=14.0.0'} @@ -6759,6 +6927,23 @@ packages: react-transition-group: 4.4.5(react-dom@18.3.1)(react@18.2.0) dev: false + /@mui/private-theming@6.1.5(@types/react@18.2.14)(react@18.2.0): + resolution: {integrity: sha512-FJqweqEXk0KdtTho9C2h6JEKXsOT7MAVH2Uj3N5oIqs6YKxnwBn2/zL2QuYYEtj5OJ87rEUnCfFic6ldClvzJw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@mui/utils': 6.1.5(@types/react@18.2.14)(react@18.2.0) + '@types/react': 18.2.14 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + /@mui/private-theming@6.1.5(@types/react@18.2.75)(react@18.2.0): resolution: {integrity: sha512-FJqweqEXk0KdtTho9C2h6JEKXsOT7MAVH2Uj3N5oIqs6YKxnwBn2/zL2QuYYEtj5OJ87rEUnCfFic6ldClvzJw==} engines: {node: '>=14.0.0'} @@ -6791,10 +6976,40 @@ packages: dependencies: '@babel/runtime': 7.26.0 '@emotion/cache': 11.13.1 - '@emotion/react': 11.13.3(@types/react@18.2.75)(react@18.2.0) + '@emotion/react': 11.13.3(@types/react@18.2.14)(react@18.2.0) '@emotion/serialize': 1.3.2 '@emotion/sheet': 1.4.0 - '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.75)(react@18.2.0) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.14)(react@18.2.0) + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/system@6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.14)(react@18.2.0): + resolution: {integrity: sha512-vPM9ocQ8qquRDByTG3XF/wfYTL7IWL/20EiiKqByLDps8wOmbrDG9rVznSE3ZbcjFCFfMRMhtxvN92bwe/63SA==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/react': 11.13.3(@types/react@18.2.14)(react@18.2.0) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.14)(react@18.2.0) + '@mui/private-theming': 6.1.5(@types/react@18.2.14)(react@18.2.0) + '@mui/styled-engine': 6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(react@18.2.0) + '@mui/types': 7.2.18(@types/react@18.2.14) + '@mui/utils': 6.1.5(@types/react@18.2.14)(react@18.2.0) + '@types/react': 18.2.14 + clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 react: 18.2.0 @@ -6830,6 +7045,17 @@ packages: react: 18.2.0 dev: false + /@mui/types@7.2.18(@types/react@18.2.14): + resolution: {integrity: sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.14 + dev: false + /@mui/types@7.2.18(@types/react@18.2.75): resolution: {integrity: sha512-uvK9dWeyCJl/3ocVnTOS6nlji/Knj8/tVqVX03UVTpdmTJYu/s4jtDd9Kvv0nRGE0CUSNW1UYAci7PYypjealg==} peerDependencies: @@ -6841,6 +7067,26 @@ packages: '@types/react': 18.2.75 dev: false + /@mui/utils@6.1.5(@types/react@18.2.14)(react@18.2.0): + resolution: {integrity: sha512-vp2WfNDY+IbKUIGg+eqX1Ry4t/BilMjzp6p9xO1rfqpYjH1mj8coQxxDfKxcQLzBQkmBJjymjoGOak5VUYwXug==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.26.0 + '@mui/types': 7.2.18(@types/react@18.2.14) + '@types/prop-types': 15.7.13 + '@types/react': 18.2.14 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.2.0 + react-is: 18.3.1 + dev: false + /@mui/utils@6.1.5(@types/react@18.2.75)(react@18.2.0): resolution: {integrity: sha512-vp2WfNDY+IbKUIGg+eqX1Ry4t/BilMjzp6p9xO1rfqpYjH1mj8coQxxDfKxcQLzBQkmBJjymjoGOak5VUYwXug==} engines: {node: '>=14.0.0'} @@ -21458,6 +21704,19 @@ packages: iconv-lite: 0.4.24 unpipe: 1.0.0 + /react-apple-login@1.1.6(prop-types@15.8.1)(react-dom@18.3.1)(react@18.2.0): + resolution: {integrity: sha512-ySV6ax0aB+ksA7lKzhr4MvsgjwSH068VtdHJXS+7rL380IJnNQNl14SszR31k3UqB8q8C1H1oyjJFGq4MyO6tw==} + engines: {node: '>=8', npm: '>=5'} + peerDependencies: + prop-types: ^15.5.4 + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.3.1(react@18.2.0) + dev: false + /react-devtools-core@5.1.0: resolution: {integrity: sha512-NRtLBqYVLrIY+lOa2oTpFiAhI7Hru0AUXI0tP9neCyaPPAzlZyeH0i+VZ0shIyRTJbpvyqbD/uCsewA2hpfZHw==} dependencies: @@ -21757,6 +22016,20 @@ packages: tslib: 2.8.1 dev: false + /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + dependencies: + '@babel/runtime': 7.26.0 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-transition-group@4.4.5(react-dom@18.3.1)(react@18.2.0): resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: From 1006fd97cd746fb230a28919f6d02ccc8d6be231 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Mon, 4 Nov 2024 10:36:54 -0500 Subject: [PATCH 10/73] separate out buttons --- packages/sdk-react/package.json | 1 + .../sdk-react/src/components/auth/Apple.tsx | 29 ++++++ .../sdk-react/src/components/auth/Auth.tsx | 89 +++++++++---------- .../sdk-react/src/components/auth/Google.tsx | 25 ++++++ pnpm-lock.yaml | 22 +++++ 5 files changed, 119 insertions(+), 47 deletions(-) create mode 100644 packages/sdk-react/src/components/auth/Apple.tsx create mode 100644 packages/sdk-react/src/components/auth/Google.tsx diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index 2dae1b733..33060d402 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -50,6 +50,7 @@ "dependencies": { "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", + "@greatsumini/react-facebook-login": "^3.3.3", "@mui/icons-material": "^6.1.5", "@mui/material": "^6.1.5", "@noble/hashes": "1.4.0", diff --git a/packages/sdk-react/src/components/auth/Apple.tsx b/packages/sdk-react/src/components/auth/Apple.tsx new file mode 100644 index 000000000..631920d32 --- /dev/null +++ b/packages/sdk-react/src/components/auth/Apple.tsx @@ -0,0 +1,29 @@ +import AppleLogin from "react-apple-login"; + +interface AppleAuthButtonProps { + onSuccess: (response: any) => void; +} + +const AppleAuthButton: React.FC = ({ onSuccess }) => { + return ( + { + if (response.error) { + console.error("Apple login error:", response.error); + } else { + onSuccess(response); + } + }} + usePopup + designProp={{ + type: "continue", + color: "white", + }} + /> + ); +}; + +export default AppleAuthButton; diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 8e1958141..4b422e2e6 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -13,7 +13,8 @@ import OTPInput from "./OtpInput"; import { createSuborg } from "../../api/createSuborg"; import { oauth } from "../../api/oauth"; import AppleLogin from "react-apple-login"; - +import GoogleAuthButton from "./Google"; +import AppleAuthButton from "./Apple"; interface AuthProps { turnkeyClient: TurnkeySDKClient; onHandleAuthSuccess: () => Promise; @@ -43,7 +44,6 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon alert(error); } }, [error]); - const handleLoginWithPasskey = async () => { setLoadingAction("email"); const getSuborgsRequest = { @@ -343,15 +343,8 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon {!otpId && authConfig.googleEnabled && authIframeClient && (
- - - + +
)} @@ -360,48 +353,50 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon {!otpId && authConfig.appleEnabled && authIframeClient && (
- { - if (response.error) { - console.error("Apple login error:", response.error); - } else { - handleAppleLogin(response); - } - }} - usePopup - /> +
)} +
+ {!otpId ? ( + + By logging in you agree to our{" "} + + Terms of Service + {" "} + &{" "} + + Privacy Policy + + + ) : ( + + Did not receive your code?{" "} + + {resendText} + + + )} +
-
- {!otpId ? ( - - By logging in you agree to our{" "} - Terms of Service &{" "} - Privacy Policy - - ) : ( - - Did not receive your code?{" "} - - {resendText} - - - )} -
-
Powered by diff --git a/packages/sdk-react/src/components/auth/Google.tsx b/packages/sdk-react/src/components/auth/Google.tsx new file mode 100644 index 000000000..e51ceecb7 --- /dev/null +++ b/packages/sdk-react/src/components/auth/Google.tsx @@ -0,0 +1,25 @@ +import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google"; +import { sha256 } from "@noble/hashes/sha2"; +import { bytesToHex } from "@noble/hashes/utils"; + +interface GoogleAuthButtonProps { + iframePublicKey: string; + onSuccess: (response: any) => void; +} + +const GoogleAuthButton: React.FC = ({ iframePublicKey, onSuccess }) => { + return ( + + + + ); +}; + +export default GoogleAuthButton; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4caf8514..ff2a0aecd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1789,6 +1789,9 @@ importers: '@emotion/styled': specifier: ^11.13.0 version: 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.75)(react@18.2.0) + '@greatsumini/react-facebook-login': + specifier: ^3.3.3 + version: 3.3.3(react@18.2.0) '@mui/icons-material': specifier: ^6.1.5 version: 6.1.5(@mui/material@6.1.5)(@types/react@18.2.75)(react@18.2.0) @@ -1813,6 +1816,9 @@ importers: react-apple-login: specifier: ^1.1.6 version: 1.1.6(prop-types@15.8.1)(react-dom@18.3.1)(react@18.2.0) + react-facebook-login: + specifier: ^4.1.1 + version: 4.1.1(react@18.2.0) react-hook-form: specifier: ^7.45.1 version: 7.45.1(react@18.2.0) @@ -6057,6 +6063,14 @@ packages: semver: 7.5.4 dev: false + /@greatsumini/react-facebook-login@3.3.3(react@18.2.0): + resolution: {integrity: sha512-Y5D7EncR3iy/X/OfWwjjpM5OW0XV6PCE08RZUV/yhAE413PEBIlML7S6z69BcpUPWO5XzEt7cytHChUdwXO4Dw==} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /@hapi/hoek@9.3.0: resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} dev: false @@ -21769,6 +21783,14 @@ packages: scheduler: 0.23.2 dev: false + /react-facebook-login@4.1.1(react@18.2.0): + resolution: {integrity: sha512-COnHEHlYGTKipz4963safFAK9PaNTcCiXfPXMS/yxo8El+/AJL5ye8kMJf23lKSSGGPgqFQuInskIHVqGqTvSw==} + peerDependencies: + react: ^16.0.0 + dependencies: + react: 18.2.0 + dev: false + /react-hook-form@7.45.1(react@18.2.0): resolution: {integrity: sha512-6dWoFJwycbuFfw/iKMcl+RdAOAOHDiF11KWYhNDRN/OkUt+Di5qsZHwA0OwsVnu9y135gkHpTw9DJA+WzCeR9w==} engines: {node: '>=12.22.0'} From e35e19b30a76551b01c64a5f82c6f0fd44870899 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Mon, 4 Nov 2024 12:15:16 -0500 Subject: [PATCH 11/73] cleaned up code --- .../sdk-react/src/components/auth/Auth.tsx | 288 +++++++----------- 1 file changed, 104 insertions(+), 184 deletions(-) diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 4b422e2e6..b93000549 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -6,15 +6,12 @@ import { initOtpAuth } from "../../api/initOtpAuth"; import { otpAuth } from "../../api/otpAuth"; import { getSuborgs } from "../../api/getSuborgs"; import { MuiPhone } from "./PhoneInput"; -import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google"; -import { sha256 } from "@noble/hashes/sha2"; -import { bytesToHex } from "@noble/hashes/utils"; import OTPInput from "./OtpInput"; import { createSuborg } from "../../api/createSuborg"; import { oauth } from "../../api/oauth"; -import AppleLogin from "react-apple-login"; import GoogleAuthButton from "./Google"; import AppleAuthButton from "./Apple"; + interface AuthProps { turnkeyClient: TurnkeySDKClient; onHandleAuthSuccess: () => Promise; @@ -28,8 +25,7 @@ interface AuthProps { } const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authConfig }) => { - const { turnkey, passkeyClient, authIframeClient } = useTurnkey(); - const [loadingAction, setLoadingAction] = useState(null); + const { passkeyClient, authIframeClient } = useTurnkey(); const [error, setError] = useState(null); const [otpError, setOtpError] = useState(null); const [email, setEmail] = useState(""); @@ -44,207 +40,129 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon alert(error); } }, [error]); - const handleLoginWithPasskey = async () => { - setLoadingAction("email"); - const getSuborgsRequest = { - filterType: "EMAIL", - filterValue: email, - }; - const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); - if (getSuborgsResponse!.organizationIds.length > 0){ - const createReadWriteSessionResponse = await passkeyClient?.createReadWriteSession({organizationId: getSuborgsResponse!.organizationIds[0]!, targetPublicKey: authIframeClient?.iframePublicKey!}) - if (createReadWriteSessionResponse!.credentialBundle) { - await authIframeClient!.injectCredentialBundle(createReadWriteSessionResponse!.credentialBundle); - await onHandleAuthSuccess(); - } - } - else { - // User either does not have an account with a sub organization - // or does not have a passkey - // Create a new passkey for the user - const { encodedChallenge, attestation } = - (await passkeyClient?.createUserPasskey({ - publicKey: { - user: { - name: email, - displayName: email, - }, - }, - })) || {} - // Create a new sub organization for the user - if (encodedChallenge && attestation) { - const createSuborgRequest = { - email, - passkey: { - authenticatorName: "First Passkey", - challenge: encodedChallenge, - attestation + const isValidEmail = (email: string) => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + }; + + const isValidPhone = (phone: string) => { + const usCanadaRegex = /^\+1\d{10}$/; + return usCanadaRegex.test(phone); + }; + - } - }; - const createSuborgResponse = await createSuborg(createSuborgRequest, turnkeyClient); - if (createSuborgResponse?.subOrganizationId) { - const createReadWriteSessionResponse = await passkeyClient?.createReadWriteSession({organizationId: createSuborgResponse?.subOrganizationId, targetPublicKey: authIframeClient?.iframePublicKey!}) - if (createReadWriteSessionResponse!.credentialBundle) { - await authIframeClient!.injectCredentialBundle(createReadWriteSessionResponse!.credentialBundle); - await onHandleAuthSuccess(); - } - } - } - } + const handleGetOrCreateSuborg = async (filterType: string, filterValue: string, additionalData = {}) => { + const getSuborgsResponse = await getSuborgs({ filterType, filterValue }, turnkeyClient); + let suborgId = getSuborgsResponse!.organizationIds[0]; + if (!suborgId) { + const createSuborgResponse = await createSuborg({ [filterType.toLowerCase()]: filterValue, ...additionalData }, turnkeyClient); + suborgId = createSuborgResponse?.subOrganizationId!; } + return suborgId; + }; - const handleEmailLogin = async () => { - setLoadingAction("email"); - const getSuborgsRequest = { - filterType: "EMAIL", - filterValue: email, - }; - const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); - let suborgId = getSuborgsResponse!.organizationIds[0]! - if (!suborgId){ - const createSuborgRequest = { - email, - }; - const createSuborgResponse = await createSuborg(createSuborgRequest, turnkeyClient); - suborgId = createSuborgResponse?.subOrganizationId! + const handleAuthSuccess = async (credentialBundle: any) => { + if (credentialBundle) { + await authIframeClient!.injectCredentialBundle(credentialBundle); + await onHandleAuthSuccess(); } - const initAuthRequest = { - suborgID: suborgId, - otpType: "OTP_TYPE_EMAIL", - contact: email, - }; - const initAuthResponse = await initOtpAuth(initAuthRequest, turnkeyClient); - setSuborgId(suborgId); - setOtpId(initAuthResponse!.otpId); - setStep("otpEmail"); - setLoadingAction(null); }; - const handlePhoneLogin = async () => { - setLoadingAction("phone"); - const getSuborgsRequest = { - filterType: "PHONE_NUMBER", - filterValue: phone, - }; - const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); - let suborgId = getSuborgsResponse!.organizationIds[0]! - if (!suborgId){ - const createSuborgRequest = { - phoneNumber: phone, - }; - const createSuborgResponse = await createSuborg(createSuborgRequest, turnkeyClient); - suborgId = createSuborgResponse?.subOrganizationId! + const handleLoginWithPasskey = async () => { + + // Step 1: Try to retrieve the suborg by email + const getSuborgsResponse = await getSuborgs({ filterType: "EMAIL", filterValue: email }, turnkeyClient); + const existingSuborgId = getSuborgsResponse!.organizationIds[0]; + + if (existingSuborgId) { + // If a suborg exists, use it to create a read/write session without a new passkey + const sessionResponse = await passkeyClient?.createReadWriteSession({ + organizationId: existingSuborgId, + targetPublicKey: authIframeClient?.iframePublicKey!, + }); + + if (sessionResponse?.credentialBundle) { + await handleAuthSuccess(sessionResponse.credentialBundle); + } else { + setError("Failed to complete passkey login."); + } + } else { + // If no suborg exists, first create a user passkey + const { encodedChallenge, attestation } = await passkeyClient?.createUserPasskey({ + publicKey: { user: { name: email, displayName: email } }, + }) || {}; + + if (encodedChallenge && attestation) { + // Use the generated passkey to create a new suborg + const createSuborgResponse = await createSuborg({ + email, + passkey: { authenticatorName: "First Passkey", challenge: encodedChallenge, attestation }, + }, turnkeyClient); + + const newSuborgId = createSuborgResponse?.subOrganizationId; + + if (newSuborgId) { + // With the new suborg, create a read/write session + const newSessionResponse = await passkeyClient?.createReadWriteSession({ + organizationId: newSuborgId, + targetPublicKey: authIframeClient?.iframePublicKey!, + }); + + if (newSessionResponse?.credentialBundle) { + await handleAuthSuccess(newSessionResponse.credentialBundle); + } else { + setError("Failed to complete passkey login with new suborg."); + } + } else { + setError("Failed to create suborg with passkey."); + } + } else { + setError("Failed to create user passkey."); + } } - const initAuthRequest = { - suborgID: suborgId, - otpType: "OTP_TYPE_SMS", - contact: phone, - }; - const initAuthResponse = await initOtpAuth(initAuthRequest, turnkeyClient); + }; + + const handleOtpLogin = async (type: "EMAIL" | "PHONE_NUMBER", value: string, otpType: string) => { + const suborgId = await handleGetOrCreateSuborg(type, value); + const initAuthResponse = await initOtpAuth({ suborgID: suborgId, otpType, contact: value }, turnkeyClient); setSuborgId(suborgId); setOtpId(initAuthResponse!.otpId); - setStep("otpPhone"); - setLoadingAction(null); + setStep(type === "EMAIL" ? "otpEmail" : "otpPhone"); }; const handleEnterOtp = async (otp: string) => { - setLoadingAction("otp"); - const authRequest = { - suborgID: suborgId, - otpId: otpId!, - otpCode: otp, - targetPublicKey: authIframeClient!.iframePublicKey!, - }; - - const authResponse = await otpAuth(authRequest, turnkeyClient); - if (authResponse?.credentialBundle) { - await authIframeClient!.injectCredentialBundle(authResponse.credentialBundle); - await onHandleAuthSuccess(); - setOtpError(null); - } else { - setOtpError("Invalid code, please try again"); - } - setLoadingAction(null); + const authResponse = await otpAuth( + { suborgID: suborgId, otpId: otpId!, otpCode: otp, targetPublicKey: authIframeClient!.iframePublicKey! }, + turnkeyClient + ); + authResponse?.credentialBundle ? await handleAuthSuccess(authResponse.credentialBundle) : setOtpError("Invalid code, please try again"); }; const handleGoogleLogin = async (response: any) => { - setLoadingAction("oauth"); - - const getSuborgsRequest = { - filterType: "OIDC_TOKEN", - filterValue: response.credential, - }; - const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); - let suborgId = getSuborgsResponse!.organizationIds[0]! - if (!suborgId){ - const createSuborgRequest = { - oauthProviders : [{ - providerName: "Google OIDC", - oidcToken: response.credential - }] - }; - const createSuborgResponse = await createSuborg(createSuborgRequest, turnkeyClient); - suborgId = createSuborgResponse?.subOrganizationId! - } - const oauthRequest = { - suborgID: suborgId, - oidcToken: response.credential, - targetPublicKey: authIframeClient?.iframePublicKey!, - }; - const oauthResponse = await oauth(oauthRequest, turnkeyClient); - setSuborgId(suborgId); - if (oauthResponse!.credentialBundle) { - await authIframeClient!.injectCredentialBundle(oauthResponse!.credentialBundle); - await onHandleAuthSuccess(); - } - setLoadingAction(null); + const credential = response.credential; + await handleOAuthLogin(credential, "Google OIDC"); }; const handleAppleLogin = async (response: any) => { - setLoadingAction("oauth"); - // Implement Apple OAuth with the response received from react-apple-login const appleToken = response.authorization?.id_token; - if (appleToken) { - const getSuborgsRequest = { - filterType: "OIDC_TOKEN", - filterValue: appleToken, - }; - const getSuborgsResponse = await getSuborgs(getSuborgsRequest, turnkeyClient); - let suborgId = getSuborgsResponse!.organizationIds[0]! - if (!suborgId){ - const createSuborgRequest = { - oauthProviders : [{ - providerName: "Apple OIDC", - oidcToken: response.credential - }] - }; - const createSuborgResponse = await createSuborg(createSuborgRequest, turnkeyClient); - suborgId = createSuborgResponse?.subOrganizationId! - } - - const oauthResponse = await oauth({ - suborgID: suborgId, - oidcToken: appleToken, - targetPublicKey: authIframeClient?.iframePublicKey!, - }, turnkeyClient); - - if (oauthResponse!.credentialBundle) { - await authIframeClient!.injectCredentialBundle(oauthResponse!.credentialBundle); - await onHandleAuthSuccess(); - } + await handleOAuthLogin(appleToken, "Apple OIDC"); } - setLoadingAction(null); + }; + + const handleOAuthLogin = async (credential: string, providerName: string) => { + const suborgId = await handleGetOrCreateSuborg("OIDC_TOKEN", credential, { oauthProviders: [{ providerName, oidcToken: credential }] }); + const oauthResponse = await oauth({ suborgID: suborgId, oidcToken: credential, targetPublicKey: authIframeClient?.iframePublicKey! }, turnkeyClient); + await handleAuthSuccess(oauthResponse!.credentialBundle); }; const handleResendCode = async () => { setOtpError(null); if (step === "otpEmail") { - await handleEmailLogin(); + await handleOtpLogin("EMAIL", email, "OTP_TYPE_EMAIL"); } else if (step === "otpPhone") { - await handlePhoneLogin(); + await handleOtpLogin("PHONE_NUMBER", phone, "OTP_TYPE_SMS"); } setResendText("Code Sent ✓"); }; @@ -265,8 +183,8 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon
@@ -279,7 +197,7 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon type="button" className={styles.passkeyButton} onClick={handleLoginWithPasskey} - disabled={loadingAction === "passkey"} + disabled={!isValidEmail(email)} > Continue with passkey @@ -297,8 +215,8 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon
@@ -419,3 +337,5 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon }; export default Auth; + + From 627ea14473f28852ef1aa1d2318350d38f01780f Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Mon, 4 Nov 2024 13:07:04 -0500 Subject: [PATCH 12/73] updates to google and apple auth buttons --- packages/sdk-react/src/components/auth/Apple.tsx | 13 ++++++++++--- packages/sdk-react/src/components/auth/Auth.tsx | 4 ++-- packages/sdk-react/src/components/auth/Google.tsx | 5 +++-- pnpm-lock.yaml | 11 ----------- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/sdk-react/src/components/auth/Apple.tsx b/packages/sdk-react/src/components/auth/Apple.tsx index 631920d32..d8c337bd8 100644 --- a/packages/sdk-react/src/components/auth/Apple.tsx +++ b/packages/sdk-react/src/components/auth/Apple.tsx @@ -1,15 +1,22 @@ +import { sha256 } from "@noble/hashes/sha2"; +import { bytesToHex } from "@noble/hashes/utils"; import AppleLogin from "react-apple-login"; interface AppleAuthButtonProps { + iframePublicKey: string; + clientId: string; + redirectURI: string; onSuccess: (response: any) => void; } -const AppleAuthButton: React.FC = ({ onSuccess }) => { +const AppleAuthButton: React.FC = ({ iframePublicKey, onSuccess, clientId, redirectURI }) => { return ( { if (response.error) { console.error("Apple login error:", response.error); diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index b93000549..745348ee9 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -262,7 +262,7 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon
- +
)} @@ -271,7 +271,7 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon {!otpId && authConfig.appleEnabled && authIframeClient && (
- +
)} diff --git a/packages/sdk-react/src/components/auth/Google.tsx b/packages/sdk-react/src/components/auth/Google.tsx index e51ceecb7..7fe90f60d 100644 --- a/packages/sdk-react/src/components/auth/Google.tsx +++ b/packages/sdk-react/src/components/auth/Google.tsx @@ -4,12 +4,13 @@ import { bytesToHex } from "@noble/hashes/utils"; interface GoogleAuthButtonProps { iframePublicKey: string; + clientId: string; onSuccess: (response: any) => void; } -const GoogleAuthButton: React.FC = ({ iframePublicKey, onSuccess }) => { +const GoogleAuthButton: React.FC = ({ iframePublicKey, onSuccess, clientId}) => { return ( - + =12.22.0'} From f1c75f076329cf7c2a084176bf93d4c18d33c9c1 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Mon, 4 Nov 2024 19:05:53 -0500 Subject: [PATCH 13/73] more updates --- examples/oauth/.env.local.example | 2 +- .../src/{pages => app}/index.module.css | 0 .../src/{pages/_app.tsx => app/layout.tsx} | 21 +++++++----- .../src/{pages/index.tsx => app/page.tsx} | 33 ++++++++++++------- .../src/{api => actions}/createSuborg.ts | 0 .../src/{api => actions}/getSuborgs.ts | 5 ++- .../src/{api => actions}/initOtpAuth.ts | 2 +- .../sdk-react/src/{api => actions}/oauth.ts | 0 .../sdk-react/src/{api => actions}/otpAuth.ts | 2 +- 9 files changed, 42 insertions(+), 23 deletions(-) rename examples/react-components/src/{pages => app}/index.module.css (100%) rename examples/react-components/src/{pages/_app.tsx => app/layout.tsx} (62%) rename examples/react-components/src/{pages/index.tsx => app/page.tsx} (90%) rename packages/sdk-react/src/{api => actions}/createSuborg.ts (100%) rename packages/sdk-react/src/{api => actions}/getSuborgs.ts (92%) rename packages/sdk-react/src/{api => actions}/initOtpAuth.ts (90%) rename packages/sdk-react/src/{api => actions}/oauth.ts (100%) rename packages/sdk-react/src/{api => actions}/otpAuth.ts (92%) diff --git a/examples/oauth/.env.local.example b/examples/oauth/.env.local.example index b24b0e2d3..9d4344fd4 100644 --- a/examples/oauth/.env.local.example +++ b/examples/oauth/.env.local.example @@ -1,7 +1,7 @@ API_PUBLIC_KEY="" API_PRIVATE_KEY="" NEXT_PUBLIC_ORGANIZATION_ID="" -NEXT_PUBLIC_GOOGLE_CLIENT_ID=" +
- - - - + {children}
+ + ); } -export default Oauth; +export default RootLayout; diff --git a/examples/react-components/src/pages/index.tsx b/examples/react-components/src/app/page.tsx similarity index 90% rename from examples/react-components/src/pages/index.tsx rename to examples/react-components/src/app/page.tsx index 308ad7d77..2a22ff5ef 100644 --- a/examples/react-components/src/pages/index.tsx +++ b/examples/react-components/src/app/page.tsx @@ -1,7 +1,9 @@ +"use client" + import styles from "./index.module.css"; import * as React from "react"; import { useState } from "react"; -import { useTurnkey, Auth } from "@turnkey/sdk-react"; +import { useTurnkey, Auth, AuthServerWrapper } from "@turnkey/sdk-react"; import { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; import { Switch, Typography, IconButton } from '@mui/material'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; @@ -21,17 +23,19 @@ interface Config { socials: SocialConfig; } -export default function AuthPage() { +interface AuthPageProps { + turnkeyClientConfig: { + apiBaseUrl: string; + defaultOrganizationId: string; + apiPublicKey: string; + apiPrivateKey: string; + }; +} + +export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { const { authIframeClient } = useTurnkey(); const [orgData, setOrgData] = useState(); - - const turnkeyClient = new TurnkeySDKClient({ - apiBaseUrl: "https://api.preprod.turnkey.engineering", - apiPublicKey: "03352f813eadd3efa85a2dad1a0c39391e5544de074665f3986185c92566b39885", - apiPrivateKey: "954aa8fdef994a555952f1ee1ea4fb554cb422e5fc896939412a954c4e87c603", - defaultOrganizationId: "51fb75bf-c044-4f9f-903b-4fba6bfedab9", - }); - + const handleAuthSuccess = async () => { const whoamiResponse = await authIframeClient!.getWhoami({ organizationId: "51fb75bf-c044-4f9f-903b-4fba6bfedab9", @@ -106,6 +110,9 @@ export default function AuthPage() { phoneEnabled: config.phone, appleEnabled: config.socials.apple, googleEnabled: config.socials.google, + googleClientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!, + appleClientId: process.env.NEXT_PUBLIC_APPLE_CLIENT_ID!, + appleRedirectURI: process.env.NEXT_PUBLIC_APPLE_REDIRECT_URI! }; navigator.clipboard.writeText(JSON.stringify(authConfig, null, 2)); alert('Auth config copied to clipboard!'); @@ -117,6 +124,9 @@ export default function AuthPage() { phoneEnabled: config.phone, appleEnabled: config.socials.apple, googleEnabled: config.socials.google, + googleClientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!, + appleClientId: process.env.NEXT_PUBLIC_APPLE_CLIENT_ID!, + appleRedirectURI: process.env.NEXT_PUBLIC_APPLE_REDIRECT_URI! }; return ( @@ -252,9 +262,10 @@ export default function AuthPage() { :
- +
} ); } + diff --git a/packages/sdk-react/src/api/createSuborg.ts b/packages/sdk-react/src/actions/createSuborg.ts similarity index 100% rename from packages/sdk-react/src/api/createSuborg.ts rename to packages/sdk-react/src/actions/createSuborg.ts diff --git a/packages/sdk-react/src/api/getSuborgs.ts b/packages/sdk-react/src/actions/getSuborgs.ts similarity index 92% rename from packages/sdk-react/src/api/getSuborgs.ts rename to packages/sdk-react/src/actions/getSuborgs.ts index 2cc259594..1f9e32ce0 100644 --- a/packages/sdk-react/src/api/getSuborgs.ts +++ b/packages/sdk-react/src/actions/getSuborgs.ts @@ -13,13 +13,16 @@ export async function getSuborgs( request: GetSuborgsRequest, turnkeyClient: TurnkeySDKClient ): Promise { + try { + console.log("1") const response = await turnkeyClient.apiClient().getSubOrgIds({ organizationId: turnkeyClient.config.defaultOrganizationId, filterType: request.filterType, filterValue: request.filterValue, }); - + console.log("2") + console.log(response) if (!response || !response.organizationIds) { throw new Error("Expected a non-null response with organizationIds."); } diff --git a/packages/sdk-react/src/api/initOtpAuth.ts b/packages/sdk-react/src/actions/initOtpAuth.ts similarity index 90% rename from packages/sdk-react/src/api/initOtpAuth.ts rename to packages/sdk-react/src/actions/initOtpAuth.ts index b5b53d479..51ddf61cf 100644 --- a/packages/sdk-react/src/api/initOtpAuth.ts +++ b/packages/sdk-react/src/actions/initOtpAuth.ts @@ -1,4 +1,4 @@ -import type {Turnkey as TurnkeySDKClient} from "@turnkey/sdk-server"; +import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; type InitOtpAuthRequest = { suborgID: string; diff --git a/packages/sdk-react/src/api/oauth.ts b/packages/sdk-react/src/actions/oauth.ts similarity index 100% rename from packages/sdk-react/src/api/oauth.ts rename to packages/sdk-react/src/actions/oauth.ts diff --git a/packages/sdk-react/src/api/otpAuth.ts b/packages/sdk-react/src/actions/otpAuth.ts similarity index 92% rename from packages/sdk-react/src/api/otpAuth.ts rename to packages/sdk-react/src/actions/otpAuth.ts index 2d8538efa..c6306798e 100644 --- a/packages/sdk-react/src/api/otpAuth.ts +++ b/packages/sdk-react/src/actions/otpAuth.ts @@ -1,4 +1,4 @@ -import type {Turnkey as TurnkeySDKClient} from "@turnkey/sdk-server"; +import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; type OtpAuthRequest = { suborgID: string; From e9beba3d8bbe5a345a2324d13bb96a2f4f66ac34 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Tue, 5 Nov 2024 13:30:47 -0500 Subject: [PATCH 14/73] working server actions --- examples/react-components/src/app/layout.tsx | 2 - examples/react-components/src/app/page.tsx | 5 +- package.json | 4 + packages/sdk-react/package.json | 1 + .../sdk-react/src/actions/createSuborg.ts | 11 ++- packages/sdk-react/src/actions/getSuborgs.ts | 15 ++-- packages/sdk-react/src/actions/index.ts | 7 ++ packages/sdk-react/src/actions/initOtpAuth.ts | 12 ++- packages/sdk-react/src/actions/oauth.ts | 11 ++- packages/sdk-react/src/actions/otpAuth.ts | 11 ++- .../sdk-react/src/components/auth/Auth.tsx | 27 +++--- packages/sdk-react/src/index.ts | 1 + pnpm-lock.yaml | 82 +++++++++++++++---- rollup.config.base.mjs | 8 +- 14 files changed, 141 insertions(+), 56 deletions(-) create mode 100644 packages/sdk-react/src/actions/index.ts diff --git a/examples/react-components/src/app/layout.tsx b/examples/react-components/src/app/layout.tsx index b15f6224a..a8f7b5a3a 100644 --- a/examples/react-components/src/app/layout.tsx +++ b/examples/react-components/src/app/layout.tsx @@ -19,11 +19,9 @@ function RootLayout({ children }: RootLayoutProps) { return ( -
{children} -
); diff --git a/examples/react-components/src/app/page.tsx b/examples/react-components/src/app/page.tsx index 2a22ff5ef..51a3fcf3c 100644 --- a/examples/react-components/src/app/page.tsx +++ b/examples/react-components/src/app/page.tsx @@ -3,8 +3,7 @@ import styles from "./index.module.css"; import * as React from "react"; import { useState } from "react"; -import { useTurnkey, Auth, AuthServerWrapper } from "@turnkey/sdk-react"; -import { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; +import { useTurnkey, Auth } from "@turnkey/sdk-react"; import { Switch, Typography, IconButton } from '@mui/material'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import AppsIcon from '@mui/icons-material/Apps'; @@ -262,7 +261,7 @@ export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { :
- +
} diff --git a/package.json b/package.json index fb33deb00..9fe576324 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "rollup": "^4.22.4", "rollup-plugin-node-externals": "^6.1.2", "rollup-plugin-postcss": "^4.0.2", + "rollup-plugin-preserve-directives": "^0.4.0", "tsx": "^3.12.7", "typescript": "^5.1.4" }, @@ -63,5 +64,8 @@ "secp256k1": ">=4.0.4", "cross-spawn": ">=7.0.5" } + }, + "dependencies": { + "rollup-preserve-directives": "^1.1.2" } } diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index 33060d402..333c12b59 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -59,6 +59,7 @@ "@turnkey/wallet-stamper": "workspace:*", "usehooks-ts": "^3.1.0", "@turnkey/sdk-server": "workspace:*", + "axios": "^1.7.7", "react-apple-login": "^1.1.6", "react-hook-form": "^7.45.1", "react-international-phone": "^4.3.0" diff --git a/packages/sdk-react/src/actions/createSuborg.ts b/packages/sdk-react/src/actions/createSuborg.ts index 1059ff089..00cdb0faa 100644 --- a/packages/sdk-react/src/actions/createSuborg.ts +++ b/packages/sdk-react/src/actions/createSuborg.ts @@ -1,4 +1,6 @@ -import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; +'use server' + +import { Turnkey } from "@turnkey/sdk-server"; type CreateSuborgRequest = { oauthProviders?: Provider[]; @@ -25,8 +27,13 @@ type CreateSuborgResponse = { export async function createSuborg( request: CreateSuborgRequest, - turnkeyClient: TurnkeySDKClient ): Promise { + const turnkeyClient = new Turnkey({ + apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, + defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, + apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + }) try { const suborgResponse = await turnkeyClient.apiClient().createSubOrganization({ subOrganizationName: `suborg-${String(Date.now())}`, diff --git a/packages/sdk-react/src/actions/getSuborgs.ts b/packages/sdk-react/src/actions/getSuborgs.ts index 1f9e32ce0..1da084e2b 100644 --- a/packages/sdk-react/src/actions/getSuborgs.ts +++ b/packages/sdk-react/src/actions/getSuborgs.ts @@ -1,4 +1,6 @@ -import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; +'use server' + +import { Turnkey } from "@turnkey/sdk-server"; type GetSuborgsRequest = { filterValue: string; @@ -11,18 +13,19 @@ type GetSuborgsResponse = { export async function getSuborgs( request: GetSuborgsRequest, - turnkeyClient: TurnkeySDKClient ): Promise { - + const turnkeyClient = new Turnkey({ + apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, + defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, + apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + }) try { - console.log("1") const response = await turnkeyClient.apiClient().getSubOrgIds({ organizationId: turnkeyClient.config.defaultOrganizationId, filterType: request.filterType, filterValue: request.filterValue, }); - console.log("2") - console.log(response) if (!response || !response.organizationIds) { throw new Error("Expected a non-null response with organizationIds."); } diff --git a/packages/sdk-react/src/actions/index.ts b/packages/sdk-react/src/actions/index.ts new file mode 100644 index 000000000..b470036d6 --- /dev/null +++ b/packages/sdk-react/src/actions/index.ts @@ -0,0 +1,7 @@ +// actions that need to be run on the server side (i.e Activities signed by the Parent Organization) + +export {createSuborg} from './createSuborg' +export {getSuborgs} from './getSuborgs' +export {initOtpAuth} from './initOtpAuth' +export {oauth} from './oauth' +export {otpAuth} from './otpAuth' \ No newline at end of file diff --git a/packages/sdk-react/src/actions/initOtpAuth.ts b/packages/sdk-react/src/actions/initOtpAuth.ts index 51ddf61cf..bd513d2b7 100644 --- a/packages/sdk-react/src/actions/initOtpAuth.ts +++ b/packages/sdk-react/src/actions/initOtpAuth.ts @@ -1,4 +1,6 @@ -import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; +'use server' + +import {Turnkey } from "@turnkey/sdk-server"; type InitOtpAuthRequest = { suborgID: string; @@ -13,8 +15,14 @@ type InitOtpAuthResponse = { export async function initOtpAuth( request: InitOtpAuthRequest, - turnkeyClient: TurnkeySDKClient ): Promise { + const turnkeyClient = new Turnkey({ + apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, + defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, + apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + }) + try { const initOtpAuthResponse = await turnkeyClient.apiClient().initOtpAuth({ contact: request.contact, diff --git a/packages/sdk-react/src/actions/oauth.ts b/packages/sdk-react/src/actions/oauth.ts index c25b4b118..7fb77567d 100644 --- a/packages/sdk-react/src/actions/oauth.ts +++ b/packages/sdk-react/src/actions/oauth.ts @@ -1,4 +1,6 @@ -import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; +'use server' + +import { Turnkey } from "@turnkey/sdk-server"; type OauthRequest = { suborgID: string; @@ -14,8 +16,13 @@ type OauthResponse = { export async function oauth( request: OauthRequest, - turnkeyClient: TurnkeySDKClient ): Promise { + const turnkeyClient = new Turnkey({ + apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, + defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, + apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + }) try { const oauthResponse = await turnkeyClient.apiClient().oauth({ oidcToken: request.oidcToken, diff --git a/packages/sdk-react/src/actions/otpAuth.ts b/packages/sdk-react/src/actions/otpAuth.ts index c6306798e..0f29982ed 100644 --- a/packages/sdk-react/src/actions/otpAuth.ts +++ b/packages/sdk-react/src/actions/otpAuth.ts @@ -1,4 +1,6 @@ -import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; +'use server' + +import { Turnkey } from "@turnkey/sdk-server"; type OtpAuthRequest = { suborgID: string; @@ -15,8 +17,13 @@ type OtpAuthResponse = { export async function otpAuth( request: OtpAuthRequest, - turnkeyClient: TurnkeySDKClient ): Promise { + const turnkeyClient = new Turnkey({ + apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, + defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, + apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + }) try { const otpAuthResponse = await turnkeyClient.apiClient().otpAuth({ otpId: request.otpId, diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 745348ee9..36bdb48f4 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -2,13 +2,9 @@ import styles from "./Auth.module.css"; import { useEffect, useState } from "react"; import { useTurnkey } from "../../hooks/useTurnkey"; import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; -import { initOtpAuth } from "../../api/initOtpAuth"; -import { otpAuth } from "../../api/otpAuth"; -import { getSuborgs } from "../../api/getSuborgs"; +import { initOtpAuth, otpAuth, getSuborgs, createSuborg, oauth } from "../../actions/"; import { MuiPhone } from "./PhoneInput"; import OTPInput from "./OtpInput"; -import { createSuborg } from "../../api/createSuborg"; -import { oauth } from "../../api/oauth"; import GoogleAuthButton from "./Google"; import AppleAuthButton from "./Apple"; @@ -24,7 +20,7 @@ interface AuthProps { }; } -const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authConfig }) => { +const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const { passkeyClient, authIframeClient } = useTurnkey(); const [error, setError] = useState(null); const [otpError, setOtpError] = useState(null); @@ -53,10 +49,10 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon const handleGetOrCreateSuborg = async (filterType: string, filterValue: string, additionalData = {}) => { - const getSuborgsResponse = await getSuborgs({ filterType, filterValue }, turnkeyClient); + const getSuborgsResponse = await getSuborgs({ filterType, filterValue }); let suborgId = getSuborgsResponse!.organizationIds[0]; if (!suborgId) { - const createSuborgResponse = await createSuborg({ [filterType.toLowerCase()]: filterValue, ...additionalData }, turnkeyClient); + const createSuborgResponse = await createSuborg({ [filterType.toLowerCase()]: filterValue, ...additionalData }); suborgId = createSuborgResponse?.subOrganizationId!; } return suborgId; @@ -72,7 +68,7 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon const handleLoginWithPasskey = async () => { // Step 1: Try to retrieve the suborg by email - const getSuborgsResponse = await getSuborgs({ filterType: "EMAIL", filterValue: email }, turnkeyClient); + const getSuborgsResponse = await getSuborgs({ filterType: "EMAIL", filterValue: email }); const existingSuborgId = getSuborgsResponse!.organizationIds[0]; if (existingSuborgId) { @@ -98,7 +94,7 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon const createSuborgResponse = await createSuborg({ email, passkey: { authenticatorName: "First Passkey", challenge: encodedChallenge, attestation }, - }, turnkeyClient); + }); const newSuborgId = createSuborgResponse?.subOrganizationId; @@ -125,7 +121,7 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon const handleOtpLogin = async (type: "EMAIL" | "PHONE_NUMBER", value: string, otpType: string) => { const suborgId = await handleGetOrCreateSuborg(type, value); - const initAuthResponse = await initOtpAuth({ suborgID: suborgId, otpType, contact: value }, turnkeyClient); + const initAuthResponse = await initOtpAuth({ suborgID: suborgId, otpType, contact: value }); setSuborgId(suborgId); setOtpId(initAuthResponse!.otpId); setStep(type === "EMAIL" ? "otpEmail" : "otpPhone"); @@ -133,8 +129,7 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon const handleEnterOtp = async (otp: string) => { const authResponse = await otpAuth( - { suborgID: suborgId, otpId: otpId!, otpCode: otp, targetPublicKey: authIframeClient!.iframePublicKey! }, - turnkeyClient + { suborgID: suborgId, otpId: otpId!, otpCode: otp, targetPublicKey: authIframeClient!.iframePublicKey! } ); authResponse?.credentialBundle ? await handleAuthSuccess(authResponse.credentialBundle) : setOtpError("Invalid code, please try again"); }; @@ -153,7 +148,7 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon const handleOAuthLogin = async (credential: string, providerName: string) => { const suborgId = await handleGetOrCreateSuborg("OIDC_TOKEN", credential, { oauthProviders: [{ providerName, oidcToken: credential }] }); - const oauthResponse = await oauth({ suborgID: suborgId, oidcToken: credential, targetPublicKey: authIframeClient?.iframePublicKey! }, turnkeyClient); + const oauthResponse = await oauth({ suborgID: suborgId, oidcToken: credential, targetPublicKey: authIframeClient?.iframePublicKey! }); await handleAuthSuccess(oauthResponse!.credentialBundle); }; @@ -268,13 +263,13 @@ const Auth: React.FC = ({ turnkeyClient, onHandleAuthSuccess, authCon )} -{!otpId && authConfig.appleEnabled && authIframeClient && ( +{/* {!otpId && authConfig.appleEnabled && authIframeClient && (
-)} +)} */}
{!otpId ? ( diff --git a/packages/sdk-react/src/index.ts b/packages/sdk-react/src/index.ts index cab836e5a..37815ccc0 100644 --- a/packages/sdk-react/src/index.ts +++ b/packages/sdk-react/src/index.ts @@ -3,4 +3,5 @@ import "./components/auth/PhoneInput.css"; import { TurnkeyContext, TurnkeyProvider } from "./contexts/TurnkeyContext"; import { useTurnkey } from "./hooks/useTurnkey"; export * from "./components" +export * from "./actions" export { TurnkeyContext, TurnkeyProvider, useTurnkey}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b637e3fc..0e0dbdacc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,10 @@ overrides: importers: .: + dependencies: + rollup-preserve-directives: + specifier: ^1.1.2 + version: 1.1.2(rollup@4.22.4) devDependencies: '@changesets/cli': specifier: ^2.27.5 @@ -59,6 +63,9 @@ importers: rollup-plugin-postcss: specifier: ^4.0.2 version: 4.0.2(postcss@8.4.38) + rollup-plugin-preserve-directives: + specifier: ^0.4.0 + version: 0.4.0(rollup@4.22.4) tsx: specifier: ^3.12.7 version: 3.12.7 @@ -1813,6 +1820,9 @@ importers: '@turnkey/wallet-stamper': specifier: workspace:* version: link:../wallet-stamper + axios: + specifier: ^1.7.7 + version: 1.7.7 react-apple-login: specifier: ^1.1.6 version: 1.1.6(prop-types@15.8.1)(react-dom@18.3.1)(react@18.2.0) @@ -10247,12 +10257,26 @@ packages: rollup: 4.22.4 dev: true + /@rollup/pluginutils@5.1.3(rollup@4.22.4): + resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 4.0.2 + rollup: 4.22.4 + dev: true + /@rollup/rollup-android-arm-eabi@4.22.4: resolution: {integrity: sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==} cpu: [arm] os: [android] requiresBuild: true - dev: true optional: true /@rollup/rollup-android-arm64@4.22.4: @@ -10260,7 +10284,6 @@ packages: cpu: [arm64] os: [android] requiresBuild: true - dev: true optional: true /@rollup/rollup-darwin-arm64@4.22.4: @@ -10268,7 +10291,6 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true - dev: true optional: true /@rollup/rollup-darwin-x64@4.22.4: @@ -10276,7 +10298,6 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm-gnueabihf@4.22.4: @@ -10284,7 +10305,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm-musleabihf@4.22.4: @@ -10292,7 +10312,6 @@ packages: cpu: [arm] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm64-gnu@4.22.4: @@ -10300,7 +10319,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-arm64-musl@4.22.4: @@ -10308,7 +10326,6 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-powerpc64le-gnu@4.22.4: @@ -10316,7 +10333,6 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-riscv64-gnu@4.22.4: @@ -10324,7 +10340,6 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-s390x-gnu@4.22.4: @@ -10332,7 +10347,6 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-x64-gnu@4.22.4: @@ -10340,7 +10354,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-linux-x64-musl@4.22.4: @@ -10348,7 +10361,6 @@ packages: cpu: [x64] os: [linux] requiresBuild: true - dev: true optional: true /@rollup/rollup-win32-arm64-msvc@4.22.4: @@ -10356,7 +10368,6 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true - dev: true optional: true /@rollup/rollup-win32-ia32-msvc@4.22.4: @@ -10364,7 +10375,6 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true - dev: true optional: true /@rollup/rollup-win32-x64-msvc@4.22.4: @@ -10372,7 +10382,6 @@ packages: cpu: [x64] os: [win32] requiresBuild: true - dev: true optional: true /@rushstack/eslint-patch@1.9.0: @@ -12463,7 +12472,6 @@ packages: /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: true /@types/express-serve-static-core@4.17.43: resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==} @@ -14086,6 +14094,16 @@ packages: - debug dev: false + /axios@1.7.7: + resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + dependencies: + follow-redirects: 1.15.4(debug@4.3.4) + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} dependencies: @@ -19578,6 +19596,11 @@ packages: react: 18.3.1 dev: false + /magic-string@0.30.12: + resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + /make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -20880,6 +20903,11 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + /picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + dev: true + /pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -22474,12 +22502,31 @@ packages: - ts-node dev: true + /rollup-plugin-preserve-directives@0.4.0(rollup@4.22.4): + resolution: {integrity: sha512-gx4nBxYm5BysmEQS+e2tAMrtFxrGvk+Pe5ppafRibQi0zlW7VYAbEGk6IKDw9sJGPdFWgVTE0o4BU4cdG0Fylg==} + peerDependencies: + rollup: 2.x || 3.x || 4.x + dependencies: + '@rollup/pluginutils': 5.1.3(rollup@4.22.4) + magic-string: 0.30.12 + rollup: 4.22.4 + dev: true + /rollup-pluginutils@2.8.2: resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} dependencies: estree-walker: 0.6.1 dev: true + /rollup-preserve-directives@1.1.2(rollup@4.22.4): + resolution: {integrity: sha512-OOaYh4zO0Dcd/eVWGB8H69CgTiohl+jJqc2TLtjLENVIQaV2rxO3OW6RILzCQOdDvPT+/rzwRp+97OXhem895Q==} + peerDependencies: + rollup: ^2.0.0 || ^3.0.0 || ^4.0.0 + dependencies: + magic-string: 0.30.12 + rollup: 4.22.4 + dev: false + /rollup@4.22.4: resolution: {integrity: sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -22504,7 +22551,6 @@ packages: '@rollup/rollup-win32-ia32-msvc': 4.22.4 '@rollup/rollup-win32-x64-msvc': 4.22.4 fsevents: 2.3.3 - dev: true /rpc-websockets@7.5.1: resolution: {integrity: sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w==} diff --git a/rollup.config.base.mjs b/rollup.config.base.mjs index de74ad699..7131a4d38 100644 --- a/rollup.config.base.mjs +++ b/rollup.config.base.mjs @@ -2,11 +2,11 @@ import typescript from "@rollup/plugin-typescript"; import nodeExternals from "rollup-plugin-node-externals"; import path from "node:path"; import postcss from 'rollup-plugin-postcss'; +import preserveDirectives from 'rollup-plugin-preserve-directives'; const getFormatConfig = (format) => { const pkgPath = path.join(process.cwd(), "package.json"); - /** @type {import('rollup').RollupOptions} */ return { input: 'src/index.ts', output: { @@ -19,8 +19,8 @@ const getFormatConfig = (format) => { plugins: [ postcss({ modules: true, - extensions: ['.css', '.scss'], - use: ['sass'], + extensions: ['.css', '.scss'], + use: ['sass'], extract: `styles.${format}.css`, minimize: true, sourceMap: true, @@ -35,6 +35,8 @@ const getFormatConfig = (format) => { sourceMap: true, }, }), + // Add the preserveDirectives plugin after typescript + preserveDirectives(), nodeExternals({ packagePath: pkgPath, builtinsPrefix: 'ignore', From b2942f1c4efc443b33a90f0b24748f06d5f37806 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Tue, 5 Nov 2024 16:34:46 -0500 Subject: [PATCH 15/73] better styling, cleanup --- examples/react-components/src/app/page.tsx | 25 ++++-- packages/sdk-react/package.json | 7 +- .../src/components/auth/Apple.module.css | 25 ++++++ .../sdk-react/src/components/auth/Apple.tsx | 42 ++++++++-- .../src/components/auth/Auth.module.css | 42 +++------- .../sdk-react/src/components/auth/Auth.tsx | 36 +++++---- .../src/components/auth/Facebook.module.css | 25 ++++++ .../src/components/auth/Facebook.tsx | 78 +++++++++++++++++++ .../sdk-react/src/components/auth/Google.tsx | 14 +++- .../src/components/auth/OTPInput.tsx | 4 +- .../src/components/auth/facebook-utils.ts | 25 ++++++ pnpm-lock.yaml | 58 +++++++++----- 12 files changed, 303 insertions(+), 78 deletions(-) create mode 100644 packages/sdk-react/src/components/auth/Apple.module.css create mode 100644 packages/sdk-react/src/components/auth/Facebook.module.css create mode 100644 packages/sdk-react/src/components/auth/Facebook.tsx create mode 100644 packages/sdk-react/src/components/auth/facebook-utils.ts diff --git a/examples/react-components/src/app/page.tsx b/examples/react-components/src/app/page.tsx index 51a3fcf3c..491c0d330 100644 --- a/examples/react-components/src/app/page.tsx +++ b/examples/react-components/src/app/page.tsx @@ -13,6 +13,7 @@ interface SocialConfig { enabled: boolean; google: boolean; apple: boolean; + facebook: boolean; } interface Config { @@ -51,6 +52,7 @@ export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { enabled: true, google: true, apple: true, + facebook: true, }, }); @@ -84,6 +86,7 @@ export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { enabled: isEnabled, google: isEnabled, apple: isEnabled, + facebook: isEnabled, }, }; } @@ -109,9 +112,7 @@ export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { phoneEnabled: config.phone, appleEnabled: config.socials.apple, googleEnabled: config.socials.google, - googleClientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!, - appleClientId: process.env.NEXT_PUBLIC_APPLE_CLIENT_ID!, - appleRedirectURI: process.env.NEXT_PUBLIC_APPLE_REDIRECT_URI! + facebookEnabled: config.socials.facebook, }; navigator.clipboard.writeText(JSON.stringify(authConfig, null, 2)); alert('Auth config copied to clipboard!'); @@ -123,9 +124,7 @@ export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { phoneEnabled: config.phone, appleEnabled: config.socials.apple, googleEnabled: config.socials.google, - googleClientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!, - appleClientId: process.env.NEXT_PUBLIC_APPLE_CLIENT_ID!, - appleRedirectURI: process.env.NEXT_PUBLIC_APPLE_REDIRECT_URI! + facebookEnabled: config.socials.facebook }; return ( @@ -229,6 +228,20 @@ export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { }, }} checked={config.socials.apple} onChange={() => toggleSocials('apple')} />
+
+
+ Facebook +
+ toggleSocials('facebook')} /> +
)}
diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index 333c12b59..cc56042f2 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -50,7 +50,7 @@ "dependencies": { "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@greatsumini/react-facebook-login": "^3.3.3", + "@icons-pack/react-simple-icons": "^10.1.0", "@mui/icons-material": "^6.1.5", "@mui/material": "^6.1.5", "@noble/hashes": "1.4.0", @@ -61,8 +61,9 @@ "@turnkey/sdk-server": "workspace:*", "axios": "^1.7.7", "react-apple-login": "^1.1.6", - "react-hook-form": "^7.45.1", - "react-international-phone": "^4.3.0" + "react-international-phone": "^4.3.0", + "react-social-login-buttons": "^4.1.0", + "reactjs-social-login": "^2.6.3" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" diff --git a/packages/sdk-react/src/components/auth/Apple.module.css b/packages/sdk-react/src/components/auth/Apple.module.css new file mode 100644 index 000000000..5266b822c --- /dev/null +++ b/packages/sdk-react/src/components/auth/Apple.module.css @@ -0,0 +1,25 @@ +.appleButton { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + padding: 10px 16px; + color: #000000; + font-size: 14px; + background-color: #ffffff; + border-radius: 8px; + cursor: pointer; + font-weight: 500; + border: 1px solid #f2f2f2; + transition: background-color 0.3s ease; +} + +.appleButton:hover { + background-color: #f2f2f2; +} + +.buttonText { + flex-grow: 1; + text-align: center; + font-family: "Inter", sans-serif; +} diff --git a/packages/sdk-react/src/components/auth/Apple.tsx b/packages/sdk-react/src/components/auth/Apple.tsx index d8c337bd8..8d7668d0b 100644 --- a/packages/sdk-react/src/components/auth/Apple.tsx +++ b/packages/sdk-react/src/components/auth/Apple.tsx @@ -1,15 +1,45 @@ +import { useEffect, useState } from "react"; import { sha256 } from "@noble/hashes/sha2"; import { bytesToHex } from "@noble/hashes/utils"; import AppleLogin from "react-apple-login"; - +import { SiApple } from "@icons-pack/react-simple-icons" +import styles from "./Apple.module.css"; interface AppleAuthButtonProps { iframePublicKey: string; clientId: string; redirectURI: string; onSuccess: (response: any) => void; } +declare global { + interface Window { + AppleID?: any; + } +} + const AppleAuthButton: React.FC = ({ iframePublicKey, onSuccess, clientId, redirectURI }) => { + const [appleSDKLoaded, setAppleSDKLoaded] = useState(false); + + useEffect(() => { + const loadAppleSDK = () => { + const script = document.createElement("script"); + script.src = "https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"; + script.onload = () => setAppleSDKLoaded(true); + script.onerror = () => console.error("Failed to load AppleID SDK"); + document.body.appendChild(script); + }; + + if (!window.AppleID) { + loadAppleSDK(); + } else { + setAppleSDKLoaded(true); + } + }, []); + + if (!appleSDKLoaded) { + return null; // Or render a loading spinner + } + return ( = ({ iframePublicKey, onSu redirectURI={redirectURI} responseType="code id_token" responseMode="fragment" + render={({ onClick }) => ( +
+ + Continue with Apple +
+ )} callback={(response) => { if (response.error) { console.error("Apple login error:", response.error); @@ -25,10 +61,6 @@ const AppleAuthButton: React.FC = ({ iframePublicKey, onSu } }} usePopup - designProp={{ - type: "continue", - color: "white", - }} /> ); }; diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index f8ec5b43f..7284f1920 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -151,7 +151,6 @@ color: var(--Greyscale-600, #6C727E); font-weight: 700; cursor: pointer; } -/* Auth Button Styles */ .authButton { display: flex; @@ -160,40 +159,21 @@ color: var(--Greyscale-600, #6C727E); margin-bottom: 12px; } -.appleButtonContainer { - width: 100%; - max-width: 180px; /* Optional max-width */ - height: 40px; /* Adjust height as desired */ +.authButton { display: flex; - cursor: pointer; justify-content: center; -} - -.google, -.facebook, -.apple { - display: block; width: 100%; - padding: 12px; - font-size: 1rem; - margin-top: 8px; - color: #fff; - background-color: #4285f4; - border: none; - border-radius: 4px; - cursor: pointer; -} - -.google:hover { - background-color: #357ae8; -} - -.facebook { - background-color: #3b5998; + margin-bottom: 12px; } -.apple { - background-color: #000; +.socialButtonContainer { + width: 235px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + text-align: center; } .poweredBy { @@ -224,5 +204,5 @@ color: var(--Greyscale-600, #6C727E); text-align: center; margin-top: 12px; color: #FF4C4C; - font-weight: 550; + font-weight: 500; } \ No newline at end of file diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 36bdb48f4..193557dc1 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -7,6 +7,7 @@ import { MuiPhone } from "./PhoneInput"; import OTPInput from "./OtpInput"; import GoogleAuthButton from "./Google"; import AppleAuthButton from "./Apple"; +import FacebookAuthButton from "./Facebook"; interface AuthProps { turnkeyClient: TurnkeySDKClient; @@ -16,6 +17,7 @@ interface AuthProps { passkeyEnabled: boolean; phoneEnabled: boolean; appleEnabled: boolean; + facebookEnabled: boolean; googleEnabled: boolean; }; } @@ -127,7 +129,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { setStep(type === "EMAIL" ? "otpEmail" : "otpPhone"); }; - const handleEnterOtp = async (otp: string) => { + const handleValidateOtp = async (otp: string) => { const authResponse = await otpAuth( { suborgID: suborgId, otpId: otpId!, otpCode: otp, targetPublicKey: authIframeClient!.iframePublicKey! } ); @@ -198,7 +200,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => {
)} -{!otpId && (authConfig.passkeyEnabled || authConfig.emailEnabled) && (authConfig.googleEnabled || authConfig.appleEnabled || authConfig.phoneEnabled) && +{!otpId && (authConfig.passkeyEnabled || authConfig.emailEnabled) && (authConfig.googleEnabled || authConfig.appleEnabled || authConfig.facebookEnabled || authConfig.phoneEnabled) &&
OR
@@ -239,7 +241,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { We've sent a verification code to{" "}
{step === "otpEmail" ? email : phone}
- + setOtpError(null)} onComplete={handleValidateOtp} />
)} @@ -247,29 +249,37 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => {
{otpError}
)}
- {!otpId && (authConfig.googleEnabled || authConfig.appleEnabled) && authConfig.phoneEnabled && + {!otpId && (authConfig.googleEnabled || authConfig.appleEnabled || authConfig.facebookEnabled) && authConfig.phoneEnabled &&
OR
} + {!otpId && authConfig.googleEnabled && authIframeClient && ( -
+
- - + +
+)} + +{!otpId && authConfig.appleEnabled && authIframeClient && ( +
+
+ +
)} -{/* {!otpId && authConfig.appleEnabled && authIframeClient && ( -
-
- -
+{!otpId && authConfig.facebookEnabled && authIframeClient && ( +
+
+
-)} */} +
+)}
{!otpId ? ( diff --git a/packages/sdk-react/src/components/auth/Facebook.module.css b/packages/sdk-react/src/components/auth/Facebook.module.css new file mode 100644 index 000000000..dd55a14d1 --- /dev/null +++ b/packages/sdk-react/src/components/auth/Facebook.module.css @@ -0,0 +1,25 @@ +.facebookButton { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + padding: 10px 16px; + color: #fff; + font-size: 14px; + background-color: #3b5998; + border: none; + border-radius: 8px; + cursor: pointer; + font-weight: 500; + transition: background-color 0.3s ease; +} + +.facebookButton:hover { + background-color: #2d4373; +} + +.buttonText { + flex-grow: 1; + text-align: center; + font-family: "Inter", sans-serif; +} diff --git a/packages/sdk-react/src/components/auth/Facebook.tsx b/packages/sdk-react/src/components/auth/Facebook.tsx new file mode 100644 index 000000000..13285be03 --- /dev/null +++ b/packages/sdk-react/src/components/auth/Facebook.tsx @@ -0,0 +1,78 @@ +"use client" + +import { SiFacebook } from "@icons-pack/react-simple-icons" +import { sha256 } from "@noble/hashes/sha2"; +import { bytesToHex } from "@noble/hashes/utils"; +import { useEffect } from "react"; +import { generateChallengePair } from "./facebook-utils" +import styles from "./Facebook.module.css"; + +interface FacebookAuthButtonProps { + iframePublicKey: string; + clientId: string; + authAPIVersion: string; + redirectURI: string; + onSuccess: (response: any) => void; +} + +const FacebookAuthButton: React.FC = ({ iframePublicKey, onSuccess, clientId, authAPIVersion, redirectURI }) => { + + const redirectToFacebook = async () => { + const { verifier, codeChallenge } = await generateChallengePair() + const codeChallengeMethod = "sha256" + + // Generate the Facebook OAuth URL + const params = new URLSearchParams({ + client_id: clientId, + redirect_uri: redirectURI, + state: verifier, + code_challenge: codeChallenge, + code_challenge_method: codeChallengeMethod, + nonce: bytesToHex(sha256(iframePublicKey)), + scope: "openid", + response_type: "code", + } as any) + + const facebookOAuthURL = `https://www.facebook.com/v${authAPIVersion}/dialog/oauth?${params.toString()}` + window.location.href = facebookOAuthURL + } + + useEffect(() => { + const handleFacebookRedirect = async () => { + const urlParams = new URLSearchParams(window.location.search) + const code = urlParams.get("code") + const state = urlParams.get("state") + + if (code && state) { + try { + // Call backend to exchange the code for a token + const response = await fetch("/api/auth/facebook/callback", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ code, verifier: state }) + }) + const result = await response.json() + if (response.ok) { + onSuccess(result) + } else { + console.error("Facebook auth failed", result) + } + } catch (error) { + console.error("Error during Facebook auth callback", error) + } + } + } + + // Run this only once after redirect + handleFacebookRedirect() + }, [onSuccess]) + + return ( +
+ + Continue with Facebook +
+ ) +} + +export default FacebookAuthButton diff --git a/packages/sdk-react/src/components/auth/Google.tsx b/packages/sdk-react/src/components/auth/Google.tsx index 7fe90f60d..eae3cd4dd 100644 --- a/packages/sdk-react/src/components/auth/Google.tsx +++ b/packages/sdk-react/src/components/auth/Google.tsx @@ -15,8 +15,20 @@ const GoogleAuthButton: React.FC = ({ iframePublicKey, on nonce={bytesToHex(sha256(iframePublicKey))} onSuccess={onSuccess} useOneTap + width={235} + containerProps={{ + style: { + width: '100%', + display: 'flex', + justifyContent: 'center', + borderRadius: '8px', + padding: '10px', + cursor: 'pointer', + maxWidth: '235px', + }, + }} auto_select={false} - text="signin_with" + text="continue_with" ux_mode="popup" /> diff --git a/packages/sdk-react/src/components/auth/OTPInput.tsx b/packages/sdk-react/src/components/auth/OTPInput.tsx index 2d74998ca..30c10b564 100644 --- a/packages/sdk-react/src/components/auth/OTPInput.tsx +++ b/packages/sdk-react/src/components/auth/OTPInput.tsx @@ -3,12 +3,14 @@ import { TextField, Box } from "@mui/material"; interface OTPInputProps { onComplete: (otp: string) => void; + onChange: () => void; } -const OTPInput: React.FC = ({ onComplete }) => { +const OTPInput: React.FC = ({ onComplete, onChange }) => { const [otp, setOtp] = useState(Array(6).fill("")); const handleChange = (value: string, index: number) => { + onChange() if (/^\d*$/.test(value)) { const newOtp = [...otp]; newOtp[index] = value; diff --git a/packages/sdk-react/src/components/auth/facebook-utils.ts b/packages/sdk-react/src/components/auth/facebook-utils.ts new file mode 100644 index 000000000..3f598f801 --- /dev/null +++ b/packages/sdk-react/src/components/auth/facebook-utils.ts @@ -0,0 +1,25 @@ +"use server" + +import crypto from "crypto" + +export const generateChallengePair = async (): Promise<{ + verifier: string + codeChallenge: string +}> => { + // Step 1: Generate a random 48-character verifier + const verifier = crypto.randomBytes(32).toString("base64url") // URL-safe Base64 string + + const codeChallenge = await verifierSegmentToChallenge(verifier) + + // Return both the verifier and the codeChallenge to the client + return { verifier, codeChallenge } +} + +export const verifierSegmentToChallenge = async ( + segment: string +): Promise => { + const salt = process.env.FACEBOOK_SECRET_SALT + const saltedVerifier = segment + salt + + return crypto.createHash("sha256").update(saltedVerifier).digest("base64url") +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e0dbdacc..45de3dee1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1796,9 +1796,9 @@ importers: '@emotion/styled': specifier: ^11.13.0 version: 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.75)(react@18.2.0) - '@greatsumini/react-facebook-login': - specifier: ^3.3.3 - version: 3.3.3(react@18.2.0) + '@icons-pack/react-simple-icons': + specifier: ^10.1.0 + version: 10.1.0(react@18.2.0) '@mui/icons-material': specifier: ^6.1.5 version: 6.1.5(@mui/material@6.1.5)(@types/react@18.2.75)(react@18.2.0) @@ -1826,12 +1826,15 @@ importers: react-apple-login: specifier: ^1.1.6 version: 1.1.6(prop-types@15.8.1)(react-dom@18.3.1)(react@18.2.0) - react-hook-form: - specifier: ^7.45.1 - version: 7.45.1(react@18.2.0) react-international-phone: specifier: ^4.3.0 version: 4.3.0(react@18.2.0) + react-social-login-buttons: + specifier: ^4.1.0 + version: 4.1.0(react@18.2.0) + reactjs-social-login: + specifier: ^2.6.3 + version: 2.6.3(react-dom@18.3.1)(react@18.2.0) usehooks-ts: specifier: ^3.1.0 version: 3.1.0(react@18.2.0) @@ -6070,14 +6073,6 @@ packages: semver: 7.5.4 dev: false - /@greatsumini/react-facebook-login@3.3.3(react@18.2.0): - resolution: {integrity: sha512-Y5D7EncR3iy/X/OfWwjjpM5OW0XV6PCE08RZUV/yhAE413PEBIlML7S6z69BcpUPWO5XzEt7cytHChUdwXO4Dw==} - peerDependencies: - react: ^16.0.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - /@hapi/hoek@9.3.0: resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} dev: false @@ -6106,6 +6101,14 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} deprecated: Use @eslint/object-schema instead + /@icons-pack/react-simple-icons@10.1.0(react@18.2.0): + resolution: {integrity: sha512-sZ2oDkYaVAci7GuNL8okERJn4Ej0INbeCwtIDVuwWfO5zILW7j5frvKQbozTB+fLtZqEwAP9KkNp7oR8WeHaIg==} + peerDependencies: + react: ^16.13 || ^17 || ^18 + dependencies: + react: 18.2.0 + dev: false + /@inquirer/checkbox@1.5.2: resolution: {integrity: sha512-CifrkgQjDkUkWexmgYYNyB5603HhTHI91vLFeQXh6qrTKiCMVASol01Rs1cv6LP/A2WccZSRlJKZhbaBIs/9ZA==} engines: {node: '>=14.18.0'} @@ -19596,8 +19599,8 @@ packages: react: 18.3.1 dev: false - /magic-string@0.30.12: - resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} + /magic-string@0.30.14: + resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==} dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -22021,6 +22024,14 @@ packages: react-is: 18.3.1 dev: false + /react-social-login-buttons@4.1.0(react@18.2.0): + resolution: {integrity: sha512-PoZKH6h3eKGgcviFC5Sh50QDVQ5KhFf7MrPTFBU/wwPNn0DVNEb5km+T7z83+CiM24Z8qV/H82wHZ4OE6gcawQ==} + peerDependencies: + react: ^16.0.0 || ^17.x || ^18.x + dependencies: + react: 18.2.0 + dev: false + /react-style-singleton@2.2.1(@types/react@18.2.14)(react@18.2.0): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} @@ -22105,6 +22116,17 @@ packages: loose-envify: 1.4.0 dev: false + /reactjs-social-login@2.6.3(react-dom@18.3.1)(react@18.2.0): + resolution: {integrity: sha512-i/pcyJPtlVpHoPRr/j5/KjaP/GNxAInnnHDk0PD2Zo0B3cMdvr0ZiGC/GMjOKAAgKCujyniDf/OiZ7c6MrOLQg==} + engines: {node: '>=10'} + peerDependencies: + react: ^16 || ^17 || ^18 + react-dom: ^16 || ^17 || ^18 + dependencies: + react: 18.2.0 + react-dom: 18.3.1(react@18.2.0) + dev: false + /read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: @@ -22508,7 +22530,7 @@ packages: rollup: 2.x || 3.x || 4.x dependencies: '@rollup/pluginutils': 5.1.3(rollup@4.22.4) - magic-string: 0.30.12 + magic-string: 0.30.14 rollup: 4.22.4 dev: true @@ -22523,7 +22545,7 @@ packages: peerDependencies: rollup: ^2.0.0 || ^3.0.0 || ^4.0.0 dependencies: - magic-string: 0.30.12 + magic-string: 0.30.14 rollup: 4.22.4 dev: false From 43956590c0c2eaeb7e4ae7f6b371af6240c1fc8e Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Wed, 6 Nov 2024 12:01:49 -0500 Subject: [PATCH 16/73] progress on facebook button --- packages/sdk-react/package.json | 1 + .../src/components/auth/Auth.module.css | 1 + .../sdk-react/src/components/auth/Auth.tsx | 2 - .../src/components/auth/Facebook.tsx | 76 ++-- .../src/components/auth/facebook-utils.ts | 50 ++- pnpm-lock.yaml | 405 ++++++++++++++++++ 6 files changed, 470 insertions(+), 65 deletions(-) diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index cc56042f2..21da51593 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -60,6 +60,7 @@ "usehooks-ts": "^3.1.0", "@turnkey/sdk-server": "workspace:*", "axios": "^1.7.7", + "next": "^15.0.2", "react-apple-login": "^1.1.6", "react-international-phone": "^4.3.0", "react-social-login-buttons": "^4.1.0", diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index 7284f1920..c2483ccd2 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -3,6 +3,7 @@ position: relative; /* Ensure contained elements respect authCard boundaries */ width: 100%; max-width: 450px; + min-width: 375px; margin: 0 auto; padding: 20px; background: var(--Greyscale-20, #F5F7FB); diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 193557dc1..289182d89 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -1,7 +1,6 @@ import styles from "./Auth.module.css"; import { useEffect, useState } from "react"; import { useTurnkey } from "../../hooks/useTurnkey"; -import type { Turnkey as TurnkeySDKClient } from "@turnkey/sdk-server"; import { initOtpAuth, otpAuth, getSuborgs, createSuborg, oauth } from "../../actions/"; import { MuiPhone } from "./PhoneInput"; import OTPInput from "./OtpInput"; @@ -10,7 +9,6 @@ import AppleAuthButton from "./Apple"; import FacebookAuthButton from "./Facebook"; interface AuthProps { - turnkeyClient: TurnkeySDKClient; onHandleAuthSuccess: () => Promise; authConfig: { emailEnabled: boolean; diff --git a/packages/sdk-react/src/components/auth/Facebook.tsx b/packages/sdk-react/src/components/auth/Facebook.tsx index 13285be03..3a6d245c1 100644 --- a/packages/sdk-react/src/components/auth/Facebook.tsx +++ b/packages/sdk-react/src/components/auth/Facebook.tsx @@ -1,11 +1,12 @@ -"use client" +"use client"; -import { SiFacebook } from "@icons-pack/react-simple-icons" -import { sha256 } from "@noble/hashes/sha2"; -import { bytesToHex } from "@noble/hashes/utils"; import { useEffect } from "react"; -import { generateChallengePair } from "./facebook-utils" +import { useSearchParams } from "next/navigation"; +import { SiFacebook } from "@icons-pack/react-simple-icons"; import styles from "./Facebook.module.css"; +import { exchangeCodeForToken, generateChallengePair } from "./facebook-utils"; +import { sha256 } from "@noble/hashes/sha256"; +import { bytesToHex } from "@noble/hashes/utils"; interface FacebookAuthButtonProps { iframePublicKey: string; @@ -16,63 +17,50 @@ interface FacebookAuthButtonProps { } const FacebookAuthButton: React.FC = ({ iframePublicKey, onSuccess, clientId, authAPIVersion, redirectURI }) => { + const searchParams = useSearchParams(); - const redirectToFacebook = async () => { - const { verifier, codeChallenge } = await generateChallengePair() - const codeChallengeMethod = "sha256" + const initiateFacebookLogin = async () => { + const { verifier, codeChallenge } = await generateChallengePair(); + const codeChallengeMethod = "sha256"; - // Generate the Facebook OAuth URL const params = new URLSearchParams({ client_id: clientId, redirect_uri: redirectURI, state: verifier, code_challenge: codeChallenge, code_challenge_method: codeChallengeMethod, - nonce: bytesToHex(sha256(iframePublicKey)), + nonce: bytesToHex(sha256(iframePublicKey || "")), scope: "openid", response_type: "code", - } as any) + }); + + const facebookOAuthURL = `https://www.facebook.com/v${authAPIVersion}/dialog/oauth?${params.toString()}`; + window.location.href = facebookOAuthURL; + }; - const facebookOAuthURL = `https://www.facebook.com/v${authAPIVersion}/dialog/oauth?${params.toString()}` - window.location.href = facebookOAuthURL - } + const handleTokenExchange = async (authCode: string, authState: string) => { + console.log("HERE") + const verifier = authState; + const tokenData = await exchangeCodeForToken(clientId, redirectURI, authCode, verifier); + console.log(tokenData) + onSuccess(tokenData); + }; useEffect(() => { - const handleFacebookRedirect = async () => { - const urlParams = new URLSearchParams(window.location.search) - const code = urlParams.get("code") - const state = urlParams.get("state") + const code = searchParams.get("code"); + const state = searchParams.get("state"); - if (code && state) { - try { - // Call backend to exchange the code for a token - const response = await fetch("/api/auth/facebook/callback", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ code, verifier: state }) - }) - const result = await response.json() - if (response.ok) { - onSuccess(result) - } else { - console.error("Facebook auth failed", result) - } - } catch (error) { - console.error("Error during Facebook auth callback", error) - } - } + if (code && state) { + handleTokenExchange(code, state); } - - // Run this only once after redirect - handleFacebookRedirect() - }, [onSuccess]) + }, [searchParams]); return ( -
+
Continue with Facebook
- ) -} + ); +}; -export default FacebookAuthButton +export default FacebookAuthButton; diff --git a/packages/sdk-react/src/components/auth/facebook-utils.ts b/packages/sdk-react/src/components/auth/facebook-utils.ts index 3f598f801..31df7f557 100644 --- a/packages/sdk-react/src/components/auth/facebook-utils.ts +++ b/packages/sdk-react/src/components/auth/facebook-utils.ts @@ -1,25 +1,37 @@ -"use server" +"use server"; +import crypto from "crypto"; -import crypto from "crypto" - -export const generateChallengePair = async (): Promise<{ - verifier: string - codeChallenge: string -}> => { - // Step 1: Generate a random 48-character verifier - const verifier = crypto.randomBytes(32).toString("base64url") // URL-safe Base64 string - - const codeChallenge = await verifierSegmentToChallenge(verifier) +export async function generateChallengePair() { + const verifier = crypto.randomBytes(32).toString("base64url"); + const codeChallenge = await verifierSegmentToChallenge(verifier); + return { verifier, codeChallenge }; +} - // Return both the verifier and the codeChallenge to the client - return { verifier, codeChallenge } +async function verifierSegmentToChallenge(segment: string) { + const salt = process.env.FACEBOOK_SECRET_SALT! + const saltedVerifier = segment + salt; + return crypto.createHash("sha256").update(saltedVerifier).digest("base64url"); } -export const verifierSegmentToChallenge = async ( - segment: string -): Promise => { - const salt = process.env.FACEBOOK_SECRET_SALT - const saltedVerifier = segment + salt +export async function exchangeCodeForToken(clientId: any, redirectURI: any, authCode: any, verifier: any) { + console.log(clientId) + console.log(redirectURI) + console.log(authCode) + console.log(verifier) + const response = await fetch(`https://graph.facebook.com/v11.0/oauth/access_token`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + client_id: clientId, + redirect_uri: redirectURI, + code: authCode, + code_verifier: verifier, + }), + }); - return crypto.createHash("sha256").update(saltedVerifier).digest("base64url") + const tokenData = await response.json(); + if (!response.ok) { + throw new Error("Token exchange failed: " + JSON.stringify(tokenData)); + } + return tokenData; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45de3dee1..3a4f3c0bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1823,6 +1823,9 @@ importers: axios: specifier: ^1.7.7 version: 1.7.7 + next: + specifier: ^15.0.2 + version: 15.0.2(@babel/core@7.24.5)(react-dom@18.3.1)(react@18.2.0) react-apple-login: specifier: ^1.1.6 version: 1.1.6(prop-types@15.8.1)(react-dom@18.3.1)(react@18.2.0) @@ -5220,6 +5223,14 @@ packages: resolution: {integrity: sha512-nNcycZWUYLNJlrIXgpcgVRqdl6BXjF4YlXdxobQWpW9Tikk61bEGeAFhDYtC0PwHlokCNw0KxWiHGJL4nL7Q5A==} dev: false + /@emnapi/runtime@1.3.1: + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} + requiresBuild: true + dependencies: + tslib: 2.6.3 + dev: false + optional: true + /@emotion/babel-plugin@11.12.0: resolution: {integrity: sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==} dependencies: @@ -6249,6 +6260,185 @@ packages: dependencies: mute-stream: 1.0.0 dev: false + /@img/sharp-darwin-arm64@0.33.5: + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + dev: false + optional: true + + /@img/sharp-darwin-x64@0.33.5: + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + dev: false + optional: true + + /@img/sharp-libvips-darwin-arm64@1.0.4: + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-darwin-x64@1.0.4: + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-arm64@1.0.4: + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-arm@1.0.5: + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-s390x@1.0.4: + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linux-x64@1.0.4: + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linuxmusl-arm64@1.0.4: + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-libvips-linuxmusl-x64@1.0.4: + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-linux-arm64@0.33.5: + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + dev: false + optional: true + + /@img/sharp-linux-arm@0.33.5: + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + dev: false + optional: true + + /@img/sharp-linux-s390x@0.33.5: + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + dev: false + optional: true + + /@img/sharp-linux-x64@0.33.5: + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + dev: false + optional: true + + /@img/sharp-linuxmusl-arm64@0.33.5: + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + dev: false + optional: true + + /@img/sharp-linuxmusl-x64@0.33.5: + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + requiresBuild: true + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + dev: false + optional: true + + /@img/sharp-wasm32@0.33.5: + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + requiresBuild: true + dependencies: + '@emnapi/runtime': 1.3.1 + dev: false + optional: true + + /@img/sharp-win32-ia32@0.33.5: + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + + /@img/sharp-win32-x64@0.33.5: + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -7161,6 +7351,10 @@ packages: resolution: {integrity: sha512-dZIu93Bf5LUtluBXIv4woQw2cZVZ2DJTjax5/5DOs3lzEOeKLy7GxRSr4caK9/SCPdaW6bCgpye6+n4Dh9oJPw==} dev: false + /@next/env@15.0.2: + resolution: {integrity: sha512-c0Zr0ModK5OX7D4ZV8Jt/wqoXtitLNPwUfG9zElCZztdaZyNVnN40rDXVZ/+FGuR4CcNV5AEfM6N8f+Ener7Dg==} + dev: false + /@next/eslint-plugin-next@13.4.7: resolution: {integrity: sha512-ANEPltxzXbyyG7CvqxdY4PmeM5+RyWdAJGufTHnU+LA/i3J6IDV2r8Z4onKwskwKEhwqzz5lMaSYGGXLyHX+mg==} dependencies: @@ -7187,6 +7381,15 @@ packages: dev: false optional: true + /@next/swc-darwin-arm64@15.0.2: + resolution: {integrity: sha512-GK+8w88z+AFlmt+ondytZo2xpwlfAR8U6CRwXancHImh6EdGfHMIrTSCcx5sOSBei00GyLVL0ioo1JLKTfprgg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-darwin-x64@14.2.10: resolution: {integrity: sha512-Y0TC+FXbFUQ2MQgimJ/7Ina2mXIKhE7F+GUe1SgnzRmwFY3hX2z8nyVCxE82I2RicspdkZnSWMn4oTjIKz4uzA==} engines: {node: '>= 10'} @@ -7196,6 +7399,15 @@ packages: dev: false optional: true + /@next/swc-darwin-x64@15.0.2: + resolution: {integrity: sha512-KUpBVxIbjzFiUZhiLIpJiBoelqzQtVZbdNNsehhUn36e2YzKHphnK8eTUW1s/4aPy5kH/UTid8IuVbaOpedhpw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-arm64-gnu@14.2.10: resolution: {integrity: sha512-ZfQ7yOy5zyskSj9rFpa0Yd7gkrBnJTkYVSya95hX3zeBG9E55Z6OTNPn1j2BTFWvOVVj65C3T+qsjOyVI9DQpA==} engines: {node: '>= 10'} @@ -7205,6 +7417,15 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-gnu@15.0.2: + resolution: {integrity: sha512-9J7TPEcHNAZvwxXRzOtiUvwtTD+fmuY0l7RErf8Yyc7kMpE47MIQakl+3jecmkhOoIyi/Rp+ddq7j4wG6JDskQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-arm64-musl@14.2.10: resolution: {integrity: sha512-n2i5o3y2jpBfXFRxDREr342BGIQCJbdAUi/K4q6Env3aSx8erM9VuKXHw5KNROK9ejFSPf0LhoSkU/ZiNdacpQ==} engines: {node: '>= 10'} @@ -7214,6 +7435,15 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-musl@15.0.2: + resolution: {integrity: sha512-BjH4ZSzJIoTTZRh6rG+a/Ry4SW0HlizcPorqNBixBWc3wtQtj4Sn9FnRZe22QqrPnzoaW0ctvSz4FaH4eGKMww==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-x64-gnu@14.2.10: resolution: {integrity: sha512-GXvajAWh2woTT0GKEDlkVhFNxhJS/XdDmrVHrPOA83pLzlGPQnixqxD8u3bBB9oATBKB//5e4vpACnx5Vaxdqg==} engines: {node: '>= 10'} @@ -7223,6 +7453,15 @@ packages: dev: false optional: true + /@next/swc-linux-x64-gnu@15.0.2: + resolution: {integrity: sha512-i3U2TcHgo26sIhcwX/Rshz6avM6nizrZPvrDVDY1bXcLH1ndjbO8zuC7RoHp0NSK7wjJMPYzm7NYL1ksSKFreA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-x64-musl@14.2.10: resolution: {integrity: sha512-opFFN5B0SnO+HTz4Wq4HaylXGFV+iHrVxd3YvREUX9K+xfc4ePbRrxqOuPOFjtSuiVouwe6uLeDtabjEIbkmDA==} engines: {node: '>= 10'} @@ -7232,6 +7471,15 @@ packages: dev: false optional: true + /@next/swc-linux-x64-musl@15.0.2: + resolution: {integrity: sha512-AMfZfSVOIR8fa+TXlAooByEF4OB00wqnms1sJ1v+iu8ivwvtPvnkwdzzFMpsK5jA2S9oNeeQ04egIWVb4QWmtQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-arm64-msvc@14.2.10: resolution: {integrity: sha512-9NUzZuR8WiXTvv+EiU/MXdcQ1XUvFixbLIMNQiVHuzs7ZIFrJDLJDaOF1KaqttoTujpcxljM/RNAOmw1GhPPQQ==} engines: {node: '>= 10'} @@ -7241,6 +7489,15 @@ packages: dev: false optional: true + /@next/swc-win32-arm64-msvc@15.0.2: + resolution: {integrity: sha512-JkXysDT0/hEY47O+Hvs8PbZAeiCQVxKfGtr4GUpNAhlG2E0Mkjibuo8ryGD29Qb5a3IOnKYNoZlh/MyKd2Nbww==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-ia32-msvc@14.2.10: resolution: {integrity: sha512-fr3aEbSd1GeW3YUMBkWAu4hcdjZ6g4NBl1uku4gAn661tcxd1bHs1THWYzdsbTRLcCKLjrDZlNp6j2HTfrw+Bg==} engines: {node: '>= 10'} @@ -7259,6 +7516,15 @@ packages: dev: false optional: true + /@next/swc-win32-x64-msvc@15.0.2: + resolution: {integrity: sha512-foaUL0NqJY/dX0Pi/UcZm5zsmSk5MtP/gxx3xOPyREkMFN+CTjctPfu3QaqrQHinaKdPnMWPJDKt4VjDfTBe/Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@ngraveio/bc-ur@1.1.13: resolution: {integrity: sha512-j73akJMV4+vLR2yQ4AphPIT5HZmxVjn/LxpL7YHoINnXoH6ccc90Zzck6/n6a3bCXOVZwBxq+YHwbAKRV+P8Zg==} dependencies: @@ -15129,6 +15395,23 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: false + optional: true + + /color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + dev: false + optional: true + /colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} dev: true @@ -15825,6 +16108,12 @@ packages: hasBin: true dev: false + /detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + dev: false + optional: true + /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -18092,6 +18381,11 @@ packages: /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + dev: false + optional: true + /is-async-function@2.0.0: resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} engines: {node: '>= 0.4'} @@ -20252,6 +20546,51 @@ packages: - babel-plugin-macros dev: false + /next@15.0.2(@babel/core@7.24.5)(react-dom@18.3.1)(react@18.2.0): + resolution: {integrity: sha512-rxIWHcAu4gGSDmwsELXacqAPUk+j8dV/A9cDF5fsiCMpkBDYkO2AEaL1dfD+nNmDiU6QMCFN8Q30VEKapT9UHQ==} + engines: {node: '>=18.18.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-02c0e824-20241028 + react-dom: ^18.2.0 || 19.0.0-rc-02c0e824-20241028 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + dependencies: + '@next/env': 15.0.2 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.13 + busboy: 1.6.0 + caniuse-lite: 1.0.30001581 + postcss: 8.4.31 + react: 18.2.0 + react-dom: 18.3.1(react@18.2.0) + styled-jsx: 5.1.6(@babel/core@7.24.5)(react@18.2.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.0.2 + '@next/swc-darwin-x64': 15.0.2 + '@next/swc-linux-arm64-gnu': 15.0.2 + '@next/swc-linux-arm64-musl': 15.0.2 + '@next/swc-linux-x64-gnu': 15.0.2 + '@next/swc-linux-x64-musl': 15.0.2 + '@next/swc-win32-arm64-msvc': 15.0.2 + '@next/swc-win32-x64-msvc': 15.0.2 + sharp: 0.33.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + /no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: @@ -22838,6 +23177,47 @@ packages: kind-of: 6.0.3 dev: false +<<<<<<< HEAD +======= + /sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + requiresBuild: true + dependencies: + color: 4.2.3 + detect-libc: 2.0.3 + semver: 7.5.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + dev: false + optional: true + + /shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + dependencies: + shebang-regex: 1.0.0 + dev: true + +>>>>>>> 08f3aa86 (progress on facebook button) /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -22868,6 +23248,13 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + /simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + dependencies: + is-arrayish: 0.3.2 + dev: false + optional: true + /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -23345,6 +23732,24 @@ packages: react: 18.3.1 dev: false + /styled-jsx@5.1.6(@babel/core@7.24.5)(react@18.2.0): + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + dependencies: + '@babel/core': 7.24.5 + client-only: 0.0.1 + react: 18.2.0 + dev: false + /stylehacks@5.1.1(postcss@8.4.38): resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} engines: {node: ^10 || ^12 || >=14.0} From 211ad91cdb793226c7bfdca957059dbafe5e48d2 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Wed, 6 Nov 2024 15:23:35 -0500 Subject: [PATCH 17/73] working fb button --- packages/sdk-react/package.json | 5 +- .../sdk-react/src/components/auth/Apple.tsx | 5 +- .../sdk-react/src/components/auth/Auth.tsx | 14 +- .../src/components/auth/Facebook.tsx | 84 +++-- .../src/components/auth/facebook-utils.ts | 12 +- pnpm-lock.yaml | 331 ++++++++---------- 6 files changed, 215 insertions(+), 236 deletions(-) diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index 21da51593..d4015abb6 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -59,12 +59,9 @@ "@turnkey/wallet-stamper": "workspace:*", "usehooks-ts": "^3.1.0", "@turnkey/sdk-server": "workspace:*", - "axios": "^1.7.7", "next": "^15.0.2", "react-apple-login": "^1.1.6", - "react-international-phone": "^4.3.0", - "react-social-login-buttons": "^4.1.0", - "reactjs-social-login": "^2.6.3" + "react-international-phone": "^4.3.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" diff --git a/packages/sdk-react/src/components/auth/Apple.tsx b/packages/sdk-react/src/components/auth/Apple.tsx index 8d7668d0b..1179aa0ef 100644 --- a/packages/sdk-react/src/components/auth/Apple.tsx +++ b/packages/sdk-react/src/components/auth/Apple.tsx @@ -7,7 +7,6 @@ import styles from "./Apple.module.css"; interface AppleAuthButtonProps { iframePublicKey: string; clientId: string; - redirectURI: string; onSuccess: (response: any) => void; } declare global { @@ -17,9 +16,9 @@ declare global { } -const AppleAuthButton: React.FC = ({ iframePublicKey, onSuccess, clientId, redirectURI }) => { +const AppleAuthButton: React.FC = ({ iframePublicKey, onSuccess, clientId }) => { const [appleSDKLoaded, setAppleSDKLoaded] = useState(false); - + const redirectURI = process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI! useEffect(() => { const loadAppleSDK = () => { const script = document.createElement("script"); diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 289182d89..a36ec315e 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -28,6 +28,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const [phone, setPhone] = useState(""); const [otpId, setOtpId] = useState(null); const [step, setStep] = useState("auth"); + const [loading, setLoading] = useState(false); const [suborgId, setSuborgId] = useState(""); const [resendText, setResendText] = useState("Re-send Code"); @@ -146,6 +147,15 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { } }; + const handleFacebookLogin = async (response:any) => { + const facebookToken = response?.id_token; + if (facebookToken) { + await handleOAuthLogin(facebookToken, "Facebook OIDC"); + } else { + setError("Facebook login failed: No token returned."); + } + }; + const handleOAuthLogin = async (credential: string, providerName: string) => { const suborgId = await handleGetOrCreateSuborg("OIDC_TOKEN", credential, { oauthProviders: [{ providerName, oidcToken: credential }] }); const oauthResponse = await oauth({ suborgID: suborgId, oidcToken: credential, targetPublicKey: authIframeClient?.iframePublicKey! }); @@ -265,7 +275,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { {!otpId && authConfig.appleEnabled && authIframeClient && (
- +
)} @@ -274,7 +284,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { {!otpId && authConfig.facebookEnabled && authIframeClient && (
- +
)} diff --git a/packages/sdk-react/src/components/auth/Facebook.tsx b/packages/sdk-react/src/components/auth/Facebook.tsx index 3a6d245c1..dc64ffb5a 100644 --- a/packages/sdk-react/src/components/auth/Facebook.tsx +++ b/packages/sdk-react/src/components/auth/Facebook.tsx @@ -1,7 +1,6 @@ "use client"; -import { useEffect } from "react"; -import { useSearchParams } from "next/navigation"; +import { useState } from "react"; import { SiFacebook } from "@icons-pack/react-simple-icons"; import styles from "./Facebook.module.css"; import { exchangeCodeForToken, generateChallengePair } from "./facebook-utils"; @@ -11,54 +10,85 @@ import { bytesToHex } from "@noble/hashes/utils"; interface FacebookAuthButtonProps { iframePublicKey: string; clientId: string; - authAPIVersion: string; - redirectURI: string; onSuccess: (response: any) => void; } -const FacebookAuthButton: React.FC = ({ iframePublicKey, onSuccess, clientId, authAPIVersion, redirectURI }) => { - const searchParams = useSearchParams(); +const FacebookAuthButton: React.FC = ({ + iframePublicKey, + onSuccess, + clientId, +}) => { + const [loading, setLoading] = useState(false); + const [tokenExchanged, setTokenExchanged] = useState(false); + const redirectURI = process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI!; const initiateFacebookLogin = async () => { const { verifier, codeChallenge } = await generateChallengePair(); - const codeChallengeMethod = "sha256"; + sessionStorage.setItem("facebook_verifier", verifier); const params = new URLSearchParams({ client_id: clientId, redirect_uri: redirectURI, state: verifier, code_challenge: codeChallenge, - code_challenge_method: codeChallengeMethod, - nonce: bytesToHex(sha256(iframePublicKey || "")), + code_challenge_method: "S256", + nonce: bytesToHex(sha256(iframePublicKey)), scope: "openid", response_type: "code", }); - const facebookOAuthURL = `https://www.facebook.com/v${authAPIVersion}/dialog/oauth?${params.toString()}`; - window.location.href = facebookOAuthURL; - }; + const facebookOAuthURL = `https://www.facebook.com/v11.0/dialog/oauth?${params.toString()}`; + + const popup = window.open(facebookOAuthURL, "_blank", "width=500,height=600"); + + if (popup) { + const interval = setInterval(async () => { + try { + if (popup.closed) { + clearInterval(interval); + return; + } + + const popupUrl = new URL(popup.location.href); + const authCode = popupUrl.searchParams.get("code"); - const handleTokenExchange = async (authCode: string, authState: string) => { - console.log("HERE") - const verifier = authState; - const tokenData = await exchangeCodeForToken(clientId, redirectURI, authCode, verifier); - console.log(tokenData) - onSuccess(tokenData); + if (authCode) { + popup.close(); + clearInterval(interval); + handleTokenExchange(authCode); + } + } catch (error) { + // Ignore cross-origin errors until the popup redirects to the same origin + } + }, 250); + } }; - useEffect(() => { - const code = searchParams.get("code"); - const state = searchParams.get("state"); + const handleTokenExchange = async (authCode: string) => { + const verifier = sessionStorage.getItem("facebook_verifier"); + if (!verifier || tokenExchanged) { + console.error("No verifier found in sessionStorage or token exchange already completed"); + return; + } + + setLoading(true); - if (code && state) { - handleTokenExchange(code, state); + try { + const tokenData = await exchangeCodeForToken(clientId, redirectURI, authCode, verifier); + sessionStorage.removeItem("facebook_verifier"); // Remove verifier after use + onSuccess(tokenData); + setTokenExchanged(true); // Prevent further exchanges + } catch (error) { + console.error("Error during token exchange:", error); + } finally { + setLoading(false); } - }, [searchParams]); + }; return ( -
- - Continue with Facebook +
{} : initiateFacebookLogin}> + + Continue with Facebook
); }; diff --git a/packages/sdk-react/src/components/auth/facebook-utils.ts b/packages/sdk-react/src/components/auth/facebook-utils.ts index 31df7f557..130acb009 100644 --- a/packages/sdk-react/src/components/auth/facebook-utils.ts +++ b/packages/sdk-react/src/components/auth/facebook-utils.ts @@ -3,21 +3,11 @@ import crypto from "crypto"; export async function generateChallengePair() { const verifier = crypto.randomBytes(32).toString("base64url"); - const codeChallenge = await verifierSegmentToChallenge(verifier); + const codeChallenge = crypto.createHash("sha256").update(verifier).digest("base64url"); return { verifier, codeChallenge }; } -async function verifierSegmentToChallenge(segment: string) { - const salt = process.env.FACEBOOK_SECRET_SALT! - const saltedVerifier = segment + salt; - return crypto.createHash("sha256").update(saltedVerifier).digest("base64url"); -} - export async function exchangeCodeForToken(clientId: any, redirectURI: any, authCode: any, verifier: any) { - console.log(clientId) - console.log(redirectURI) - console.log(authCode) - console.log(verifier) const response = await fetch(`https://graph.facebook.com/v11.0/oauth/access_token`, { method: "POST", headers: { "Content-Type": "application/json" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a4f3c0bd..96e767b31 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1820,9 +1820,6 @@ importers: '@turnkey/wallet-stamper': specifier: workspace:* version: link:../wallet-stamper - axios: - specifier: ^1.7.7 - version: 1.7.7 next: specifier: ^15.0.2 version: 15.0.2(@babel/core@7.24.5)(react-dom@18.3.1)(react@18.2.0) @@ -1832,12 +1829,6 @@ importers: react-international-phone: specifier: ^4.3.0 version: 4.3.0(react@18.2.0) - react-social-login-buttons: - specifier: ^4.1.0 - version: 4.1.0(react@18.2.0) - reactjs-social-login: - specifier: ^2.6.3 - version: 2.6.3(react-dom@18.3.1)(react@18.2.0) usehooks-ts: specifier: ^3.1.0 version: 3.1.0(react@18.2.0) @@ -5227,7 +5218,7 @@ packages: resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} requiresBuild: true dependencies: - tslib: 2.6.3 + tslib: 2.8.1 dev: false optional: true @@ -6120,146 +6111,6 @@ packages: react: 18.2.0 dev: false - /@inquirer/checkbox@1.5.2: - resolution: {integrity: sha512-CifrkgQjDkUkWexmgYYNyB5603HhTHI91vLFeQXh6qrTKiCMVASol01Rs1cv6LP/A2WccZSRlJKZhbaBIs/9ZA==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.5.5 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - figures: 3.2.0 - dev: false - - /@inquirer/confirm@2.0.17: - resolution: {integrity: sha512-EqzhGryzmGpy2aJf6LxJVhndxYmFs+m8cxXzf8nejb1DE3sabf6mUgBcp4J0jAUEiAcYzqmkqRr7LPFh/WdnXA==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.5.5 - chalk: 4.1.2 - dev: false - - /@inquirer/core@2.3.1: - resolution: {integrity: sha512-faYAYnIfdEuns3jGKykaog5oUqFiEVbCx9nXGZfUhyEEpKcHt5bpJfZTb3eOBQKo8I/v4sJkZeBHmFlSZQuBCw==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/type': 1.5.5 - '@types/mute-stream': 0.0.1 - '@types/node': 20.17.9 - '@types/wrap-ansi': 3.0.0 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-spinners: 2.9.2 - cli-width: 4.1.0 - figures: 3.2.0 - mute-stream: 1.0.0 - run-async: 3.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - dev: false - - /@inquirer/core@6.0.0: - resolution: {integrity: sha512-fKi63Khkisgda3ohnskNf5uZJj+zXOaBvOllHsOkdsXRA/ubQLJQrZchFFi57NKbZzkTunXiBMdvWOv71alonw==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/type': 1.5.5 - '@types/mute-stream': 0.0.4 - '@types/node': 20.17.9 - '@types/wrap-ansi': 3.0.0 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-spinners: 2.9.2 - cli-width: 4.1.0 - figures: 3.2.0 - mute-stream: 1.0.0 - run-async: 3.0.0 - signal-exit: 4.1.0 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - dev: false - - /@inquirer/editor@1.2.15: - resolution: {integrity: sha512-gQ77Ls09x5vKLVNMH9q/7xvYPT6sIs5f7URksw+a2iJZ0j48tVS6crLqm2ugG33tgXHIwiEqkytY60Zyh5GkJQ==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.5.5 - chalk: 4.1.2 - external-editor: 3.1.0 - dev: false - - /@inquirer/expand@1.1.16: - resolution: {integrity: sha512-TGLU9egcuo+s7PxphKUCnJnpCIVY32/EwPCLLuu+gTvYiD8hZgx8Z2niNQD36sa6xcfpdLY6xXDBiL/+g1r2XQ==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.5.5 - chalk: 4.1.2 - figures: 3.2.0 - dev: false - - /@inquirer/input@1.2.16: - resolution: {integrity: sha512-Ou0LaSWvj1ni+egnyQ+NBtfM1885UwhRCMtsRt2bBO47DoC1dwtCa+ZUNgrxlnCHHF0IXsbQHYtIIjFGAavI4g==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.5.5 - chalk: 4.1.2 - dev: false - - /@inquirer/password@1.1.16: - resolution: {integrity: sha512-aZYZVHLUXZ2gbBot+i+zOJrks1WaiI95lvZCn1sKfcw6MtSSlYC8uDX8sTzQvAsQ8epHoP84UNvAIT0KVGOGqw==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.5.5 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - dev: false - - /@inquirer/prompts@2.1.0: - resolution: {integrity: sha512-YpfNrTjItyB9CYJUdIBp+9hRUu0e5JR//qC55gRrKjpGZP7CwrMvFJiS1LMZNG5vzd61CDxpOvNmkHcmMe6QmA==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/checkbox': 1.5.2 - '@inquirer/confirm': 2.0.17 - '@inquirer/core': 2.3.1 - '@inquirer/editor': 1.2.15 - '@inquirer/expand': 1.1.16 - '@inquirer/input': 1.2.16 - '@inquirer/password': 1.1.16 - '@inquirer/rawlist': 1.2.16 - '@inquirer/select': 1.3.3 - dev: false - - /@inquirer/rawlist@1.2.16: - resolution: {integrity: sha512-pZ6TRg2qMwZAOZAV6TvghCtkr53dGnK29GMNQ3vMZXSNguvGqtOVc4j/h1T8kqGJFagjyfBZhUPGwNS55O5qPQ==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.5.5 - chalk: 4.1.2 - dev: false - - /@inquirer/select@1.3.3: - resolution: {integrity: sha512-RzlRISXWqIKEf83FDC9ZtJ3JvuK1l7aGpretf41BCWYrvla2wU8W8MTRNMiPrPJ+1SIqrRC1nZdZ60hD9hRXLg==} - engines: {node: '>=14.18.0'} - dependencies: - '@inquirer/core': 6.0.0 - '@inquirer/type': 1.5.5 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - figures: 3.2.0 - dev: false - - /@inquirer/type@1.5.5: - resolution: {integrity: sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==} - engines: {node: '>=18'} - dependencies: - mute-stream: 1.0.0 - dev: false /@img/sharp-darwin-arm64@0.33.5: resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -6440,6 +6291,147 @@ packages: dev: false optional: true + /@inquirer/checkbox@1.5.2: + resolution: {integrity: sha512-CifrkgQjDkUkWexmgYYNyB5603HhTHI91vLFeQXh6qrTKiCMVASol01Rs1cv6LP/A2WccZSRlJKZhbaBIs/9ZA==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + figures: 3.2.0 + dev: false + + /@inquirer/confirm@2.0.17: + resolution: {integrity: sha512-EqzhGryzmGpy2aJf6LxJVhndxYmFs+m8cxXzf8nejb1DE3sabf6mUgBcp4J0jAUEiAcYzqmkqRr7LPFh/WdnXA==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + chalk: 4.1.2 + dev: false + + /@inquirer/core@2.3.1: + resolution: {integrity: sha512-faYAYnIfdEuns3jGKykaog5oUqFiEVbCx9nXGZfUhyEEpKcHt5bpJfZTb3eOBQKo8I/v4sJkZeBHmFlSZQuBCw==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/type': 1.5.5 + '@types/mute-stream': 0.0.1 + '@types/node': 20.17.9 + '@types/wrap-ansi': 3.0.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-spinners: 2.9.2 + cli-width: 4.1.0 + figures: 3.2.0 + mute-stream: 1.0.0 + run-async: 3.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: false + + /@inquirer/core@6.0.0: + resolution: {integrity: sha512-fKi63Khkisgda3ohnskNf5uZJj+zXOaBvOllHsOkdsXRA/ubQLJQrZchFFi57NKbZzkTunXiBMdvWOv71alonw==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/type': 1.5.5 + '@types/mute-stream': 0.0.4 + '@types/node': 20.17.9 + '@types/wrap-ansi': 3.0.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-spinners: 2.9.2 + cli-width: 4.1.0 + figures: 3.2.0 + mute-stream: 1.0.0 + run-async: 3.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: false + + /@inquirer/editor@1.2.15: + resolution: {integrity: sha512-gQ77Ls09x5vKLVNMH9q/7xvYPT6sIs5f7URksw+a2iJZ0j48tVS6crLqm2ugG33tgXHIwiEqkytY60Zyh5GkJQ==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + chalk: 4.1.2 + external-editor: 3.1.0 + dev: false + + /@inquirer/expand@1.1.16: + resolution: {integrity: sha512-TGLU9egcuo+s7PxphKUCnJnpCIVY32/EwPCLLuu+gTvYiD8hZgx8Z2niNQD36sa6xcfpdLY6xXDBiL/+g1r2XQ==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + chalk: 4.1.2 + figures: 3.2.0 + dev: false + + /@inquirer/input@1.2.16: + resolution: {integrity: sha512-Ou0LaSWvj1ni+egnyQ+NBtfM1885UwhRCMtsRt2bBO47DoC1dwtCa+ZUNgrxlnCHHF0IXsbQHYtIIjFGAavI4g==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + chalk: 4.1.2 + dev: false + + /@inquirer/password@1.1.16: + resolution: {integrity: sha512-aZYZVHLUXZ2gbBot+i+zOJrks1WaiI95lvZCn1sKfcw6MtSSlYC8uDX8sTzQvAsQ8epHoP84UNvAIT0KVGOGqw==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + dev: false + + /@inquirer/prompts@2.1.0: + resolution: {integrity: sha512-YpfNrTjItyB9CYJUdIBp+9hRUu0e5JR//qC55gRrKjpGZP7CwrMvFJiS1LMZNG5vzd61CDxpOvNmkHcmMe6QmA==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/checkbox': 1.5.2 + '@inquirer/confirm': 2.0.17 + '@inquirer/core': 2.3.1 + '@inquirer/editor': 1.2.15 + '@inquirer/expand': 1.1.16 + '@inquirer/input': 1.2.16 + '@inquirer/password': 1.1.16 + '@inquirer/rawlist': 1.2.16 + '@inquirer/select': 1.3.3 + dev: false + + /@inquirer/rawlist@1.2.16: + resolution: {integrity: sha512-pZ6TRg2qMwZAOZAV6TvghCtkr53dGnK29GMNQ3vMZXSNguvGqtOVc4j/h1T8kqGJFagjyfBZhUPGwNS55O5qPQ==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + chalk: 4.1.2 + dev: false + + /@inquirer/select@1.3.3: + resolution: {integrity: sha512-RzlRISXWqIKEf83FDC9ZtJ3JvuK1l7aGpretf41BCWYrvla2wU8W8MTRNMiPrPJ+1SIqrRC1nZdZ60hD9hRXLg==} + engines: {node: '>=14.18.0'} + dependencies: + '@inquirer/core': 6.0.0 + '@inquirer/type': 1.5.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + figures: 3.2.0 + dev: false + + /@inquirer/type@1.5.5: + resolution: {integrity: sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==} + engines: {node: '>=18'} + dependencies: + mute-stream: 1.0.0 + dev: false + /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -14363,16 +14355,6 @@ packages: - debug dev: false - /axios@1.7.7: - resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} - dependencies: - follow-redirects: 1.15.4(debug@4.3.4) - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - dev: false - /axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} dependencies: @@ -22363,14 +22345,6 @@ packages: react-is: 18.3.1 dev: false - /react-social-login-buttons@4.1.0(react@18.2.0): - resolution: {integrity: sha512-PoZKH6h3eKGgcviFC5Sh50QDVQ5KhFf7MrPTFBU/wwPNn0DVNEb5km+T7z83+CiM24Z8qV/H82wHZ4OE6gcawQ==} - peerDependencies: - react: ^16.0.0 || ^17.x || ^18.x - dependencies: - react: 18.2.0 - dev: false - /react-style-singleton@2.2.1(@types/react@18.2.14)(react@18.2.0): resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} @@ -22455,17 +22429,6 @@ packages: loose-envify: 1.4.0 dev: false - /reactjs-social-login@2.6.3(react-dom@18.3.1)(react@18.2.0): - resolution: {integrity: sha512-i/pcyJPtlVpHoPRr/j5/KjaP/GNxAInnnHDk0PD2Zo0B3cMdvr0ZiGC/GMjOKAAgKCujyniDf/OiZ7c6MrOLQg==} - engines: {node: '>=10'} - peerDependencies: - react: ^16 || ^17 || ^18 - react-dom: ^16 || ^17 || ^18 - dependencies: - react: 18.2.0 - react-dom: 18.3.1(react@18.2.0) - dev: false - /read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: @@ -23177,8 +23140,6 @@ packages: kind-of: 6.0.3 dev: false -<<<<<<< HEAD -======= /sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -23210,14 +23171,6 @@ packages: dev: false optional: true - /shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} - dependencies: - shebang-regex: 1.0.0 - dev: true - ->>>>>>> 08f3aa86 (progress on facebook button) /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} From d84280cd006557607e6ca66af1550ef2dc1d6187 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Wed, 6 Nov 2024 16:46:44 -0500 Subject: [PATCH 18/73] working oauth --- package.json | 5 +- packages/sdk-react/package.json | 5 ++ .../src/components/auth/Auth.module.css | 53 +++++++++++++++- .../sdk-react/src/components/auth/Auth.tsx | 45 +++++++++++++- .../src/components/auth/Facebook.tsx | 5 +- pnpm-lock.yaml | 61 +++++++------------ rollup.config.base.mjs | 3 +- 7 files changed, 125 insertions(+), 52 deletions(-) diff --git a/package.json b/package.json index 9fe576324..1704d9a48 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "rollup": "^4.22.4", "rollup-plugin-node-externals": "^6.1.2", "rollup-plugin-postcss": "^4.0.2", - "rollup-plugin-preserve-directives": "^0.4.0", + "rollup-preserve-directives": "^1.1.2", "tsx": "^3.12.7", "typescript": "^5.1.4" }, @@ -64,8 +64,5 @@ "secp256k1": ">=4.0.4", "cross-spawn": ">=7.0.5" } - }, - "dependencies": { - "rollup-preserve-directives": "^1.1.2" } } diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index d4015abb6..3dcdb040c 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -66,10 +66,15 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, + "devDependencies": { "@types/react": "^18.2.75", "react": "^18.2.0" }, + "overrides": { + "react": "18.2.0", + "@types/react": "18.2.75" + }, "engines": { "node": ">=18.0.0" } diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index c2483ccd2..7f75c86b7 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -1,6 +1,55 @@ -/* Auth Card */ +.authCardLoading { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + max-width: 450px; + min-width: 375px; + height: 200px; + margin: 0 auto; + padding: 20px; + background: var(--Greyscale-20, #F5F7FB); + border: 1px solid var(--Greyscale-100, #EBEDF2); + border-radius: 15px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + position: relative; +} + +.verifyingText { + font-weight: bold; + font-size: 18px; + color: var(--Greyscale-900, #2B2F33); + margin-bottom: 20px; + text-align: center; +} + +.loadingWrapper { + position: relative; + width: 100px; + height: 100px; + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 20px; +} + +.circularProgress { + position: absolute; + color: #007AFF; +} + +.oauthIcon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-size: 60px; + color: gray; +} + .authCard { - position: relative; /* Ensure contained elements respect authCard boundaries */ + position: relative; width: 100%; max-width: 450px; min-width: 375px; diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index a36ec315e..3050ea8e9 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -7,6 +7,10 @@ import OTPInput from "./OtpInput"; import GoogleAuthButton from "./Google"; import AppleAuthButton from "./Apple"; import FacebookAuthButton from "./Facebook"; +import { CircularProgress } from "@mui/material"; +import GoogleIcon from "@mui/icons-material/Google"; +import FacebookIcon from "@mui/icons-material/Facebook"; +import AppleIcon from "@mui/icons-material/Apple"; interface AuthProps { onHandleAuthSuccess: () => Promise; @@ -28,7 +32,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const [phone, setPhone] = useState(""); const [otpId, setOtpId] = useState(null); const [step, setStep] = useState("auth"); - const [loading, setLoading] = useState(false); + const [oauthLoading, setOauthLoading] = useState(""); const [suborgId, setSuborgId] = useState(""); const [resendText, setResendText] = useState("Re-send Code"); @@ -137,11 +141,13 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const handleGoogleLogin = async (response: any) => { const credential = response.credential; + setOauthLoading("Google") await handleOAuthLogin(credential, "Google OIDC"); }; const handleAppleLogin = async (response: any) => { const appleToken = response.authorization?.id_token; + setOauthLoading("Apple") if (appleToken) { await handleOAuthLogin(appleToken, "Apple OIDC"); } @@ -149,6 +155,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const handleFacebookLogin = async (response:any) => { const facebookToken = response?.id_token; + setOauthLoading("Facebook") if (facebookToken) { await handleOAuthLogin(facebookToken, "Facebook OIDC"); } else { @@ -173,6 +180,37 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { }; return ( +
+ {oauthLoading != "" ? +
+

Verifying with {oauthLoading}

+
+ + {oauthLoading === "Google" && } + {oauthLoading === "Facebook" && } + {oauthLoading === "Apple" && } +
+
+ Powered by + + + + + + + + + + + + + + +
+
+ + : +

{otpId ? "Enter verification code" : "Log in or sign up"}

@@ -284,7 +322,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { {!otpId && authConfig.facebookEnabled && authIframeClient && (
- +
)} @@ -346,6 +384,9 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => {
+} +
+ ); }; diff --git a/packages/sdk-react/src/components/auth/Facebook.tsx b/packages/sdk-react/src/components/auth/Facebook.tsx index dc64ffb5a..392f13bc1 100644 --- a/packages/sdk-react/src/components/auth/Facebook.tsx +++ b/packages/sdk-react/src/components/auth/Facebook.tsx @@ -18,7 +18,6 @@ const FacebookAuthButton: React.FC = ({ onSuccess, clientId, }) => { - const [loading, setLoading] = useState(false); const [tokenExchanged, setTokenExchanged] = useState(false); const redirectURI = process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI!; @@ -71,7 +70,6 @@ const FacebookAuthButton: React.FC = ({ return; } - setLoading(true); try { const tokenData = await exchangeCodeForToken(clientId, redirectURI, authCode, verifier); @@ -81,12 +79,11 @@ const FacebookAuthButton: React.FC = ({ } catch (error) { console.error("Error during token exchange:", error); } finally { - setLoading(false); } }; return ( -
{} : initiateFacebookLogin}> +
Continue with Facebook
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96e767b31..18215f2be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,10 +19,6 @@ overrides: importers: .: - dependencies: - rollup-preserve-directives: - specifier: ^1.1.2 - version: 1.1.2(rollup@4.22.4) devDependencies: '@changesets/cli': specifier: ^2.27.5 @@ -63,9 +59,9 @@ importers: rollup-plugin-postcss: specifier: ^4.0.2 version: 4.0.2(postcss@8.4.38) - rollup-plugin-preserve-directives: - specifier: ^0.4.0 - version: 0.4.0(rollup@4.22.4) + rollup-preserve-directives: + specifier: ^1.1.2 + version: 1.1.2(rollup@4.22.4) tsx: specifier: ^3.12.7 version: 3.12.7 @@ -10518,26 +10514,12 @@ packages: rollup: 4.22.4 dev: true - /@rollup/pluginutils@5.1.3(rollup@4.22.4): - resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - dependencies: - '@types/estree': 1.0.5 - estree-walker: 2.0.2 - picomatch: 4.0.2 - rollup: 4.22.4 - dev: true - /@rollup/rollup-android-arm-eabi@4.22.4: resolution: {integrity: sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==} cpu: [arm] os: [android] requiresBuild: true + dev: true optional: true /@rollup/rollup-android-arm64@4.22.4: @@ -10545,6 +10527,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: true optional: true /@rollup/rollup-darwin-arm64@4.22.4: @@ -10552,6 +10535,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: true optional: true /@rollup/rollup-darwin-x64@4.22.4: @@ -10559,6 +10543,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-arm-gnueabihf@4.22.4: @@ -10566,6 +10551,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-arm-musleabihf@4.22.4: @@ -10573,6 +10559,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-arm64-gnu@4.22.4: @@ -10580,6 +10567,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-arm64-musl@4.22.4: @@ -10587,6 +10575,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-powerpc64le-gnu@4.22.4: @@ -10594,6 +10583,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-riscv64-gnu@4.22.4: @@ -10601,6 +10591,7 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-s390x-gnu@4.22.4: @@ -10608,6 +10599,7 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-x64-gnu@4.22.4: @@ -10615,6 +10607,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-linux-x64-musl@4.22.4: @@ -10622,6 +10615,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: true optional: true /@rollup/rollup-win32-arm64-msvc@4.22.4: @@ -10629,6 +10623,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: true optional: true /@rollup/rollup-win32-ia32-msvc@4.22.4: @@ -10636,6 +10631,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: true optional: true /@rollup/rollup-win32-x64-msvc@4.22.4: @@ -10643,6 +10639,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: true optional: true /@rushstack/eslint-patch@1.9.0: @@ -12733,6 +12730,7 @@ packages: /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: true /@types/express-serve-static-core@4.17.43: resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==} @@ -19879,6 +19877,7 @@ packages: resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==} dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + dev: true /make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} @@ -21227,11 +21226,6 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - /picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} - dev: true - /pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -22826,16 +22820,6 @@ packages: - ts-node dev: true - /rollup-plugin-preserve-directives@0.4.0(rollup@4.22.4): - resolution: {integrity: sha512-gx4nBxYm5BysmEQS+e2tAMrtFxrGvk+Pe5ppafRibQi0zlW7VYAbEGk6IKDw9sJGPdFWgVTE0o4BU4cdG0Fylg==} - peerDependencies: - rollup: 2.x || 3.x || 4.x - dependencies: - '@rollup/pluginutils': 5.1.3(rollup@4.22.4) - magic-string: 0.30.14 - rollup: 4.22.4 - dev: true - /rollup-pluginutils@2.8.2: resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} dependencies: @@ -22849,7 +22833,7 @@ packages: dependencies: magic-string: 0.30.14 rollup: 4.22.4 - dev: false + dev: true /rollup@4.22.4: resolution: {integrity: sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==} @@ -22875,6 +22859,7 @@ packages: '@rollup/rollup-win32-ia32-msvc': 4.22.4 '@rollup/rollup-win32-x64-msvc': 4.22.4 fsevents: 2.3.3 + dev: true /rpc-websockets@7.5.1: resolution: {integrity: sha512-kGFkeTsmd37pHPMaHIgN1LVKXMi0JD782v4Ds9ZKtLlwdTKjn+CxM9A9/gLT2LaOuEcEFGL98h1QWQtlOIdW0w==} diff --git a/rollup.config.base.mjs b/rollup.config.base.mjs index 7131a4d38..ad9c7be67 100644 --- a/rollup.config.base.mjs +++ b/rollup.config.base.mjs @@ -2,7 +2,7 @@ import typescript from "@rollup/plugin-typescript"; import nodeExternals from "rollup-plugin-node-externals"; import path from "node:path"; import postcss from 'rollup-plugin-postcss'; -import preserveDirectives from 'rollup-plugin-preserve-directives'; +import preserveDirectives from 'rollup-preserve-directives' const getFormatConfig = (format) => { const pkgPath = path.join(process.cwd(), "package.json"); @@ -35,7 +35,6 @@ const getFormatConfig = (format) => { sourceMap: true, }, }), - // Add the preserveDirectives plugin after typescript preserveDirectives(), nodeExternals({ packagePath: pkgPath, From c55719f73d6071ab72502a53d41f45fd7d307d15 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Wed, 6 Nov 2024 17:02:50 -0500 Subject: [PATCH 19/73] prettier --- .../react-components/src/app/index.module.css | 6 +- examples/react-components/src/app/layout.tsx | 14 +- examples/react-components/src/app/page.tsx | 332 +++++----- packages/sdk-react/package.json | 1 - .../sdk-react/src/actions/createSuborg.ts | 47 +- packages/sdk-react/src/actions/getSuborgs.ts | 10 +- packages/sdk-react/src/actions/index.ts | 10 +- packages/sdk-react/src/actions/initOtpAuth.ts | 15 +- packages/sdk-react/src/actions/oauth.ts | 12 +- packages/sdk-react/src/actions/otpAuth.ts | 14 +- .../sdk-react/src/components/auth/Apple.tsx | 20 +- .../src/components/auth/Auth.module.css | 80 ++- .../sdk-react/src/components/auth/Auth.tsx | 617 +++++++++++------- .../src/components/auth/Facebook.tsx | 22 +- .../sdk-react/src/components/auth/Google.tsx | 20 +- .../src/components/auth/OTPInput.tsx | 2 +- .../src/components/auth/PhoneInput.css | 2 +- .../src/components/auth/PhoneInput.tsx | 4 +- .../src/components/auth/facebook-utils.ts | 35 +- .../sdk-react/src/components/auth/index.ts | 2 +- packages/sdk-react/src/components/index.ts | 2 +- packages/sdk-react/src/index.ts | 6 +- 22 files changed, 739 insertions(+), 534 deletions(-) diff --git a/examples/react-components/src/app/index.module.css b/examples/react-components/src/app/index.module.css index 78612df85..6d59bcb61 100644 --- a/examples/react-components/src/app/index.module.css +++ b/examples/react-components/src/app/index.module.css @@ -33,12 +33,12 @@ } .authConfigCard { - background: var(--Greyscale-20, #F5F7FB); + background: var(--Greyscale-20, #f5f7fb); padding: 16px; border-radius: 8px; width: 552px; height: 768px; - border: 1px solid var(--Greyscale-100, #EBEDF2); + border: 1px solid var(--Greyscale-100, #ebedf2); font-family: Arial, sans-serif; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); display: flex; @@ -65,7 +65,7 @@ .labelContainer { display: flex; align-items: center; - gap: 8px; + gap: 8px; } .toggleRow { diff --git a/examples/react-components/src/app/layout.tsx b/examples/react-components/src/app/layout.tsx index a8f7b5a3a..f73ac3e75 100644 --- a/examples/react-components/src/app/layout.tsx +++ b/examples/react-components/src/app/layout.tsx @@ -1,6 +1,6 @@ -"use client" +"use client"; -import '@turnkey/sdk-react/styles'; +import "@turnkey/sdk-react/styles"; import { TurnkeyProvider } from "@turnkey/sdk-react"; const turnkeyConfig = { @@ -12,17 +12,15 @@ const turnkeyConfig = { }; interface RootLayoutProps { - children: React.ReactNode + children: React.ReactNode; } function RootLayout({ children }: RootLayoutProps) { return ( - - - {children} - - + + {children} + ); } diff --git a/examples/react-components/src/app/page.tsx b/examples/react-components/src/app/page.tsx index 491c0d330..1ed98fdeb 100644 --- a/examples/react-components/src/app/page.tsx +++ b/examples/react-components/src/app/page.tsx @@ -1,12 +1,12 @@ -"use client" +"use client"; import styles from "./index.module.css"; import * as React from "react"; import { useState } from "react"; import { useTurnkey, Auth } from "@turnkey/sdk-react"; -import { Switch, Typography, IconButton } from '@mui/material'; -import ContentCopyIcon from '@mui/icons-material/ContentCopy'; -import AppsIcon from '@mui/icons-material/Apps'; +import { Switch, Typography, IconButton } from "@mui/material"; +import ContentCopyIcon from "@mui/icons-material/ContentCopy"; +import AppsIcon from "@mui/icons-material/Apps"; // Define types for config and socials interface SocialConfig { @@ -32,17 +32,16 @@ interface AuthPageProps { }; } -export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { +export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) { const { authIframeClient } = useTurnkey(); const [orgData, setOrgData] = useState(); - + const handleAuthSuccess = async () => { const whoamiResponse = await authIframeClient!.getWhoami({ organizationId: "51fb75bf-c044-4f9f-903b-4fba6bfedab9", }); - setOrgData(whoamiResponse as any) - - } + setOrgData(whoamiResponse as any); + }; const [config, setConfig] = useState({ email: true, @@ -59,7 +58,7 @@ export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { const toggleConfig = (key: keyof Config) => { setConfig((prev) => { const newConfig = { ...prev }; - + if (key === "email") { newConfig.email = !prev.email; if (!newConfig.email) { @@ -71,14 +70,14 @@ export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { } else if (key !== "socials") { newConfig[key] = !prev[key]; } - + return newConfig; }); }; - + const toggleSocials = (key: keyof SocialConfig) => { setConfig((prev) => { - if (key === 'enabled') { + if (key === "enabled") { const isEnabled = !prev.socials.enabled; return { ...prev, @@ -90,7 +89,7 @@ export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { }, }; } - + if (prev.socials.enabled) { return { ...prev, @@ -101,7 +100,7 @@ export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { }; } - return prev; + return prev; }); }; @@ -115,7 +114,7 @@ export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { facebookEnabled: config.socials.facebook, }; navigator.clipboard.writeText(JSON.stringify(authConfig, null, 2)); - alert('Auth config copied to clipboard!'); + alert("Auth config copied to clipboard!"); }; const authConfig = { @@ -124,160 +123,195 @@ export default function AuthPage({turnkeyClientConfig}: AuthPageProps) { phoneEnabled: config.phone, appleEnabled: config.socials.apple, googleEnabled: config.socials.google, - facebookEnabled: config.socials.facebook + facebookEnabled: config.socials.facebook, }; return (
- {!orgData && -
- Authentication config - -
-
-
- - Email -
- toggleConfig('email')} /> -
- -
-
- - Passkey -
- toggleConfig('passkey')} /> -
+ {!orgData && ( +
+ + Authentication config + -
-
- - Phone -
- toggleConfig('phone')} /> -
- -
-
- - Socials +
+
+
+ + Email +
+ toggleConfig("email")} + />
- toggleSocials('enabled')} /> -
- {config.socials.enabled && ( - <> -
-
- Google -
- +
+ + Passkey +
+ toggleSocials('google')} /> + }} + checked={config.passkey} + onChange={() => toggleConfig("passkey")} + /> +
+ +
+
+ + Phone
-
-
- Apple -
- toggleSocials('apple')} /> + }} + checked={config.phone} + onChange={() => toggleConfig("phone")} + /> +
+ +
+
+ + Socials
-
-
- Facebook -
- toggleSocials('facebook')} /> -
- - )} -
+ }} + checked={config.socials.enabled} + onChange={() => toggleSocials("enabled")} + /> +
-
- -
- Copy config + {config.socials.enabled && ( + <> +
+
+ Google +
+ toggleSocials("google")} + /> +
+
+
+ Apple +
+ toggleSocials("apple")} + /> +
+
+
+ Facebook +
+ toggleSocials("facebook")} + /> +
+ + )}
-
-
-} -{orgData ? -
- YOU ARE AUTHENTICATED ON TURNKEY! -
- Organization Id: {orgData.organizationId} -
-
- User Id: {orgData.userId} -
-
- Username: {orgData.username} -
-
- Organization Name: {orgData.organizationName} -
-
-: -
- - -
-} +
+ +
+ + Copy config + +
+
+
+ )} + {orgData ? ( +
+ YOU ARE AUTHENTICATED ON TURNKEY! +
+ Organization Id: {orgData.organizationId} +
+
+ User Id: {orgData.userId} +
+
+ Username: {orgData.username} +
+
+ Organization Name: {orgData.organizationName} +
+
+ ) : ( +
+ +
+ )}
); } - diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index 3dcdb040c..407f93f1d 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -66,7 +66,6 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" }, - "devDependencies": { "@types/react": "^18.2.75", "react": "^18.2.0" diff --git a/packages/sdk-react/src/actions/createSuborg.ts b/packages/sdk-react/src/actions/createSuborg.ts index 00cdb0faa..b6e1b0e79 100644 --- a/packages/sdk-react/src/actions/createSuborg.ts +++ b/packages/sdk-react/src/actions/createSuborg.ts @@ -1,4 +1,4 @@ -'use server' +"use server"; import { Turnkey } from "@turnkey/sdk-server"; @@ -7,14 +7,13 @@ type CreateSuborgRequest = { email?: string; phoneNumber?: string; passkey?: Passkey; - }; type Passkey = { - authenticatorName: string, - challenge: any, - attestation: any, - }; + authenticatorName: string; + challenge: any; + attestation: any; +}; type Provider = { providerName: string; @@ -26,29 +25,31 @@ type CreateSuborgResponse = { }; export async function createSuborg( - request: CreateSuborgRequest, + request: CreateSuborgRequest ): Promise { const turnkeyClient = new Turnkey({ apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE - apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE - }) + apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + }); try { - const suborgResponse = await turnkeyClient.apiClient().createSubOrganization({ - subOrganizationName: `suborg-${String(Date.now())}`, - rootQuorumThreshold: 1, - rootUsers: [ - { - userName: request.email ?? "", - userEmail: request.email ?? "", - userPhoneNumber: request.phoneNumber ?? "", - apiKeys: [], - authenticators: request.passkey ? [request.passkey] : [], - oauthProviders: request.oauthProviders ?? [], - }, - ], - }); + const suborgResponse = await turnkeyClient + .apiClient() + .createSubOrganization({ + subOrganizationName: `suborg-${String(Date.now())}`, + rootQuorumThreshold: 1, + rootUsers: [ + { + userName: request.email ?? "", + userEmail: request.email ?? "", + userPhoneNumber: request.phoneNumber ?? "", + apiKeys: [], + authenticators: request.passkey ? [request.passkey] : [], + oauthProviders: request.oauthProviders ?? [], + }, + ], + }); const { subOrganizationId } = suborgResponse; if (!subOrganizationId) { diff --git a/packages/sdk-react/src/actions/getSuborgs.ts b/packages/sdk-react/src/actions/getSuborgs.ts index 1da084e2b..831700283 100644 --- a/packages/sdk-react/src/actions/getSuborgs.ts +++ b/packages/sdk-react/src/actions/getSuborgs.ts @@ -1,4 +1,4 @@ -'use server' +"use server"; import { Turnkey } from "@turnkey/sdk-server"; @@ -12,14 +12,14 @@ type GetSuborgsResponse = { }; export async function getSuborgs( - request: GetSuborgsRequest, + request: GetSuborgsRequest ): Promise { const turnkeyClient = new Turnkey({ apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE - apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE - }) + apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + }); try { const response = await turnkeyClient.apiClient().getSubOrgIds({ organizationId: turnkeyClient.config.defaultOrganizationId, @@ -33,6 +33,6 @@ export async function getSuborgs( return { organizationIds: response.organizationIds }; } catch (e) { console.error(e); - return + return; } } diff --git a/packages/sdk-react/src/actions/index.ts b/packages/sdk-react/src/actions/index.ts index b470036d6..e51b3fb15 100644 --- a/packages/sdk-react/src/actions/index.ts +++ b/packages/sdk-react/src/actions/index.ts @@ -1,7 +1,7 @@ // actions that need to be run on the server side (i.e Activities signed by the Parent Organization) -export {createSuborg} from './createSuborg' -export {getSuborgs} from './getSuborgs' -export {initOtpAuth} from './initOtpAuth' -export {oauth} from './oauth' -export {otpAuth} from './otpAuth' \ No newline at end of file +export { createSuborg } from "./createSuborg"; +export { getSuborgs } from "./getSuborgs"; +export { initOtpAuth } from "./initOtpAuth"; +export { oauth } from "./oauth"; +export { otpAuth } from "./otpAuth"; diff --git a/packages/sdk-react/src/actions/initOtpAuth.ts b/packages/sdk-react/src/actions/initOtpAuth.ts index bd513d2b7..201d7c509 100644 --- a/packages/sdk-react/src/actions/initOtpAuth.ts +++ b/packages/sdk-react/src/actions/initOtpAuth.ts @@ -1,6 +1,6 @@ -'use server' +"use server"; -import {Turnkey } from "@turnkey/sdk-server"; +import { Turnkey } from "@turnkey/sdk-server"; type InitOtpAuthRequest = { suborgID: string; @@ -12,22 +12,21 @@ type InitOtpAuthResponse = { otpId: string; }; - export async function initOtpAuth( - request: InitOtpAuthRequest, + request: InitOtpAuthRequest ): Promise { const turnkeyClient = new Turnkey({ apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE - apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE - }) + apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + }); try { const initOtpAuthResponse = await turnkeyClient.apiClient().initOtpAuth({ contact: request.contact, otpType: request.otpType, - organizationId: request.suborgID + organizationId: request.suborgID, }); const { otpId } = initOtpAuthResponse; @@ -37,6 +36,6 @@ export async function initOtpAuth( return { otpId }; } catch (e) { - return + return; } } diff --git a/packages/sdk-react/src/actions/oauth.ts b/packages/sdk-react/src/actions/oauth.ts index 7fb77567d..abb3a71e1 100644 --- a/packages/sdk-react/src/actions/oauth.ts +++ b/packages/sdk-react/src/actions/oauth.ts @@ -1,4 +1,4 @@ -'use server' +"use server"; import { Turnkey } from "@turnkey/sdk-server"; @@ -15,14 +15,14 @@ type OauthResponse = { }; export async function oauth( - request: OauthRequest, + request: OauthRequest ): Promise { const turnkeyClient = new Turnkey({ apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE - apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE - }) + apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + }); try { const oauthResponse = await turnkeyClient.apiClient().oauth({ oidcToken: request.oidcToken, @@ -32,7 +32,9 @@ export async function oauth( const { credentialBundle, apiKeyId, userId } = oauthResponse; if (!credentialBundle || !apiKeyId || !userId) { - throw new Error("Expected non-null values for credentialBundle, apiKeyId, and userId."); + throw new Error( + "Expected non-null values for credentialBundle, apiKeyId, and userId." + ); } return { credentialBundle, apiKeyId, userId }; diff --git a/packages/sdk-react/src/actions/otpAuth.ts b/packages/sdk-react/src/actions/otpAuth.ts index 0f29982ed..f571c9b06 100644 --- a/packages/sdk-react/src/actions/otpAuth.ts +++ b/packages/sdk-react/src/actions/otpAuth.ts @@ -1,4 +1,4 @@ -'use server' +"use server"; import { Turnkey } from "@turnkey/sdk-server"; @@ -16,20 +16,20 @@ type OtpAuthResponse = { }; export async function otpAuth( - request: OtpAuthRequest, + request: OtpAuthRequest ): Promise { const turnkeyClient = new Turnkey({ apiBaseUrl: process.env.NEXT_PUBLIC_BASE_URL!, defaultOrganizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, apiPrivateKey: process.env.TURNKEY_API_PRIVATE_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE - apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE - }) + apiPublicKey: process.env.TURNKEY_API_PUBLIC_KEY!, // DO NOT EXPOSE THESE TO YOUR CLIENT SIDE CODE + }); try { const otpAuthResponse = await turnkeyClient.apiClient().otpAuth({ otpId: request.otpId, otpCode: request.otpCode, targetPublicKey: request.targetPublicKey, - organizationId: request.suborgID + organizationId: request.suborgID, }); const { credentialBundle, apiKeyId, userId } = otpAuthResponse; @@ -39,8 +39,6 @@ export async function otpAuth( return { credentialBundle, apiKeyId, userId }; } catch (e) { - return + return; } } - - diff --git a/packages/sdk-react/src/components/auth/Apple.tsx b/packages/sdk-react/src/components/auth/Apple.tsx index 1179aa0ef..e86989ce0 100644 --- a/packages/sdk-react/src/components/auth/Apple.tsx +++ b/packages/sdk-react/src/components/auth/Apple.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { sha256 } from "@noble/hashes/sha2"; import { bytesToHex } from "@noble/hashes/utils"; import AppleLogin from "react-apple-login"; -import { SiApple } from "@icons-pack/react-simple-icons" +import { SiApple } from "@icons-pack/react-simple-icons"; import styles from "./Apple.module.css"; interface AppleAuthButtonProps { iframePublicKey: string; @@ -15,14 +15,18 @@ declare global { } } - -const AppleAuthButton: React.FC = ({ iframePublicKey, onSuccess, clientId }) => { +const AppleAuthButton: React.FC = ({ + iframePublicKey, + onSuccess, + clientId, +}) => { const [appleSDKLoaded, setAppleSDKLoaded] = useState(false); - const redirectURI = process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI! + const redirectURI = process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI!; useEffect(() => { const loadAppleSDK = () => { const script = document.createElement("script"); - script.src = "https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"; + script.src = + "https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"; script.onload = () => setAppleSDKLoaded(true); script.onerror = () => console.error("Failed to load AppleID SDK"); document.body.appendChild(script); @@ -48,9 +52,9 @@ const AppleAuthButton: React.FC = ({ iframePublicKey, onSu responseMode="fragment" render={({ onClick }) => (
- - Continue with Apple -
+ + Continue with Apple +
)} callback={(response) => { if (response.error) { diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index 7f75c86b7..dbd1168c2 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -9,8 +9,8 @@ height: 200px; margin: 0 auto; padding: 20px; - background: var(--Greyscale-20, #F5F7FB); - border: 1px solid var(--Greyscale-100, #EBEDF2); + background: var(--Greyscale-20, #f5f7fb); + border: 1px solid var(--Greyscale-100, #ebedf2); border-radius: 15px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); position: relative; @@ -19,7 +19,7 @@ .verifyingText { font-weight: bold; font-size: 18px; - color: var(--Greyscale-900, #2B2F33); + color: var(--Greyscale-900, #2b2f33); margin-bottom: 20px; text-align: center; } @@ -36,7 +36,7 @@ .circularProgress { position: absolute; - color: #007AFF; + color: #007aff; } .oauthIcon { @@ -44,7 +44,7 @@ top: 50%; left: 50%; transform: translate(-50%, -50%); - font-size: 60px; + font-size: 60px; color: gray; } @@ -55,8 +55,8 @@ min-width: 375px; margin: 0 auto; padding: 20px; - background: var(--Greyscale-20, #F5F7FB); - border: 1px solid var(--Greyscale-100, #EBEDF2); + background: var(--Greyscale-20, #f5f7fb); + border: 1px solid var(--Greyscale-100, #ebedf2); border-radius: 15px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } @@ -85,55 +85,51 @@ width: 100%; padding: 12px; font-size: 1rem; - border: 1px solid var(--Greyscale-200, #D8DBE3); + border: 1px solid var(--Greyscale-200, #d8dbe3); border-radius: 4px; box-sizing: border-box; &::placeholder { - color: rgba(0, 0, 0, 0.4); + color: rgba(0, 0, 0, 0.4); } } button { -padding: 10px 16px 10px 16px; -gap: 8px; -color: #ffffff; + padding: 10px 16px 10px 16px; + gap: 8px; + color: #ffffff; width: 100%; font-size: 1rem; - background: var(--Greyscale-900, #2B2F33); - border: 1px solid var(--Greyscale-800, #3F464B); + background: var(--Greyscale-900, #2b2f33); + border: 1px solid var(--Greyscale-800, #3f464b); border-radius: 8px; cursor: pointer; } button:disabled { - color: var(--Greyscale-700, #A2A7AE); - background: var(--Greyscale-200, #DDDDDD); - border-color: var(--Greyscale-400, #A2A7AE); + color: var(--Greyscale-700, #a2a7ae); + background: var(--Greyscale-200, #dddddd); + border-color: var(--Greyscale-400, #a2a7ae); cursor: not-allowed; } - - - - .passkeyButton { margin-top: 12px; padding: 10px 16px 10px 16px; -gap: 8px; -color: var(--Greyscale-600, #6C727E); + gap: 8px; + color: var(--Greyscale-600, #6c727e); width: 100%; font-size: 1rem; - background: var(--Greyscale-1, #FFFFFF); - border: 1px solid var(--Greyscale-400, #A2A7AE); + background: var(--Greyscale-1, #ffffff); + border: 1px solid var(--Greyscale-400, #a2a7ae); border-radius: 8px; cursor: pointer; } .passkeyButton:disabled { - color: var(--Greyscale-500, #A2A7AE); - background: var(--Greyscale-200, #DDDDDD); - border-color: var(--Greyscale-400, #A2A7AE); + color: var(--Greyscale-500, #a2a7ae); + background: var(--Greyscale-200, #dddddd); + border-color: var(--Greyscale-400, #a2a7ae); cursor: not-allowed; } /* Separator */ @@ -144,12 +140,12 @@ color: var(--Greyscale-600, #6C727E); } .separator span { - color: var(--Greyscale-500, #868C95); + color: var(--Greyscale-500, #868c95); padding: 0 10px; position: relative; z-index: 0; font-size: 0.75rem; - background: var(--Greyscale-20, #F5F7FB); + background: var(--Greyscale-20, #f5f7fb); color: #666; } @@ -161,7 +157,7 @@ color: var(--Greyscale-600, #6C727E); left: 0; right: 0; height: 1px; - background: var(--Greyscale-100, #EBEDF2); + background: var(--Greyscale-100, #ebedf2); z-index: 0; } .verification { @@ -171,19 +167,19 @@ color: var(--Greyscale-600, #6C727E); line-height: 16px; letter-spacing: -0.01em; text-align: center; - color: var(--Greyscale-500, #868C95); + color: var(--Greyscale-500, #868c95); margin-top: 16px; } .verificationBold { - color: var(--Greyscale-900, #2B2F33); + color: var(--Greyscale-900, #2b2f33); font-weight: 700; margin-top: 8px; } .verificationIcon { display: flex; justify-content: center; - margin-bottom: 8px; + margin-bottom: 8px; } .tos { font-family: Inter; @@ -192,12 +188,12 @@ color: var(--Greyscale-600, #6C727E); line-height: 16px; letter-spacing: -0.01em; text-align: center; - color: var(--Greyscale-500, #868C95); + color: var(--Greyscale-500, #868c95); margin-top: 16px; } .tosBold { - color: var(--Greyscale-900, #2B2F33); + color: var(--Greyscale-900, #2b2f33); font-weight: 700; cursor: pointer; } @@ -220,10 +216,10 @@ color: var(--Greyscale-600, #6C727E); width: 235px; height: 40px; display: flex; - align-items: center; - justify-content: center; + align-items: center; + justify-content: center; cursor: pointer; - text-align: center; + text-align: center; } .poweredBy { @@ -236,7 +232,7 @@ color: var(--Greyscale-600, #6C727E); font-weight: 400; line-height: 16px; letter-spacing: -0.01em; - color: var(--Greyscale-500, #868C95); + color: var(--Greyscale-500, #868c95); margin-top: 16px; } @@ -253,6 +249,6 @@ color: var(--Greyscale-600, #6C727E); .errorText { text-align: center; margin-top: 12px; - color: #FF4C4C; + color: #ff4c4c; font-weight: 500; -} \ No newline at end of file +} diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 3050ea8e9..bbabcc5d9 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -1,7 +1,13 @@ import styles from "./Auth.module.css"; import { useEffect, useState } from "react"; import { useTurnkey } from "../../hooks/useTurnkey"; -import { initOtpAuth, otpAuth, getSuborgs, createSuborg, oauth } from "../../actions/"; +import { + initOtpAuth, + otpAuth, + getSuborgs, + createSuborg, + oauth, +} from "../../actions/"; import { MuiPhone } from "./PhoneInput"; import OTPInput from "./OtpInput"; import GoogleAuthButton from "./Google"; @@ -27,7 +33,7 @@ interface AuthProps { const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const { passkeyClient, authIframeClient } = useTurnkey(); const [error, setError] = useState(null); - const [otpError, setOtpError] = useState(null); + const [otpError, setOtpError] = useState(null); const [email, setEmail] = useState(""); const [phone, setPhone] = useState(""); const [otpId, setOtpId] = useState(null); @@ -51,13 +57,19 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const usCanadaRegex = /^\+1\d{10}$/; return usCanadaRegex.test(phone); }; - - const handleGetOrCreateSuborg = async (filterType: string, filterValue: string, additionalData = {}) => { + const handleGetOrCreateSuborg = async ( + filterType: string, + filterValue: string, + additionalData = {} + ) => { const getSuborgsResponse = await getSuborgs({ filterType, filterValue }); let suborgId = getSuborgsResponse!.organizationIds[0]; if (!suborgId) { - const createSuborgResponse = await createSuborg({ [filterType.toLowerCase()]: filterValue, ...additionalData }); + const createSuborgResponse = await createSuborg({ + [filterType.toLowerCase()]: filterValue, + ...additionalData, + }); suborgId = createSuborgResponse?.subOrganizationId!; } return suborgId; @@ -71,18 +83,20 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { }; const handleLoginWithPasskey = async () => { - // Step 1: Try to retrieve the suborg by email - const getSuborgsResponse = await getSuborgs({ filterType: "EMAIL", filterValue: email }); + const getSuborgsResponse = await getSuborgs({ + filterType: "EMAIL", + filterValue: email, + }); const existingSuborgId = getSuborgsResponse!.organizationIds[0]; - + if (existingSuborgId) { // If a suborg exists, use it to create a read/write session without a new passkey const sessionResponse = await passkeyClient?.createReadWriteSession({ organizationId: existingSuborgId, targetPublicKey: authIframeClient?.iframePublicKey!, }); - + if (sessionResponse?.credentialBundle) { await handleAuthSuccess(sessionResponse.credentialBundle); } else { @@ -90,26 +104,32 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { } } else { // If no suborg exists, first create a user passkey - const { encodedChallenge, attestation } = await passkeyClient?.createUserPasskey({ - publicKey: { user: { name: email, displayName: email } }, - }) || {}; - + const { encodedChallenge, attestation } = + (await passkeyClient?.createUserPasskey({ + publicKey: { user: { name: email, displayName: email } }, + })) || {}; + if (encodedChallenge && attestation) { // Use the generated passkey to create a new suborg const createSuborgResponse = await createSuborg({ email, - passkey: { authenticatorName: "First Passkey", challenge: encodedChallenge, attestation }, + passkey: { + authenticatorName: "First Passkey", + challenge: encodedChallenge, + attestation, + }, }); - + const newSuborgId = createSuborgResponse?.subOrganizationId; - + if (newSuborgId) { // With the new suborg, create a read/write session - const newSessionResponse = await passkeyClient?.createReadWriteSession({ - organizationId: newSuborgId, - targetPublicKey: authIframeClient?.iframePublicKey!, - }); - + const newSessionResponse = + await passkeyClient?.createReadWriteSession({ + organizationId: newSuborgId, + targetPublicKey: authIframeClient?.iframePublicKey!, + }); + if (newSessionResponse?.credentialBundle) { await handleAuthSuccess(newSessionResponse.credentialBundle); } else { @@ -122,40 +142,53 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { setError("Failed to create user passkey."); } } - }; + }; - const handleOtpLogin = async (type: "EMAIL" | "PHONE_NUMBER", value: string, otpType: string) => { + const handleOtpLogin = async ( + type: "EMAIL" | "PHONE_NUMBER", + value: string, + otpType: string + ) => { const suborgId = await handleGetOrCreateSuborg(type, value); - const initAuthResponse = await initOtpAuth({ suborgID: suborgId, otpType, contact: value }); + const initAuthResponse = await initOtpAuth({ + suborgID: suborgId, + otpType, + contact: value, + }); setSuborgId(suborgId); setOtpId(initAuthResponse!.otpId); setStep(type === "EMAIL" ? "otpEmail" : "otpPhone"); }; const handleValidateOtp = async (otp: string) => { - const authResponse = await otpAuth( - { suborgID: suborgId, otpId: otpId!, otpCode: otp, targetPublicKey: authIframeClient!.iframePublicKey! } - ); - authResponse?.credentialBundle ? await handleAuthSuccess(authResponse.credentialBundle) : setOtpError("Invalid code, please try again"); + const authResponse = await otpAuth({ + suborgID: suborgId, + otpId: otpId!, + otpCode: otp, + targetPublicKey: authIframeClient!.iframePublicKey!, + }); + authResponse?.credentialBundle + ? await handleAuthSuccess(authResponse.credentialBundle) + : setOtpError("Invalid code, please try again"); }; const handleGoogleLogin = async (response: any) => { const credential = response.credential; - setOauthLoading("Google") + setOauthLoading("Google"); await handleOAuthLogin(credential, "Google OIDC"); }; const handleAppleLogin = async (response: any) => { const appleToken = response.authorization?.id_token; - setOauthLoading("Apple") + setOauthLoading("Apple"); if (appleToken) { await handleOAuthLogin(appleToken, "Apple OIDC"); } }; - const handleFacebookLogin = async (response:any) => { - const facebookToken = response?.id_token; - setOauthLoading("Facebook") + const handleFacebookLogin = async (response: any) => { + const facebookToken = response?.id_token; + setOauthLoading("Facebook"); if (facebookToken) { await handleOAuthLogin(facebookToken, "Facebook OIDC"); } else { @@ -164,8 +197,14 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { }; const handleOAuthLogin = async (credential: string, providerName: string) => { - const suborgId = await handleGetOrCreateSuborg("OIDC_TOKEN", credential, { oauthProviders: [{ providerName, oidcToken: credential }] }); - const oauthResponse = await oauth({ suborgID: suborgId, oidcToken: credential, targetPublicKey: authIframeClient?.iframePublicKey! }); + const suborgId = await handleGetOrCreateSuborg("OIDC_TOKEN", credential, { + oauthProviders: [{ providerName, oidcToken: credential }], + }); + const oauthResponse = await oauth({ + suborgID: suborgId, + oidcToken: credential, + targetPublicKey: authIframeClient?.iframePublicKey!, + }); await handleAuthSuccess(oauthResponse!.credentialBundle); }; @@ -180,216 +219,328 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { }; return ( -
- {oauthLoading != "" ? -
-

Verifying with {oauthLoading}

-
- - {oauthLoading === "Google" && } - {oauthLoading === "Facebook" && } - {oauthLoading === "Apple" && } -
-
- Powered by - - - - - - - - - - - - - - -
-
- - : - -
-

{otpId ? "Enter verification code" : "Log in or sign up"}

-
- {authConfig.emailEnabled && !otpId && ( -
-
- setEmail(e.target.value)} - /> -
- +
+ {oauthLoading != "" ? ( +
+

+ Verifying with {oauthLoading} +

+
+ + {oauthLoading === "Google" && ( + + )} + {oauthLoading === "Facebook" && ( + + )} + {oauthLoading === "Apple" && ( + + )}
- )} - - {authConfig.passkeyEnabled && !otpId && ( -
- -
- )} -{!otpId && (authConfig.passkeyEnabled || authConfig.emailEnabled) && (authConfig.googleEnabled || authConfig.appleEnabled || authConfig.facebookEnabled || authConfig.phoneEnabled) && -
- OR -
-} - {authConfig.phoneEnabled && !otpId && ( -
-
- setPhone(value)} value={phone} /> -
- + + + + + + + + + + + + +
- )} - - {otpId && ( -
-
-{step === "otpEmail" -? - - - -: - - - - -} +
+ ) : ( +
+

{otpId ? "Enter verification code" : "Log in or sign up"}

+
+ {authConfig.emailEnabled && !otpId && ( +
+
+ setEmail(e.target.value)} + /> +
+ +
+ )} -
+ {authConfig.passkeyEnabled && !otpId && ( +
+ +
+ )} + {!otpId && + (authConfig.passkeyEnabled || authConfig.emailEnabled) && + (authConfig.googleEnabled || + authConfig.appleEnabled || + authConfig.facebookEnabled || + authConfig.phoneEnabled) && ( +
+ OR +
+ )} + {authConfig.phoneEnabled && !otpId && ( +
+
+ setPhone(value)} + value={phone} + /> +
+ +
+ )} - - We've sent a verification code to{" "} -
{step === "otpEmail" ? email : phone}
-
- setOtpError(null)} onComplete={handleValidateOtp} /> + {otpId && ( +
+
+ {step === "otpEmail" ? ( + + + + ) : ( + + + + )} +
+ + + We've sent a verification code to{" "} +
+ {step === "otpEmail" ? email : phone} +
+
+ setOtpError(null)} + onComplete={handleValidateOtp} + /> +
+ )} + {otpError &&
{otpError}
}
- - )} - {otpError && ( -
{otpError}
+ {!otpId && + (authConfig.googleEnabled || + authConfig.appleEnabled || + authConfig.facebookEnabled) && + authConfig.phoneEnabled && ( +
+ OR +
)} -
- {!otpId && (authConfig.googleEnabled || authConfig.appleEnabled || authConfig.facebookEnabled) && authConfig.phoneEnabled && -
- OR + {!otpId && authConfig.googleEnabled && authIframeClient && ( +
+
+ +
-} - -{!otpId && authConfig.googleEnabled && authIframeClient && ( -
-
- -
-
-)} - -{!otpId && authConfig.appleEnabled && authIframeClient && ( -
-
- -
-
-)} - - -{!otpId && authConfig.facebookEnabled && authIframeClient && ( -
-
- -
-
-)} - -
- {!otpId ? ( - - By logging in you agree to our{" "} - - Terms of Service - {" "} - &{" "} - - Privacy Policy - - - ) : ( - - Did not receive your code?{" "} - - {resendText} - - - )} -
- + )} + + {!otpId && authConfig.appleEnabled && authIframeClient && ( +
+
+ +
+
+ )} + + {!otpId && authConfig.facebookEnabled && authIframeClient && ( +
+
+ +
+
+ )} + +
+ {!otpId ? ( + + By logging in you agree to our{" "} + + Terms of Service + {" "} + &{" "} + + Privacy Policy + + + ) : ( + + Did not receive your code?{" "} + + {resendText} + + + )} +
-
- Powered by - - - - - - - - - - - - - - -
-
-} +
+ Powered by + + + + + + + + + + + + + + +
+
+ )}
- ); }; export default Auth; - - diff --git a/packages/sdk-react/src/components/auth/Facebook.tsx b/packages/sdk-react/src/components/auth/Facebook.tsx index 392f13bc1..c131f969c 100644 --- a/packages/sdk-react/src/components/auth/Facebook.tsx +++ b/packages/sdk-react/src/components/auth/Facebook.tsx @@ -38,7 +38,11 @@ const FacebookAuthButton: React.FC = ({ const facebookOAuthURL = `https://www.facebook.com/v11.0/dialog/oauth?${params.toString()}`; - const popup = window.open(facebookOAuthURL, "_blank", "width=500,height=600"); + const popup = window.open( + facebookOAuthURL, + "_blank", + "width=500,height=600" + ); if (popup) { const interval = setInterval(async () => { @@ -66,13 +70,19 @@ const FacebookAuthButton: React.FC = ({ const handleTokenExchange = async (authCode: string) => { const verifier = sessionStorage.getItem("facebook_verifier"); if (!verifier || tokenExchanged) { - console.error("No verifier found in sessionStorage or token exchange already completed"); + console.error( + "No verifier found in sessionStorage or token exchange already completed" + ); return; } - try { - const tokenData = await exchangeCodeForToken(clientId, redirectURI, authCode, verifier); + const tokenData = await exchangeCodeForToken( + clientId, + redirectURI, + authCode, + verifier + ); sessionStorage.removeItem("facebook_verifier"); // Remove verifier after use onSuccess(tokenData); setTokenExchanged(true); // Prevent further exchanges @@ -84,8 +94,8 @@ const FacebookAuthButton: React.FC = ({ return (
- - Continue with Facebook + + Continue with Facebook
); }; diff --git a/packages/sdk-react/src/components/auth/Google.tsx b/packages/sdk-react/src/components/auth/Google.tsx index eae3cd4dd..3422b989d 100644 --- a/packages/sdk-react/src/components/auth/Google.tsx +++ b/packages/sdk-react/src/components/auth/Google.tsx @@ -8,7 +8,11 @@ interface GoogleAuthButtonProps { onSuccess: (response: any) => void; } -const GoogleAuthButton: React.FC = ({ iframePublicKey, onSuccess, clientId}) => { +const GoogleAuthButton: React.FC = ({ + iframePublicKey, + onSuccess, + clientId, +}) => { return ( = ({ iframePublicKey, on width={235} containerProps={{ style: { - width: '100%', - display: 'flex', - justifyContent: 'center', - borderRadius: '8px', - padding: '10px', - cursor: 'pointer', - maxWidth: '235px', + width: "100%", + display: "flex", + justifyContent: "center", + borderRadius: "8px", + padding: "10px", + cursor: "pointer", + maxWidth: "235px", }, }} auto_select={false} diff --git a/packages/sdk-react/src/components/auth/OTPInput.tsx b/packages/sdk-react/src/components/auth/OTPInput.tsx index 30c10b564..1afdd15b7 100644 --- a/packages/sdk-react/src/components/auth/OTPInput.tsx +++ b/packages/sdk-react/src/components/auth/OTPInput.tsx @@ -10,7 +10,7 @@ const OTPInput: React.FC = ({ onComplete, onChange }) => { const [otp, setOtp] = useState(Array(6).fill("")); const handleChange = (value: string, index: number) => { - onChange() + onChange(); if (/^\d*$/.test(value)) { const newOtp = [...otp]; newOtp[index] = value; diff --git a/packages/sdk-react/src/components/auth/PhoneInput.css b/packages/sdk-react/src/components/auth/PhoneInput.css index 287d4a11f..cb5c8c99f 100644 --- a/packages/sdk-react/src/components/auth/PhoneInput.css +++ b/packages/sdk-react/src/components/auth/PhoneInput.css @@ -1 +1 @@ -@import 'react-international-phone/style.css'; \ No newline at end of file +@import "react-international-phone/style.css"; diff --git a/packages/sdk-react/src/components/auth/PhoneInput.tsx b/packages/sdk-react/src/components/auth/PhoneInput.tsx index d4f4ce7cb..75b64bac5 100644 --- a/packages/sdk-react/src/components/auth/PhoneInput.tsx +++ b/packages/sdk-react/src/components/auth/PhoneInput.tsx @@ -17,10 +17,9 @@ import { const countries = defaultCountries.filter((country) => { const { iso2 } = parseCountry(country); - return ['us', 'ca'].includes(iso2); + return ["us", "ca"].includes(iso2); }); - export interface MUIPhoneProps extends BaseTextFieldProps { value: string; onChange: (phone: string) => void; @@ -92,7 +91,6 @@ export const MuiPhone: React.FC = ({ ".MuiSelect-select": { padding: "8px", paddingRight: "24px !important", - }, svg: { right: 0, diff --git a/packages/sdk-react/src/components/auth/facebook-utils.ts b/packages/sdk-react/src/components/auth/facebook-utils.ts index 130acb009..cc833dbb5 100644 --- a/packages/sdk-react/src/components/auth/facebook-utils.ts +++ b/packages/sdk-react/src/components/auth/facebook-utils.ts @@ -3,21 +3,32 @@ import crypto from "crypto"; export async function generateChallengePair() { const verifier = crypto.randomBytes(32).toString("base64url"); - const codeChallenge = crypto.createHash("sha256").update(verifier).digest("base64url"); + const codeChallenge = crypto + .createHash("sha256") + .update(verifier) + .digest("base64url"); return { verifier, codeChallenge }; } -export async function exchangeCodeForToken(clientId: any, redirectURI: any, authCode: any, verifier: any) { - const response = await fetch(`https://graph.facebook.com/v11.0/oauth/access_token`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - client_id: clientId, - redirect_uri: redirectURI, - code: authCode, - code_verifier: verifier, - }), - }); +export async function exchangeCodeForToken( + clientId: any, + redirectURI: any, + authCode: any, + verifier: any +) { + const response = await fetch( + `https://graph.facebook.com/v11.0/oauth/access_token`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + client_id: clientId, + redirect_uri: redirectURI, + code: authCode, + code_verifier: verifier, + }), + } + ); const tokenData = await response.json(); if (!response.ok) { diff --git a/packages/sdk-react/src/components/auth/index.ts b/packages/sdk-react/src/components/auth/index.ts index 46a0209a0..4adba6e6c 100644 --- a/packages/sdk-react/src/components/auth/index.ts +++ b/packages/sdk-react/src/components/auth/index.ts @@ -1 +1 @@ -export {default as Auth} from "./Auth" \ No newline at end of file +export { default as Auth } from "./Auth"; diff --git a/packages/sdk-react/src/components/index.ts b/packages/sdk-react/src/components/index.ts index 63978cda7..97ccf7649 100644 --- a/packages/sdk-react/src/components/index.ts +++ b/packages/sdk-react/src/components/index.ts @@ -1 +1 @@ -export * from "./auth" \ No newline at end of file +export * from "./auth"; diff --git a/packages/sdk-react/src/index.ts b/packages/sdk-react/src/index.ts index 37815ccc0..9a3e12a47 100644 --- a/packages/sdk-react/src/index.ts +++ b/packages/sdk-react/src/index.ts @@ -2,6 +2,6 @@ import "./components/auth/Auth.module.css"; import "./components/auth/PhoneInput.css"; import { TurnkeyContext, TurnkeyProvider } from "./contexts/TurnkeyContext"; import { useTurnkey } from "./hooks/useTurnkey"; -export * from "./components" -export * from "./actions" -export { TurnkeyContext, TurnkeyProvider, useTurnkey}; +export * from "./components"; +export * from "./actions"; +export { TurnkeyContext, TurnkeyProvider, useTurnkey }; From c95556c9601800aa61634d3a85bf371e3141483b Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Wed, 6 Nov 2024 18:09:04 -0500 Subject: [PATCH 20/73] more polish --- examples/react-components/.env.local.example | 16 ++++++++++------ .../react-components/src/app/index.module.css | 13 +++++++------ examples/react-components/src/app/page.tsx | 10 +++++----- .../src/components/auth/Apple.module.css | 12 +++++++----- .../src/components/auth/Auth.module.css | 8 +++----- packages/sdk-react/src/components/auth/Auth.tsx | 17 ++++++++++++++--- .../src/components/auth/Facebook.module.css | 16 +++++++++------- 7 files changed, 55 insertions(+), 37 deletions(-) diff --git a/examples/react-components/.env.local.example b/examples/react-components/.env.local.example index b24b0e2d3..79ef9f816 100644 --- a/examples/react-components/.env.local.example +++ b/examples/react-components/.env.local.example @@ -1,8 +1,12 @@ -API_PUBLIC_KEY="" -API_PRIVATE_KEY="" -NEXT_PUBLIC_ORGANIZATION_ID="" -NEXT_PUBLIC_GOOGLE_CLIENT_ID=" = ({ onHandleAuthSuccess, authConfig }) => { ) => { const getSuborgsResponse = await getSuborgs({ filterType, filterValue }); let suborgId = getSuborgsResponse!.organizationIds[0]; + if (!suborgId) { - const createSuborgResponse = await createSuborg({ - [filterType.toLowerCase()]: filterValue, + const createSuborgData: Record = { ...additionalData, - }); + }; + + if (filterType === "EMAIL") { + createSuborgData.email = filterValue; + } else if (filterType === "PHONE_NUMBER") { + createSuborgData.phoneNumber = filterValue; + } + + const createSuborgResponse = await createSuborg(createSuborgData); suborgId = createSuborgResponse?.subOrganizationId!; } + return suborgId; }; + const handleAuthSuccess = async (credentialBundle: any) => { if (credentialBundle) { @@ -150,6 +160,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { otpType: string ) => { const suborgId = await handleGetOrCreateSuborg(type, value); + console.log(suborgId) const initAuthResponse = await initOtpAuth({ suborgID: suborgId, otpType, diff --git a/packages/sdk-react/src/components/auth/Facebook.module.css b/packages/sdk-react/src/components/auth/Facebook.module.css index dd55a14d1..b7147e1d3 100644 --- a/packages/sdk-react/src/components/auth/Facebook.module.css +++ b/packages/sdk-react/src/components/auth/Facebook.module.css @@ -3,22 +3,24 @@ align-items: center; justify-content: center; width: 100%; - padding: 10px 16px; - color: #fff; - font-size: 14px; - background-color: #3b5998; - border: none; - border-radius: 8px; + padding: 8px 10px; + color: #3b5998; + font-size: 13.5px; + background-color: #ffffff; + border-radius: 4px; cursor: pointer; font-weight: 500; + border: 1px solid #dcdcdc; transition: background-color 0.3s ease; } .facebookButton:hover { - background-color: #2d4373; + background-color: #f1f8ff; + border-color: #d0def0; } .buttonText { + color: #000000; flex-grow: 1; text-align: center; font-family: "Inter", sans-serif; From edbf29a31e8ef2313bbe57c3f0be4f84ddc959da Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Thu, 7 Nov 2024 17:39:51 -0500 Subject: [PATCH 21/73] working apple button --- examples/react-components/.env.local.example | 2 +- packages/sdk-react/src/components/auth/Google.tsx | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/react-components/.env.local.example b/examples/react-components/.env.local.example index 79ef9f816..089ac50d4 100644 --- a/examples/react-components/.env.local.example +++ b/examples/react-components/.env.local.example @@ -9,4 +9,4 @@ NEXT_PUBLIC_AUTH_IFRAME_URL="https://auth.turnkey.com" NEXT_PUBLIC_GOOGLE_CLIENT_ID="" NEXT_PUBLIC_APPLE_CLIENT_ID="" NEXT_PUBLIC_FACEBOOK_CLIENT_ID="" -NEXT_PUBLIC_OAUTH_REDIRECT_URI="http://localhost:3000/" # Where your login page is - make sure to have a trailing / +NEXT_PUBLIC_OAUTH_REDIRECT_URI="http://localhost:3000/" # Where your login page is - make sure to have a trailing "/" NOTE: Sign in with Apple does not support localhost redirects! You can use ngrok to test locally diff --git a/packages/sdk-react/src/components/auth/Google.tsx b/packages/sdk-react/src/components/auth/Google.tsx index 3422b989d..3a134ea4c 100644 --- a/packages/sdk-react/src/components/auth/Google.tsx +++ b/packages/sdk-react/src/components/auth/Google.tsx @@ -18,7 +18,6 @@ const GoogleAuthButton: React.FC = ({ = ({ maxWidth: "235px", }, }} + useOneTap={false} auto_select={false} text="continue_with" - ux_mode="popup" /> ); From baaf3502c6c854df9dd15a4267a82e64ce66ca7f Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Fri, 8 Nov 2024 11:43:48 -0500 Subject: [PATCH 22/73] styling changes --- .../src/components/auth/Auth.module.css | 5 +- .../sdk-react/src/components/auth/Auth.tsx | 25 +-- .../src/components/auth/OTPInput.tsx | 142 ++++++++++-------- 3 files changed, 98 insertions(+), 74 deletions(-) diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index 1cfdf829e..1eff629e8 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -189,7 +189,7 @@ button:disabled { letter-spacing: -0.01em; text-align: center; color: var(--Greyscale-500, #868c95); - margin-top: 16px; + margin-top: 12px; } .tosBold { @@ -245,8 +245,9 @@ button:disabled { } .errorText { + min-height: 16px; text-align: center; margin-top: 12px; color: #ff4c4c; - font-weight: 500; + font-size: 12px; } diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 9a12e27d1..d74da6f95 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -1,5 +1,5 @@ import styles from "./Auth.module.css"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useTurnkey } from "../../hooks/useTurnkey"; import { initOtpAuth, @@ -41,7 +41,8 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const [oauthLoading, setOauthLoading] = useState(""); const [suborgId, setSuborgId] = useState(""); const [resendText, setResendText] = useState("Re-send Code"); - + const otpInputRef = useRef(null); + useEffect(() => { if (error) { alert(error); @@ -172,15 +173,19 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { }; const handleValidateOtp = async (otp: string) => { + setOtpError(null) const authResponse = await otpAuth({ suborgID: suborgId, otpId: otpId!, otpCode: otp, targetPublicKey: authIframeClient!.iframePublicKey!, }); - authResponse?.credentialBundle - ? await handleAuthSuccess(authResponse.credentialBundle) - : setOtpError("Invalid code, please try again"); + if (authResponse?.credentialBundle) { + await handleAuthSuccess(authResponse.credentialBundle); + } else { + setOtpError("Invalid code. Please try again"); + otpInputRef.current.resetOtp(); + } }; const handleGoogleLogin = async (response: any) => { @@ -403,18 +408,21 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => {
- We've sent a verification code to{" "} + Enter the 6-digit code we sent to{" "}
{step === "otpEmail" ? email : phone}
setOtpError(null)} + ref = {otpInputRef} onComplete={handleValidateOtp} + hasError={!!otpError} />
)} - {otpError &&
{otpError}
} +
+ {otpError ? otpError : " "} +
{!otpId && (authConfig.googleEnabled || @@ -486,7 +494,6 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { ) : ( - Did not receive your code?{" "} void; - onChange: () => void; + hasError: boolean; } -const OTPInput: React.FC = ({ onComplete, onChange }) => { - const [otp, setOtp] = useState(Array(6).fill("")); +const OTPInput = forwardRef( + ({ onComplete, hasError }, ref) => { + const [otp, setOtp] = useState(Array(6).fill("")); - const handleChange = (value: string, index: number) => { - onChange(); - if (/^\d*$/.test(value)) { - const newOtp = [...otp]; - newOtp[index] = value; - setOtp(newOtp); + useImperativeHandle(ref, () => ({ + resetOtp() { + setOtp(Array(6).fill("")); + const firstInput = document.getElementById("otp-input-0"); + if (firstInput) (firstInput as HTMLInputElement).focus(); + }, + })); - // If all boxes are filled, call onComplete with the OTP - if (newOtp.every((digit) => digit !== "")) { - onComplete(newOtp.join("")); - } + const handleChange = (value: string, index: number) => { + if (/^\d*$/.test(value)) { + const newOtp = [...otp]; + newOtp[index] = value; + setOtp(newOtp); + + // If all boxes are filled, call onComplete with the OTP + if (newOtp.every((digit) => digit !== "")) { + onComplete(newOtp.join("")); + } - // Move focus to the next box if current is filled - if (value && index < 5) { - const nextInput = document.getElementById(`otp-input-${index + 1}`); - if (nextInput) (nextInput as HTMLInputElement).focus(); + // Move focus to the next box if current is filled + if (value && index < 5) { + const nextInput = document.getElementById(`otp-input-${index + 1}`); + if (nextInput) (nextInput as HTMLInputElement).focus(); + } } - } - }; + }; - const handleKeyDown = (event: React.KeyboardEvent, index: number) => { - if (event.key === "Backspace" && otp[index] === "" && index > 0) { - const prevInput = document.getElementById(`otp-input-${index - 1}`); - if (prevInput) (prevInput as HTMLInputElement).focus(); - } - }; + const handleKeyDown = (event: React.KeyboardEvent, index: number) => { + if (event.key === "Backspace" && otp[index] === "" && index > 0) { + const prevInput = document.getElementById(`otp-input-${index - 1}`); + if (prevInput) (prevInput as HTMLInputElement).focus(); + } + }; - const handlePaste = (event: React.ClipboardEvent) => { - const pasteData = event.clipboardData.getData("Text"); - if (/^\d{6}$/.test(pasteData)) { - const newOtp = pasteData.split(""); - setOtp(newOtp); - onComplete(newOtp.join("")); + const handlePaste = (event: React.ClipboardEvent) => { + const pasteData = event.clipboardData.getData("Text"); + if (/^\d{6}$/.test(pasteData)) { + const newOtp = pasteData.split(""); + setOtp(newOtp); + onComplete(newOtp.join("")); - // Automatically move focus to the last input box - const lastInput = document.getElementById(`otp-input-5`); - if (lastInput) (lastInput as HTMLInputElement).focus(); + // Automatically move focus to the last input box + const lastInput = document.getElementById(`otp-input-5`); + if (lastInput) (lastInput as HTMLInputElement).focus(); - event.preventDefault(); - } - }; + event.preventDefault(); + } + }; - return ( - - {otp.map((digit, index) => ( - handleChange(e.target.value, index)} - onKeyDown={(e) => handleKeyDown(e, index)} - onPaste={index === 0 ? handlePaste : undefined} // Handle paste on the first input only - inputProps={{ - maxLength: 1, - style: { - textAlign: "center", - fontSize: "1.5rem", - width: "60px", - background: "white", - }, - }} - variant="outlined" - /> - ))} - - ); -}; + return ( + + {otp.map((digit, index) => ( + handleChange(e.target.value, index)} + onKeyDown={(e) => handleKeyDown(e, index)} + onPaste={index === 0 ? handlePaste : undefined} + inputProps={{ + maxLength: 1, + style: { + textAlign: "center", + fontSize: "1.5rem", + width: "60px", + background: "white", + }, + }} + variant="outlined" + sx={{ + "& .MuiOutlinedInput-root": { + "& fieldset": { + borderColor: hasError && !digit ? "red" : "gray", + }, + }, + }} + /> + ))} + + ); + } +); export default OTPInput; From 441beedeaab285007816da15e2efcee191b88e6d Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Fri, 8 Nov 2024 13:39:36 -0500 Subject: [PATCH 23/73] more fixes --- .../src/components/auth/Auth.module.css | 23 ++++++++++++++++++- .../sdk-react/src/components/auth/Auth.tsx | 14 +++++++---- .../src/components/auth/OTPInput.tsx | 1 + 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index 1eff629e8..947bedfcb 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -189,7 +189,27 @@ button:disabled { letter-spacing: -0.01em; text-align: center; color: var(--Greyscale-500, #868c95); - margin-top: 12px; + margin-top: 20px; +} + +.resendCode { + font-size: 12px; + font-weight: 400; + letter-spacing: -0.01em; + text-align: center; + color: var(--Greyscale-500, #868c95); +} + +.resendCodeBold { + color: var(--Greyscale-900, #2b2f33); + font-weight: 700; + cursor: pointer; +} + +.tosBold { + color: var(--Greyscale-900, #2b2f33); + font-weight: 700; + cursor: pointer; } .tosBold { @@ -233,6 +253,7 @@ button:disabled { letter-spacing: -0.01em; color: var(--Greyscale-500, #868c95); margin-top: 16px; + cursor: pointer; } .poweredBy span { diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index d74da6f95..bb83562bc 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -41,6 +41,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const [oauthLoading, setOauthLoading] = useState(""); const [suborgId, setSuborgId] = useState(""); const [resendText, setResendText] = useState("Re-send Code"); + const [firstTimePasskey, setFirstTimePasskey] = useState(""); const otpInputRef = useRef(null); useEffect(() => { @@ -470,10 +471,10 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => {
)} -
{!otpId ? ( +
- By logging in you agree to our{" "} + By continuing, you agree to our{" "} = ({ onHandleAuthSuccess, authConfig }) => { Privacy Policy +
) : ( +
= ({ onHandleAuthSuccess, authConfig }) => { cursor: resendText === "Re-send Code" ? "pointer" : "not-allowed", }} - className={styles.tosBold} + className={styles.resendCodeBold} > {resendText} +
)} -
+ -
+
window.location.href = "https://www.turnkey.com/"} className={styles.poweredBy}> Powered by ( {otp.map((digit, index) => ( Date: Fri, 8 Nov 2024 14:21:55 -0500 Subject: [PATCH 24/73] config updates --- examples/react-components/public/apple.svg | 1 + examples/react-components/public/dots.svg | 5 +++ examples/react-components/public/facebook.svg | 1 + examples/react-components/public/google.svg | 1 + .../react-components/src/app/index.module.css | 41 +++++++++++++++++-- examples/react-components/src/app/page.tsx | 31 +++++++------- 6 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 examples/react-components/public/apple.svg create mode 100644 examples/react-components/public/dots.svg create mode 100644 examples/react-components/public/facebook.svg create mode 100644 examples/react-components/public/google.svg diff --git a/examples/react-components/public/apple.svg b/examples/react-components/public/apple.svg new file mode 100644 index 000000000..2ed4299f4 --- /dev/null +++ b/examples/react-components/public/apple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/react-components/public/dots.svg b/examples/react-components/public/dots.svg new file mode 100644 index 000000000..191f566b4 --- /dev/null +++ b/examples/react-components/public/dots.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/react-components/public/facebook.svg b/examples/react-components/public/facebook.svg new file mode 100644 index 000000000..31884b73e --- /dev/null +++ b/examples/react-components/public/facebook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/react-components/public/google.svg b/examples/react-components/public/google.svg new file mode 100644 index 000000000..fb226ca2d --- /dev/null +++ b/examples/react-components/public/google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/react-components/src/app/index.module.css b/examples/react-components/src/app/index.module.css index 5d5500b37..751d531a3 100644 --- a/examples/react-components/src/app/index.module.css +++ b/examples/react-components/src/app/index.module.css @@ -35,7 +35,7 @@ .authConfigCard { background: var(--Greyscale-20, #f5f7fb); - padding: 16px; + padding: 32px; border-radius: 8px; width: 542px; height: 642px; @@ -47,11 +47,11 @@ justify-content: space-between; } .configTitle { - font-size: 24px; + font-size: 32px; font-weight: 700; - line-height: 26px; + line-height: 36px; letter-spacing: -0.01em; - text-align: center; + text-align: left; margin-bottom: 16px; margin-top: 16px; } @@ -78,6 +78,26 @@ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } +.toggleSocialRow { + background-color: #ffffff; + border-radius: 8px; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.toggleSocialIndividualRow { + background-color: var(--Greyscale-20, #f5f7fb); + display: flex; + margin-left: 16px; + padding-left: 16px; + padding-top:4px; + padding-bottom: 4px; + justify-content: space-between; + align-items: center; +} + .copyConfigButton { display: flex; align-items: center; @@ -101,3 +121,16 @@ .success { justify-items: center; } + +.iconSmall { + width: 18px; + height: 18px; +} + +.socialContainer{ + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 8px 16px; + padding-bottom: 16px; + background-color: #ffffff; +} \ No newline at end of file diff --git a/examples/react-components/src/app/page.tsx b/examples/react-components/src/app/page.tsx index 2f9d942d7..53116c60f 100644 --- a/examples/react-components/src/app/page.tsx +++ b/examples/react-components/src/app/page.tsx @@ -137,7 +137,7 @@ export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) {
- + Email
- + Passkey
- + Phone
toggleConfig("phone")} />
- -
+
+
- + Socials
toggleSocials("enabled")} /> -
- {config.socials.enabled && ( - <> -
+ +
+
+ Google
toggleSocials("google")} />
-
+
+ Apple
toggleSocials("apple")} />
-
+
+ Facebook
toggleSocials("facebook")} />
- - )} -
+
+
From 887c31bdd22e31461fdb392d49980bfa3292b6f5 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Fri, 8 Nov 2024 14:29:36 -0500 Subject: [PATCH 25/73] new switches --- examples/react-components/src/app/Switch.tsx | 26 +++++++ examples/react-components/src/app/page.tsx | 81 ++------------------ 2 files changed, 34 insertions(+), 73 deletions(-) create mode 100644 examples/react-components/src/app/Switch.tsx diff --git a/examples/react-components/src/app/Switch.tsx b/examples/react-components/src/app/Switch.tsx new file mode 100644 index 000000000..abfb5a10c --- /dev/null +++ b/examples/react-components/src/app/Switch.tsx @@ -0,0 +1,26 @@ +import { styled, Switch } from "@mui/material"; + +const CustomSwitch = styled(Switch)(({ theme }) => ({ + padding: 8, + "& .MuiSwitch-track": { + borderRadius: 22 / 2, + backgroundColor: theme.palette.grey[400], + opacity: 1, + }, + "& .MuiSwitch-thumb": { + backgroundColor: "#FFFFFF", + boxShadow: "none", + width: 16, + height: 16, + margin: 2, + }, + "& .MuiSwitch-switchBase.Mui-checked": { + color: "var(--Greyscale-900, #2B2F33)", + }, + "& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track": { + backgroundColor: "var(--Greyscale-900, #2B2F33)", + opacity: 1, + }, +})); + +export default CustomSwitch; diff --git a/examples/react-components/src/app/page.tsx b/examples/react-components/src/app/page.tsx index 53116c60f..8e4b1b2cd 100644 --- a/examples/react-components/src/app/page.tsx +++ b/examples/react-components/src/app/page.tsx @@ -7,6 +7,7 @@ import { useTurnkey, Auth } from "@turnkey/sdk-react"; import { Switch, Typography } from "@mui/material"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; import AppsIcon from "@mui/icons-material/Apps"; +import CustomSwitch from "./Switch"; // Define types for config and socials interface SocialConfig { @@ -140,16 +141,7 @@ export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) { Email
- toggleConfig("email")} /> @@ -160,16 +152,7 @@ export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) { Passkey
- toggleConfig("passkey")} /> @@ -180,16 +163,7 @@ export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) { Phone
- toggleConfig("phone")} /> @@ -200,16 +174,7 @@ export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) { Socials
- toggleSocials("enabled")} /> @@ -221,17 +186,7 @@ export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) { Google
- toggleSocials("google")} /> @@ -241,17 +196,7 @@ export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) { Apple
- toggleSocials("apple")} /> @@ -261,17 +206,7 @@ export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) { Facebook
- toggleSocials("facebook")} /> From a5e385fde971a62c41642c9144973808a1ce0001 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Mon, 11 Nov 2024 12:43:34 -0500 Subject: [PATCH 26/73] custom social buttons --- examples/react-components/.env.local.example | 2 +- packages/sdk-react/package.json | 1 + packages/sdk-react/public/apple.svg | 1 + packages/sdk-react/public/facebook.svg | 1 + packages/sdk-react/public/google.svg | 1 + .../src/components/auth/Apple.module.css | 27 ----------- .../sdk-react/src/components/auth/Apple.tsx | 9 ++-- .../src/components/auth/Auth.module.css | 7 ++- .../sdk-react/src/components/auth/Auth.tsx | 17 ++++++- .../src/components/auth/Facebook.module.css | 27 ----------- .../src/components/auth/Facebook.tsx | 9 ++-- .../sdk-react/src/components/auth/Google.tsx | 46 ++++++++++--------- .../src/components/auth/Socials.module.css | 28 +++++++++++ pnpm-lock.yaml | 7 +++ 14 files changed, 93 insertions(+), 90 deletions(-) create mode 100644 packages/sdk-react/public/apple.svg create mode 100644 packages/sdk-react/public/facebook.svg create mode 100644 packages/sdk-react/public/google.svg delete mode 100644 packages/sdk-react/src/components/auth/Apple.module.css delete mode 100644 packages/sdk-react/src/components/auth/Facebook.module.css create mode 100644 packages/sdk-react/src/components/auth/Socials.module.css diff --git a/examples/react-components/.env.local.example b/examples/react-components/.env.local.example index 089ac50d4..f66a5a6fa 100644 --- a/examples/react-components/.env.local.example +++ b/examples/react-components/.env.local.example @@ -9,4 +9,4 @@ NEXT_PUBLIC_AUTH_IFRAME_URL="https://auth.turnkey.com" NEXT_PUBLIC_GOOGLE_CLIENT_ID="" NEXT_PUBLIC_APPLE_CLIENT_ID="" NEXT_PUBLIC_FACEBOOK_CLIENT_ID="" -NEXT_PUBLIC_OAUTH_REDIRECT_URI="http://localhost:3000/" # Where your login page is - make sure to have a trailing "/" NOTE: Sign in with Apple does not support localhost redirects! You can use ngrok to test locally +NEXT_PUBLIC_OAUTH_REDIRECT_URI="http://localhost:3000/" # Where your login page is - make sure to have a trailing "/" NOTE: Sign in with Apple and Google does not support localhost redirects! You can use ngrok to test locally diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index 407f93f1d..5010096d8 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -59,6 +59,7 @@ "@turnkey/wallet-stamper": "workspace:*", "usehooks-ts": "^3.1.0", "@turnkey/sdk-server": "workspace:*", + "libphonenumber-js": "^1.11.14", "next": "^15.0.2", "react-apple-login": "^1.1.6", "react-international-phone": "^4.3.0" diff --git a/packages/sdk-react/public/apple.svg b/packages/sdk-react/public/apple.svg new file mode 100644 index 000000000..2ed4299f4 --- /dev/null +++ b/packages/sdk-react/public/apple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/sdk-react/public/facebook.svg b/packages/sdk-react/public/facebook.svg new file mode 100644 index 000000000..31884b73e --- /dev/null +++ b/packages/sdk-react/public/facebook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/sdk-react/public/google.svg b/packages/sdk-react/public/google.svg new file mode 100644 index 000000000..fb226ca2d --- /dev/null +++ b/packages/sdk-react/public/google.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/sdk-react/src/components/auth/Apple.module.css b/packages/sdk-react/src/components/auth/Apple.module.css deleted file mode 100644 index bb53c9132..000000000 --- a/packages/sdk-react/src/components/auth/Apple.module.css +++ /dev/null @@ -1,27 +0,0 @@ -.appleButton { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - padding: 8px 10px; - color: #000000; - font-size: 13.5px; - background-color: #ffffff; - border-radius: 4px; - cursor: pointer; - font-weight: 500; - border: 1px solid #dcdcdc; - transition: background-color 0.3s ease; -} - -.appleButton:hover { - background-color: #f1f8ff; - border-color: #d0def0; -} - -.buttonText { - color: #000000; - flex-grow: 1; - text-align: center; - font-family: "Inter", sans-serif; -} diff --git a/packages/sdk-react/src/components/auth/Apple.tsx b/packages/sdk-react/src/components/auth/Apple.tsx index e86989ce0..eedbc5e8f 100644 --- a/packages/sdk-react/src/components/auth/Apple.tsx +++ b/packages/sdk-react/src/components/auth/Apple.tsx @@ -3,7 +3,7 @@ import { sha256 } from "@noble/hashes/sha2"; import { bytesToHex } from "@noble/hashes/utils"; import AppleLogin from "react-apple-login"; import { SiApple } from "@icons-pack/react-simple-icons"; -import styles from "./Apple.module.css"; +import styles from "./Socials.module.css"; interface AppleAuthButtonProps { iframePublicKey: string; clientId: string; @@ -51,9 +51,10 @@ const AppleAuthButton: React.FC = ({ responseType="code id_token" responseMode="fragment" render={({ onClick }) => ( -
- - Continue with Apple +
+ {/* */} + + Apple
)} callback={(response) => { diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index 947bedfcb..bd6b86f0b 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -108,7 +108,7 @@ button { button:disabled { color: var(--Greyscale-700, #a2a7ae); - background: var(--Greyscale-200, #dddddd); + background: #ffffff; border-color: var(--Greyscale-400, #a2a7ae); cursor: not-allowed; } @@ -129,7 +129,7 @@ button:disabled { .passkeyButton:disabled { color: var(--Greyscale-500, #a2a7ae); - background: var(--Greyscale-200, #dddddd); + background: #ffffff; border-color: var(--Greyscale-400, #a2a7ae); cursor: not-allowed; } @@ -233,8 +233,7 @@ button:disabled { } .socialButtonContainer { - width: 235px; - height: 40px; + width: 100%; display: flex; align-items: center; justify-content: center; diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index bb83562bc..c1d6095b7 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -17,6 +17,7 @@ import { CircularProgress } from "@mui/material"; import GoogleIcon from "@mui/icons-material/Google"; import FacebookIcon from "@mui/icons-material/Facebook"; import AppleIcon from "@mui/icons-material/Apple"; +import { parsePhoneNumberFromString } from 'libphonenumber-js'; interface AuthProps { onHandleAuthSuccess: () => Promise; @@ -44,6 +45,11 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const [firstTimePasskey, setFirstTimePasskey] = useState(""); const otpInputRef = useRef(null); + const formatPhoneNumber = (phone: string) => { + const phoneNumber = parsePhoneNumberFromString(phone); + return phoneNumber ? phoneNumber.formatInternational() : phone; + }; + useEffect(() => { if (error) { alert(error); @@ -330,6 +336,12 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => {
)} +{ + authConfig.emailEnabled && authConfig.passkeyEnabled && !otpId && +
+ OR +
+} {authConfig.passkeyEnabled && !otpId && (
+
)} {!otpId && @@ -411,7 +424,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { Enter the 6-digit code we sent to{" "}
- {step === "otpEmail" ? email : phone} + {step === "otpEmail" ? email : formatPhoneNumber(phone)}
= ({ onHandleAuthSuccess, authConfig }) => {
window.location.href = "https://www.turnkey.com/"} className={styles.poweredBy}> - Powered by + Secured by = ({ }; return ( -
- - Continue with Facebook +
+ {/* */} + + Facebook
); }; diff --git a/packages/sdk-react/src/components/auth/Google.tsx b/packages/sdk-react/src/components/auth/Google.tsx index 3a134ea4c..71b55d66e 100644 --- a/packages/sdk-react/src/components/auth/Google.tsx +++ b/packages/sdk-react/src/components/auth/Google.tsx @@ -1,39 +1,43 @@ -import { GoogleOAuthProvider, GoogleLogin } from "@react-oauth/google"; +import { GoogleOAuthProvider } from "@react-oauth/google"; import { sha256 } from "@noble/hashes/sha2"; import { bytesToHex } from "@noble/hashes/utils"; +import styles from "./Socials.module.css"; interface GoogleAuthButtonProps { iframePublicKey: string; clientId: string; onSuccess: (response: any) => void; } +declare global { + interface Window { + google: any; + } +} + const GoogleAuthButton: React.FC = ({ iframePublicKey, - onSuccess, clientId, + onSuccess, }) => { + + const handleLogin = async () => { + const nonce = bytesToHex(sha256(iframePublicKey)); + await window.google?.accounts.id.initialize({ + client_id: clientId, + callback: onSuccess, + nonce: nonce, + }); + window.google?.accounts.id.prompt(); + }; + return ( - +
+ {/* */} + + Google +
); }; diff --git a/packages/sdk-react/src/components/auth/Socials.module.css b/packages/sdk-react/src/components/auth/Socials.module.css new file mode 100644 index 000000000..7ec1deae9 --- /dev/null +++ b/packages/sdk-react/src/components/auth/Socials.module.css @@ -0,0 +1,28 @@ +.socialButton { + padding: 10px 16px 10px 16px; + gap: 8px; + color: var(--Greyscale-900, #2b2f33); + width: 100%; + font-size: 1rem; + background: #ffffff; + border: 1px solid var(--Greyscale-400, #a2a7ae); + border-radius: 8px; + cursor: pointer; + text-align: start; + align-items: center; + justify-content: center; + display: flex; + align-items: center; + justify-content: start; + transition: background-color 0.2s ease; +} + +.socialButton:hover { + background-color: #f0f8ff; +} + +.iconSmall { + width: 24px; + height: 24px; + margin-right: 4px; +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 18215f2be..c9bc3deac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1816,6 +1816,9 @@ importers: '@turnkey/wallet-stamper': specifier: workspace:* version: link:../wallet-stamper + libphonenumber-js: + specifier: ^1.11.14 + version: 1.11.14 next: specifier: ^15.0.2 version: 15.0.2(@babel/core@7.24.5)(react-dom@18.3.1)(react@18.2.0) @@ -19660,6 +19663,10 @@ packages: prelude-ls: 1.2.1 type-check: 0.4.0 + /libphonenumber-js@1.11.14: + resolution: {integrity: sha512-sexvAfwcW1Lqws4zFp8heAtAEXbEDnvkYCEGzvOoMgZR7JhXo/IkE9MkkGACgBed5fWqh3ShBGnJBdDnU9N8EQ==} + dev: false + /libsodium-sumo@0.7.11: resolution: {integrity: sha512-bY+7ph7xpk51Ez2GbE10lXAQ5sJma6NghcIDaSPbM/G9elfrjLa0COHl/7P6Wb/JizQzl5UQontOOP1z0VwbLA==} dev: false From 719ab393395eadf745d1109ca94466c0f56f11f4 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Mon, 11 Nov 2024 13:03:21 -0500 Subject: [PATCH 27/73] update icons for socials --- examples/react-components/src/app/page.tsx | 10 +--------- .../sdk-react/src/components/auth/Apple.tsx | 4 +--- .../src/components/auth/Auth.module.css | 4 ++-- .../sdk-react/src/components/auth/Auth.tsx | 13 +++++-------- .../sdk-react/src/components/auth/Facebook.tsx | 3 +-- .../sdk-react/src/components/auth/Google.tsx | 3 +-- .../sdk-react/src/components/auth/OTPInput.tsx | 6 +++--- .../src/components/auth/Socials.module.css | 18 ++++++++---------- 8 files changed, 22 insertions(+), 39 deletions(-) diff --git a/examples/react-components/src/app/page.tsx b/examples/react-components/src/app/page.tsx index 8e4b1b2cd..855bdec21 100644 --- a/examples/react-components/src/app/page.tsx +++ b/examples/react-components/src/app/page.tsx @@ -60,15 +60,7 @@ export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) { setConfig((prev) => { const newConfig = { ...prev }; - if (key === "email") { - newConfig.email = !prev.email; - if (!newConfig.email) { - newConfig.passkey = false; // Ensure passkey is off if email is off - } - } else if (key === "passkey") { - newConfig.passkey = !prev.passkey; - newConfig.email = newConfig.passkey; // Sync email with passkey's state - } else if (key !== "socials") { + if (key !== "socials") { newConfig[key] = !prev[key]; } diff --git a/packages/sdk-react/src/components/auth/Apple.tsx b/packages/sdk-react/src/components/auth/Apple.tsx index eedbc5e8f..f07e197c4 100644 --- a/packages/sdk-react/src/components/auth/Apple.tsx +++ b/packages/sdk-react/src/components/auth/Apple.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from "react"; import { sha256 } from "@noble/hashes/sha2"; import { bytesToHex } from "@noble/hashes/utils"; import AppleLogin from "react-apple-login"; -import { SiApple } from "@icons-pack/react-simple-icons"; import styles from "./Socials.module.css"; interface AppleAuthButtonProps { iframePublicKey: string; @@ -52,9 +51,8 @@ const AppleAuthButton: React.FC = ({ responseMode="fragment" render={({ onClick }) => (
- {/* */} - Apple + Continue with Apple
)} callback={(response) => { diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index bd6b86f0b..c2c600203 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -45,8 +45,8 @@ top: 50%; left: 50%; transform: translate(-50%, -50%); - font-size: 60px; - color: gray; + width: 60px; + height: 60px; } .authCard { diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index c1d6095b7..4452ecfa2 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -9,14 +9,11 @@ import { oauth, } from "../../actions/"; import { MuiPhone } from "./PhoneInput"; -import OTPInput from "./OtpInput"; +import OtpInput from "./OtpInput"; import GoogleAuthButton from "./Google"; import AppleAuthButton from "./Apple"; import FacebookAuthButton from "./Facebook"; import { CircularProgress } from "@mui/material"; -import GoogleIcon from "@mui/icons-material/Google"; -import FacebookIcon from "@mui/icons-material/Facebook"; -import AppleIcon from "@mui/icons-material/Apple"; import { parsePhoneNumberFromString } from 'libphonenumber-js'; interface AuthProps { @@ -255,13 +252,13 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { className={styles.circularProgress} /> {oauthLoading === "Google" && ( - + )} {oauthLoading === "Facebook" && ( - + )} {oauthLoading === "Apple" && ( - + )}
@@ -427,7 +424,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { {step === "otpEmail" ? email : formatPhoneNumber(phone)}
- = ({
{/* */} - Facebook + Continue with Facebook
); }; diff --git a/packages/sdk-react/src/components/auth/Google.tsx b/packages/sdk-react/src/components/auth/Google.tsx index 71b55d66e..ff94bca50 100644 --- a/packages/sdk-react/src/components/auth/Google.tsx +++ b/packages/sdk-react/src/components/auth/Google.tsx @@ -34,9 +34,8 @@ const GoogleAuthButton: React.FC = ({ return (
- {/* */} - Google + Continue with Google
); diff --git a/packages/sdk-react/src/components/auth/OTPInput.tsx b/packages/sdk-react/src/components/auth/OTPInput.tsx index b39fedb5b..cc5b49267 100644 --- a/packages/sdk-react/src/components/auth/OTPInput.tsx +++ b/packages/sdk-react/src/components/auth/OTPInput.tsx @@ -1,12 +1,12 @@ import React, { useState, forwardRef, useImperativeHandle } from "react"; import { TextField, Box } from "@mui/material"; -interface OTPInputProps { +interface OtpInputProps { onComplete: (otp: string) => void; hasError: boolean; } -const OTPInput = forwardRef( +const OtpInput = forwardRef( ({ onComplete, hasError }, ref) => { const [otp, setOtp] = useState(Array(6).fill("")); @@ -94,4 +94,4 @@ const OTPInput = forwardRef( } ); -export default OTPInput; +export default OtpInput; diff --git a/packages/sdk-react/src/components/auth/Socials.module.css b/packages/sdk-react/src/components/auth/Socials.module.css index 7ec1deae9..72397d4c3 100644 --- a/packages/sdk-react/src/components/auth/Socials.module.css +++ b/packages/sdk-react/src/components/auth/Socials.module.css @@ -1,5 +1,5 @@ .socialButton { - padding: 10px 16px 10px 16px; + padding: 10px 16px; gap: 8px; color: var(--Greyscale-900, #2b2f33); width: 100%; @@ -8,21 +8,19 @@ border: 1px solid var(--Greyscale-400, #a2a7ae); border-radius: 8px; cursor: pointer; - text-align: start; + text-align: center; + display: flex; align-items: center; justify-content: center; - display: flex; - align-items: center; - justify-content: start; - transition: background-color 0.2s ease; + transition: background-color 0.2s ease; } .socialButton:hover { - background-color: #f0f8ff; + background-color: #f0f8ff; } .iconSmall { - width: 24px; - height: 24px; + width: 20x; + height: 20px; margin-right: 4px; -} \ No newline at end of file +} From e1edd5428055099fc7037822d067835b3a079a16 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Mon, 11 Nov 2024 16:11:54 -0500 Subject: [PATCH 28/73] working passkey separation --- package.json | 2 + .../{public => src/assets}/apple.svg | 0 packages/sdk-react/src/assets/checkbox.svg | 10 + packages/sdk-react/src/assets/clock.svg | 3 + packages/sdk-react/src/assets/email.svg | 3 + .../{public => src/assets}/facebook.svg | 0 packages/sdk-react/src/assets/faceid.svg | 3 + packages/sdk-react/src/assets/fingerprint.svg | 3 + .../{public => src/assets}/google.svg | 0 packages/sdk-react/src/assets/keyhole.svg | 10 + packages/sdk-react/src/assets/sms.svg | 3 + packages/sdk-react/src/assets/turnkey.svg | 12 + .../sdk-react/src/components/auth/Apple.tsx | 3 +- .../src/components/auth/Auth.module.css | 48 +++ .../sdk-react/src/components/auth/Auth.tsx | 289 +++++++----------- .../src/components/auth/Facebook.tsx | 5 +- .../sdk-react/src/components/auth/Google.tsx | 3 +- .../sdk-react/src/components/auth/index.ts | 2 +- .../components/auth/{OTPInput.tsx => otp.tsx} | 0 pnpm-lock.yaml | 34 ++- rollup.config.base.mjs | 16 +- 21 files changed, 263 insertions(+), 186 deletions(-) rename packages/sdk-react/{public => src/assets}/apple.svg (100%) create mode 100644 packages/sdk-react/src/assets/checkbox.svg create mode 100644 packages/sdk-react/src/assets/clock.svg create mode 100644 packages/sdk-react/src/assets/email.svg rename packages/sdk-react/{public => src/assets}/facebook.svg (100%) create mode 100644 packages/sdk-react/src/assets/faceid.svg create mode 100644 packages/sdk-react/src/assets/fingerprint.svg rename packages/sdk-react/{public => src/assets}/google.svg (100%) create mode 100644 packages/sdk-react/src/assets/keyhole.svg create mode 100644 packages/sdk-react/src/assets/sms.svg create mode 100644 packages/sdk-react/src/assets/turnkey.svg rename packages/sdk-react/src/components/auth/{OTPInput.tsx => otp.tsx} (100%) diff --git a/package.json b/package.json index 1704d9a48..5bc959b70 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ "rollup-plugin-node-externals": "^6.1.2", "rollup-plugin-postcss": "^4.0.2", "rollup-preserve-directives": "^1.1.2", + "@rollup/plugin-alias": "5.1.1", + "@rollup/plugin-url":"8.0.2", "tsx": "^3.12.7", "typescript": "^5.1.4" }, diff --git a/packages/sdk-react/public/apple.svg b/packages/sdk-react/src/assets/apple.svg similarity index 100% rename from packages/sdk-react/public/apple.svg rename to packages/sdk-react/src/assets/apple.svg diff --git a/packages/sdk-react/src/assets/checkbox.svg b/packages/sdk-react/src/assets/checkbox.svg new file mode 100644 index 000000000..b8c065755 --- /dev/null +++ b/packages/sdk-react/src/assets/checkbox.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/sdk-react/src/assets/clock.svg b/packages/sdk-react/src/assets/clock.svg new file mode 100644 index 000000000..97c83c148 --- /dev/null +++ b/packages/sdk-react/src/assets/clock.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/sdk-react/src/assets/email.svg b/packages/sdk-react/src/assets/email.svg new file mode 100644 index 000000000..76f97cbb2 --- /dev/null +++ b/packages/sdk-react/src/assets/email.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/sdk-react/public/facebook.svg b/packages/sdk-react/src/assets/facebook.svg similarity index 100% rename from packages/sdk-react/public/facebook.svg rename to packages/sdk-react/src/assets/facebook.svg diff --git a/packages/sdk-react/src/assets/faceid.svg b/packages/sdk-react/src/assets/faceid.svg new file mode 100644 index 000000000..0b0e9cffe --- /dev/null +++ b/packages/sdk-react/src/assets/faceid.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/sdk-react/src/assets/fingerprint.svg b/packages/sdk-react/src/assets/fingerprint.svg new file mode 100644 index 000000000..567225f21 --- /dev/null +++ b/packages/sdk-react/src/assets/fingerprint.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/sdk-react/public/google.svg b/packages/sdk-react/src/assets/google.svg similarity index 100% rename from packages/sdk-react/public/google.svg rename to packages/sdk-react/src/assets/google.svg diff --git a/packages/sdk-react/src/assets/keyhole.svg b/packages/sdk-react/src/assets/keyhole.svg new file mode 100644 index 000000000..d0d179a8f --- /dev/null +++ b/packages/sdk-react/src/assets/keyhole.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/sdk-react/src/assets/sms.svg b/packages/sdk-react/src/assets/sms.svg new file mode 100644 index 000000000..6de1055f3 --- /dev/null +++ b/packages/sdk-react/src/assets/sms.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/sdk-react/src/assets/turnkey.svg b/packages/sdk-react/src/assets/turnkey.svg new file mode 100644 index 000000000..549b30a0c --- /dev/null +++ b/packages/sdk-react/src/assets/turnkey.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/sdk-react/src/components/auth/Apple.tsx b/packages/sdk-react/src/components/auth/Apple.tsx index f07e197c4..6bd38b526 100644 --- a/packages/sdk-react/src/components/auth/Apple.tsx +++ b/packages/sdk-react/src/components/auth/Apple.tsx @@ -3,6 +3,7 @@ import { sha256 } from "@noble/hashes/sha2"; import { bytesToHex } from "@noble/hashes/utils"; import AppleLogin from "react-apple-login"; import styles from "./Socials.module.css"; +import appleIcon from "assets/apple.svg"; interface AppleAuthButtonProps { iframePublicKey: string; clientId: string; @@ -51,7 +52,7 @@ const AppleAuthButton: React.FC = ({ responseMode="fragment" render={({ onClick }) => (
- + Continue with Apple
)} diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index c2c600203..c8b978bd2 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -271,3 +271,51 @@ button:disabled { color: #ff4c4c; font-size: 12px; } + +.passkeyIconContainer { + display: flex; + justify-content: center; + align-items: center; + gap: 8px; + margin: 16px 0; +} + +.rowsContainer { + display: flex; + flex-direction: column; + justify-content: center; + gap: 8px; + margin-top: 16px; + width: 95%; + margin-left: auto; + margin-right: auto; + margin-bottom: 32px; +} + +.row { + display: flex; + align-items: center; +} + +.rowIcon { + padding-right: 8px; +} + + +.noPasskeyLink { + color: var(--Blue-500, #4C48FF); + font-size: 0.9rem; + margin-top: 8px; + cursor: pointer; + align-items: center; + text-align: center; + display: inline-block; +} + +.passkeyContainer { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + margin-top: 16px; +} diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 4452ecfa2..c8f4c7d5f 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -9,12 +9,23 @@ import { oauth, } from "../../actions/"; import { MuiPhone } from "./PhoneInput"; -import OtpInput from "./OtpInput"; +import OtpInput from "./otp"; import GoogleAuthButton from "./Google"; import AppleAuthButton from "./Apple"; import FacebookAuthButton from "./Facebook"; import { CircularProgress } from "@mui/material"; import { parsePhoneNumberFromString } from 'libphonenumber-js'; +import turnkeyIcon from "assets/turnkey.svg" +import googleIcon from "assets/google.svg"; +import facebookIcon from "assets/facebook.svg"; +import appleIcon from "assets/apple.svg"; +import emailIcon from "assets/email.svg"; +import smsIcon from "assets/sms.svg"; +import faceidIcon from "assets/faceid.svg" +import fingerprintIcon from "assets/fingerprint.svg" +import checkboxIcon from "assets/checkbox.svg" +import clockIcon from "assets/clock.svg" +import keyholeIcon from "assets/keyhole.svg" interface AuthProps { onHandleAuthSuccess: () => Promise; @@ -39,7 +50,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const [oauthLoading, setOauthLoading] = useState(""); const [suborgId, setSuborgId] = useState(""); const [resendText, setResendText] = useState("Re-send Code"); - const [firstTimePasskey, setFirstTimePasskey] = useState(""); + const [passkeySignupScreen, setPasskeySignupScreen] = useState(false); const otpInputRef = useRef(null); const formatPhoneNumber = (phone: string) => { @@ -97,67 +108,72 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { } }; - const handleLoginWithPasskey = async () => { - // Step 1: Try to retrieve the suborg by email - const getSuborgsResponse = await getSuborgs({ - filterType: "EMAIL", - filterValue: email, - }); - const existingSuborgId = getSuborgsResponse!.organizationIds[0]; + const handleSignupWithPasskey = async () => { + const siteInfo = `${window.location.href} - ${new Date().toLocaleString(undefined, { + year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' + })}`; + const { encodedChallenge, attestation } = + (await passkeyClient?.createUserPasskey({ + publicKey: { user: { name: siteInfo, displayName: siteInfo } }, + })) || {}; - if (existingSuborgId) { - // If a suborg exists, use it to create a read/write session without a new passkey - const sessionResponse = await passkeyClient?.createReadWriteSession({ - organizationId: existingSuborgId, - targetPublicKey: authIframeClient?.iframePublicKey!, - }); + if (encodedChallenge && attestation) { + // Use the generated passkey to create a new suborg + const createSuborgResponse = await createSuborg({ + email, + passkey: { + authenticatorName: "First Passkey", + challenge: encodedChallenge, + attestation, + }, + }); - if (sessionResponse?.credentialBundle) { - await handleAuthSuccess(sessionResponse.credentialBundle); - } else { - setError("Failed to complete passkey login."); - } + const suborgId = createSuborgResponse?.subOrganizationId; + console.log(suborgId) + }else { + setError("Failed to create user passkey."); + } + const sessionResponse = await passkeyClient?.createReadWriteSession({ targetPublicKey: authIframeClient?.iframePublicKey!,}) + if (sessionResponse?.credentialBundle) { + await handleAuthSuccess(sessionResponse.credentialBundle); } else { - // If no suborg exists, first create a user passkey - const { encodedChallenge, attestation } = - (await passkeyClient?.createUserPasskey({ - publicKey: { user: { name: email, displayName: email } }, - })) || {}; - - if (encodedChallenge && attestation) { - // Use the generated passkey to create a new suborg - const createSuborgResponse = await createSuborg({ - email, - passkey: { - authenticatorName: "First Passkey", - challenge: encodedChallenge, - attestation, - }, - }); + setError("Failed to complete passkey login."); + } +} + const handleLoginWithPasskey = async () => { + const sessionResponse = await passkeyClient?.createReadWriteSession({ targetPublicKey: authIframeClient?.iframePublicKey!,}) + if (sessionResponse?.credentialBundle) { + await handleAuthSuccess(sessionResponse.credentialBundle); + } else { + setError("Failed to complete passkey login."); + } + } + + // if (existingSuborgId) { + // // If a suborg exists, use it to create a read/write session without a new passkey + // const sessionResponse = await passkeyClient?.createReadWriteSession({ + // organizationId: existingSuborgId, + // targetPublicKey: authIframeClient?.iframePublicKey!, + // }); - const newSuborgId = createSuborgResponse?.subOrganizationId; + // if (sessionResponse?.credentialBundle) { + // await handleAuthSuccess(sessionResponse.credentialBundle); + // } else { + // setError("Failed to complete passkey login."); + // } + // } else { + // If no suborg exists, first create a user passkey - if (newSuborgId) { - // With the new suborg, create a read/write session - const newSessionResponse = - await passkeyClient?.createReadWriteSession({ - organizationId: newSuborgId, - targetPublicKey: authIframeClient?.iframePublicKey!, - }); - - if (newSessionResponse?.credentialBundle) { - await handleAuthSuccess(newSessionResponse.credentialBundle); - } else { - setError("Failed to complete passkey login with new suborg."); - } - } else { - setError("Failed to create suborg with passkey."); - } - } else { - setError("Failed to create user passkey."); - } - } - }; + // } else { + // setError("Failed to complete passkey login with new suborg."); + // } + // } else { + // setError("Failed to create suborg with passkey."); + // } + // } else { + // setError("Failed to create user passkey."); + // } + // } const handleOtpLogin = async ( type: "EMAIL" | "PHONE_NUMBER", @@ -252,62 +268,48 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { className={styles.circularProgress} /> {oauthLoading === "Google" && ( - + )} {oauthLoading === "Facebook" && ( - + )} {oauthLoading === "Apple" && ( - + )}
Powered by - - - - - - - - - - - - - - +
- ) : ( + ) : passkeySignupScreen ?
+ + +
+

Secure your account with a passkey

+ +
+
+ + Log in with Touch ID, Face ID, or a security key +
+
+ + More secure than a password +
+
+ + Takes seconds to set up and use +
+ +
+ +
: (

{otpId ? "Enter verification code" : "Log in or sign up"}

@@ -340,16 +342,14 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => {
} {authConfig.passkeyEnabled && !otpId && ( -
+
- +
setPasskeySignupScreen(true)}>I don't have a passkey
)} {!otpId && @@ -386,35 +386,9 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => {
{step === "otpEmail" ? ( - - - + ) : ( - - - + )}
@@ -526,48 +500,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => {
window.location.href = "https://www.turnkey.com/"} className={styles.poweredBy}> Secured by - - - - - - - - - - - - - - +
)} diff --git a/packages/sdk-react/src/components/auth/Facebook.tsx b/packages/sdk-react/src/components/auth/Facebook.tsx index 5a7022480..a75d08e49 100644 --- a/packages/sdk-react/src/components/auth/Facebook.tsx +++ b/packages/sdk-react/src/components/auth/Facebook.tsx @@ -5,7 +5,7 @@ import styles from "./Socials.module.css"; import { exchangeCodeForToken, generateChallengePair } from "./facebook-utils"; import { sha256 } from "@noble/hashes/sha256"; import { bytesToHex } from "@noble/hashes/utils"; - +import facebookIcon from "assets/facebook.svg"; interface FacebookAuthButtonProps { iframePublicKey: string; clientId: string; @@ -93,8 +93,7 @@ const FacebookAuthButton: React.FC = ({ return (
- {/* */} - + Continue with Facebook
); diff --git a/packages/sdk-react/src/components/auth/Google.tsx b/packages/sdk-react/src/components/auth/Google.tsx index ff94bca50..68c9361b7 100644 --- a/packages/sdk-react/src/components/auth/Google.tsx +++ b/packages/sdk-react/src/components/auth/Google.tsx @@ -2,6 +2,7 @@ import { GoogleOAuthProvider } from "@react-oauth/google"; import { sha256 } from "@noble/hashes/sha2"; import { bytesToHex } from "@noble/hashes/utils"; import styles from "./Socials.module.css"; +import googleIcon from "assets/google.svg"; interface GoogleAuthButtonProps { iframePublicKey: string; @@ -34,7 +35,7 @@ const GoogleAuthButton: React.FC = ({ return (
- + Continue with Google
diff --git a/packages/sdk-react/src/components/auth/index.ts b/packages/sdk-react/src/components/auth/index.ts index 4adba6e6c..6856c482e 100644 --- a/packages/sdk-react/src/components/auth/index.ts +++ b/packages/sdk-react/src/components/auth/index.ts @@ -1 +1 @@ -export { default as Auth } from "./Auth"; +export { default as Auth } from "./Auth"; \ No newline at end of file diff --git a/packages/sdk-react/src/components/auth/OTPInput.tsx b/packages/sdk-react/src/components/auth/otp.tsx similarity index 100% rename from packages/sdk-react/src/components/auth/OTPInput.tsx rename to packages/sdk-react/src/components/auth/otp.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9bc3deac..4d409cd57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,9 +29,15 @@ importers: '@jest/types': specifier: ^29.3.1 version: 29.4.3 + '@rollup/plugin-alias': + specifier: 5.1.1 + version: 5.1.1(rollup@4.22.4) '@rollup/plugin-typescript': specifier: ^11.1.5 version: 11.1.5(rollup@4.22.4)(typescript@5.1.5) + '@rollup/plugin-url': + specifier: 8.0.2 + version: 8.0.2(rollup@4.22.4) '@tsconfig/node16-strictest': specifier: ^1.0.4 version: 1.0.4 @@ -10483,6 +10489,18 @@ packages: - supports-color dev: false + /@rollup/plugin-alias@5.1.1(rollup@4.22.4): + resolution: {integrity: sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + rollup: 4.22.4 + dev: true + /@rollup/plugin-typescript@11.1.5(rollup@4.22.4)(typescript@5.1.5): resolution: {integrity: sha512-rnMHrGBB0IUEv69Q8/JGRD/n4/n6b3nfpufUu26axhUcboUzv/twfZU8fIBbTOphRAe0v8EyxzeDpKXqGHfyDA==} engines: {node: '>=14.0.0'} @@ -10502,6 +10520,21 @@ packages: typescript: 5.1.5 dev: true + /@rollup/plugin-url@8.0.2(rollup@4.22.4): + resolution: {integrity: sha512-5yW2LP5NBEgkvIRSSEdJkmxe5cUNZKG3eenKtfJvSkxVm/xTTu7w+ayBtNwhozl1ZnTUCU0xFaRQR+cBl2H7TQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.0.5(rollup@4.22.4) + make-dir: 3.1.0 + mime: 3.0.0 + rollup: 4.22.4 + dev: true + /@rollup/pluginutils@5.0.5(rollup@4.22.4): resolution: {integrity: sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==} engines: {node: '>=14.0.0'} @@ -20250,7 +20283,6 @@ packages: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} hasBin: true - dev: false /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} diff --git a/rollup.config.base.mjs b/rollup.config.base.mjs index ad9c7be67..794c7899c 100644 --- a/rollup.config.base.mjs +++ b/rollup.config.base.mjs @@ -2,10 +2,13 @@ import typescript from "@rollup/plugin-typescript"; import nodeExternals from "rollup-plugin-node-externals"; import path from "node:path"; import postcss from 'rollup-plugin-postcss'; -import preserveDirectives from 'rollup-preserve-directives' +import preserveDirectives from 'rollup-preserve-directives'; +import url from '@rollup/plugin-url'; +import alias from '@rollup/plugin-alias'; const getFormatConfig = (format) => { const pkgPath = path.join(process.cwd(), "package.json"); + const __dirname = path.dirname(new URL(import.meta.url).pathname); return { input: 'src/index.ts', @@ -17,6 +20,11 @@ const getFormatConfig = (format) => { sourcemap: true, }, plugins: [ + alias({ + entries: [ + { find: 'assets', replacement: path.resolve(__dirname, 'packages/sdk-react/src/assets') } + ] + }), postcss({ modules: true, extensions: ['.css', '.scss'], @@ -40,6 +48,12 @@ const getFormatConfig = (format) => { packagePath: pkgPath, builtinsPrefix: 'ignore', }), + url({ + include: ['**/*.svg', '**/*.png', '**/*.jpg', '**/*.gif'], + limit: 8192, + emitFiles: true, + fileName: '[name].[hash][extname]', + }), ], }; }; From 33418837ac65714983d46462fb7745ab95945963 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Mon, 11 Nov 2024 16:36:18 -0500 Subject: [PATCH 29/73] working build without typecheck issues --- examples/react-components/src/app/Switch.tsx | 2 +- .../src/app/{index.module.css => index.css} | 6 +- examples/react-components/src/app/page.tsx | 132 ++++----- package.json | 2 +- .../sdk-react/src/components/auth/Apple.tsx | 2 +- .../src/components/auth/Auth.module.css | 7 +- .../sdk-react/src/components/auth/Auth.tsx | 271 +++++++++--------- .../src/components/auth/Facebook.tsx | 4 +- .../sdk-react/src/components/auth/Google.tsx | 10 +- .../src/components/auth/PhoneInput.tsx | 4 +- .../{facebook-utils.ts => facebookUtils.ts} | 0 .../sdk-react/src/components/auth/index.ts | 2 +- .../sdk-react/src/components/auth/otp.tsx | 2 +- packages/sdk-react/src/global.d.ts | 11 + pnpm-lock.yaml | 4 +- 15 files changed, 235 insertions(+), 224 deletions(-) rename examples/react-components/src/app/{index.module.css => index.css} (98%) rename packages/sdk-react/src/components/auth/{facebook-utils.ts => facebookUtils.ts} (100%) create mode 100644 packages/sdk-react/src/global.d.ts diff --git a/examples/react-components/src/app/Switch.tsx b/examples/react-components/src/app/Switch.tsx index abfb5a10c..f43b6d016 100644 --- a/examples/react-components/src/app/Switch.tsx +++ b/examples/react-components/src/app/Switch.tsx @@ -15,7 +15,7 @@ const CustomSwitch = styled(Switch)(({ theme }) => ({ margin: 2, }, "& .MuiSwitch-switchBase.Mui-checked": { - color: "var(--Greyscale-900, #2B2F33)", + color: "var(--Greyscale-900, #2B2F33)", }, "& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track": { backgroundColor: "var(--Greyscale-900, #2B2F33)", diff --git a/examples/react-components/src/app/index.module.css b/examples/react-components/src/app/index.css similarity index 98% rename from examples/react-components/src/app/index.module.css rename to examples/react-components/src/app/index.css index 751d531a3..4699a527b 100644 --- a/examples/react-components/src/app/index.module.css +++ b/examples/react-components/src/app/index.css @@ -92,7 +92,7 @@ display: flex; margin-left: 16px; padding-left: 16px; - padding-top:4px; + padding-top: 4px; padding-bottom: 4px; justify-content: space-between; align-items: center; @@ -127,10 +127,10 @@ height: 18px; } -.socialContainer{ +.socialContainer { border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); padding: 8px 16px; padding-bottom: 16px; background-color: #ffffff; -} \ No newline at end of file +} diff --git a/examples/react-components/src/app/page.tsx b/examples/react-components/src/app/page.tsx index 855bdec21..6cc851342 100644 --- a/examples/react-components/src/app/page.tsx +++ b/examples/react-components/src/app/page.tsx @@ -1,13 +1,12 @@ "use client"; -import styles from "./index.module.css"; import * as React from "react"; import { useState } from "react"; import { useTurnkey, Auth } from "@turnkey/sdk-react"; -import { Switch, Typography } from "@mui/material"; +import { Typography } from "@mui/material"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; -import AppsIcon from "@mui/icons-material/Apps"; import CustomSwitch from "./Switch"; +import "./index.css"; // Define types for config and socials interface SocialConfig { @@ -24,16 +23,7 @@ interface Config { socials: SocialConfig; } -interface AuthPageProps { - turnkeyClientConfig: { - apiBaseUrl: string; - defaultOrganizationId: string; - apiPublicKey: string; - apiPrivateKey: string; - }; -} - -export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) { +export default function AuthPage() { const { authIframeClient } = useTurnkey(); const [orgData, setOrgData] = useState(); @@ -120,17 +110,17 @@ export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) { }; return ( -
+
{!orgData && ( -
- +
+ Authentication config -
-
-
- +
+
+
+ Email
-
-
- +
+
+ Passkey
-
-
- +
+
+ Phone
toggleConfig("phone")} />
-
-
-
- - Socials -
- toggleSocials("enabled")} - /> - - -
-
-
- - Google -
- toggleSocials("google")} - /> -
-
-
- - Apple -
- toggleSocials("apple")} - /> +
+
+
+ + Socials
-
-
- - Facebook -
- toggleSocials("facebook")} - /> + toggleSocials("enabled")} + /> +
+
+
+ + Google
+ toggleSocials("google")} + /> +
+
+
+ + Apple
+ toggleSocials("apple")} + /> +
+
+
+ + Facebook
+ toggleSocials("facebook")} + /> +
+
+
-
+
-
- +
+ Copy config
@@ -217,7 +205,7 @@ export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) {
)} {orgData ? ( -
+
YOU ARE AUTHENTICATED ON TURNKEY!
Organization Id: {orgData.organizationId} @@ -233,7 +221,7 @@ export default function AuthPage({ turnkeyClientConfig }: AuthPageProps) {
) : ( -
+
= ({ responseMode="fragment" render={({ onClick }) => (
- + Continue with Apple
)} diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index c8b978bd2..e33ee81b6 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -276,7 +276,7 @@ button:disabled { display: flex; justify-content: center; align-items: center; - gap: 8px; + gap: 8px; margin: 16px 0; } @@ -288,7 +288,7 @@ button:disabled { margin-top: 16px; width: 95%; margin-left: auto; - margin-right: auto; + margin-right: auto; margin-bottom: 32px; } @@ -301,9 +301,8 @@ button:disabled { padding-right: 8px; } - .noPasskeyLink { - color: var(--Blue-500, #4C48FF); + color: var(--Blue-500, #4c48ff); font-size: 0.9rem; margin-top: 8px; cursor: pointer; diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index c8f4c7d5f..38f041d21 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -14,18 +14,18 @@ import GoogleAuthButton from "./Google"; import AppleAuthButton from "./Apple"; import FacebookAuthButton from "./Facebook"; import { CircularProgress } from "@mui/material"; -import { parsePhoneNumberFromString } from 'libphonenumber-js'; -import turnkeyIcon from "assets/turnkey.svg" +import { parsePhoneNumberFromString } from "libphonenumber-js"; +import turnkeyIcon from "assets/turnkey.svg"; import googleIcon from "assets/google.svg"; import facebookIcon from "assets/facebook.svg"; import appleIcon from "assets/apple.svg"; import emailIcon from "assets/email.svg"; import smsIcon from "assets/sms.svg"; -import faceidIcon from "assets/faceid.svg" -import fingerprintIcon from "assets/fingerprint.svg" -import checkboxIcon from "assets/checkbox.svg" -import clockIcon from "assets/clock.svg" -import keyholeIcon from "assets/keyhole.svg" +import faceidIcon from "assets/faceid.svg"; +import fingerprintIcon from "assets/fingerprint.svg"; +import checkboxIcon from "assets/checkbox.svg"; +import clockIcon from "assets/clock.svg"; +import keyholeIcon from "assets/keyhole.svg"; interface AuthProps { onHandleAuthSuccess: () => Promise; @@ -52,7 +52,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const [resendText, setResendText] = useState("Re-send Code"); const [passkeySignupScreen, setPasskeySignupScreen] = useState(false); const otpInputRef = useRef(null); - + const formatPhoneNumber = (phone: string) => { const phoneNumber = parsePhoneNumberFromString(phone); return phoneNumber ? phoneNumber.formatInternational() : phone; @@ -81,25 +81,24 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { ) => { const getSuborgsResponse = await getSuborgs({ filterType, filterValue }); let suborgId = getSuborgsResponse!.organizationIds[0]; - + if (!suborgId) { const createSuborgData: Record = { ...additionalData, }; - + if (filterType === "EMAIL") { createSuborgData.email = filterValue; } else if (filterType === "PHONE_NUMBER") { createSuborgData.phoneNumber = filterValue; } - + const createSuborgResponse = await createSuborg(createSuborgData); suborgId = createSuborgResponse?.subOrganizationId!; } - + return suborgId; }; - const handleAuthSuccess = async (credentialBundle: any) => { if (credentialBundle) { @@ -109,71 +108,83 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { }; const handleSignupWithPasskey = async () => { - const siteInfo = `${window.location.href} - ${new Date().toLocaleString(undefined, { - year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' - })}`; + const siteInfo = `${window.location.href} - ${new Date().toLocaleString( + undefined, + { + year: "numeric", + month: "long", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + } + )}`; const { encodedChallenge, attestation } = - (await passkeyClient?.createUserPasskey({ - publicKey: { user: { name: siteInfo, displayName: siteInfo } }, - })) || {}; - - if (encodedChallenge && attestation) { - // Use the generated passkey to create a new suborg - const createSuborgResponse = await createSuborg({ - email, - passkey: { - authenticatorName: "First Passkey", - challenge: encodedChallenge, - attestation, - }, - }); + (await passkeyClient?.createUserPasskey({ + publicKey: { user: { name: siteInfo, displayName: siteInfo } }, + })) || {}; - const suborgId = createSuborgResponse?.subOrganizationId; - console.log(suborgId) - }else { - setError("Failed to create user passkey."); - } - const sessionResponse = await passkeyClient?.createReadWriteSession({ targetPublicKey: authIframeClient?.iframePublicKey!,}) + if (encodedChallenge && attestation) { + // Use the generated passkey to create a new suborg + const createSuborgResponse = await createSuborg({ + email, + passkey: { + authenticatorName: "First Passkey", + challenge: encodedChallenge, + attestation, + }, + }); + + const suborgId = createSuborgResponse?.subOrganizationId; + console.log(suborgId); + } else { + setError("Failed to create user passkey."); + } + const sessionResponse = await passkeyClient?.createReadWriteSession({ + targetPublicKey: authIframeClient?.iframePublicKey!, + }); if (sessionResponse?.credentialBundle) { await handleAuthSuccess(sessionResponse.credentialBundle); } else { setError("Failed to complete passkey login."); } -} + }; const handleLoginWithPasskey = async () => { - const sessionResponse = await passkeyClient?.createReadWriteSession({ targetPublicKey: authIframeClient?.iframePublicKey!,}) + const sessionResponse = await passkeyClient?.createReadWriteSession({ + targetPublicKey: authIframeClient?.iframePublicKey!, + }); if (sessionResponse?.credentialBundle) { await handleAuthSuccess(sessionResponse.credentialBundle); } else { setError("Failed to complete passkey login."); } - } - - // if (existingSuborgId) { - // // If a suborg exists, use it to create a read/write session without a new passkey - // const sessionResponse = await passkeyClient?.createReadWriteSession({ - // organizationId: existingSuborgId, - // targetPublicKey: authIframeClient?.iframePublicKey!, - // }); - - // if (sessionResponse?.credentialBundle) { - // await handleAuthSuccess(sessionResponse.credentialBundle); - // } else { - // setError("Failed to complete passkey login."); - // } - // } else { - // If no suborg exists, first create a user passkey - - // } else { - // setError("Failed to complete passkey login with new suborg."); - // } - // } else { - // setError("Failed to create suborg with passkey."); - // } - // } else { - // setError("Failed to create user passkey."); - // } - // } + }; + + // if (existingSuborgId) { + // // If a suborg exists, use it to create a read/write session without a new passkey + // const sessionResponse = await passkeyClient?.createReadWriteSession({ + // organizationId: existingSuborgId, + // targetPublicKey: authIframeClient?.iframePublicKey!, + // }); + + // if (sessionResponse?.credentialBundle) { + // await handleAuthSuccess(sessionResponse.credentialBundle); + // } else { + // setError("Failed to complete passkey login."); + // } + // } else { + // If no suborg exists, first create a user passkey + + // } else { + // setError("Failed to complete passkey login with new suborg."); + // } + // } else { + // setError("Failed to create suborg with passkey."); + // } + // } else { + // setError("Failed to create user passkey."); + // } + // } const handleOtpLogin = async ( type: "EMAIL" | "PHONE_NUMBER", @@ -181,7 +192,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { otpType: string ) => { const suborgId = await handleGetOrCreateSuborg(type, value); - console.log(suborgId) + console.log(suborgId); const initAuthResponse = await initOtpAuth({ suborgID: suborgId, otpType, @@ -193,7 +204,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { }; const handleValidateOtp = async (otp: string) => { - setOtpError(null) + setOtpError(null); const authResponse = await otpAuth({ suborgID: suborgId, otpId: otpId!, @@ -204,7 +215,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { await handleAuthSuccess(authResponse.credentialBundle); } else { setOtpError("Invalid code. Please try again"); - otpInputRef.current.resetOtp(); + otpInputRef.current.resetOtp(); } }; @@ -265,51 +276,52 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { {oauthLoading === "Google" && ( - + )} {oauthLoading === "Facebook" && ( - + )} {oauthLoading === "Apple" && ( - + )}
Powered by - +
- ) : passkeySignupScreen ?
- - -
-

Secure your account with a passkey

- -
-
- - Log in with Touch ID, Face ID, or a security key -
-
- - More secure than a password -
-
- - Takes seconds to set up and use -
+ ) : passkeySignupScreen ? ( +
+
+ + +
+
+

Secure your account with a passkey

+
-
- -
: ( +
+
+ + Log in with Touch ID, Face ID, or a security key +
+
+ + More secure than a password +
+
+ + Takes seconds to set up and use +
+
+ +
+ ) : (

{otpId ? "Enter verification code" : "Log in or sign up"}

@@ -335,21 +347,22 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => {
)} -{ - authConfig.emailEnabled && authConfig.passkeyEnabled && !otpId && -
- OR -
-} + {authConfig.emailEnabled && authConfig.passkeyEnabled && !otpId && ( +
+ OR +
+ )} {authConfig.passkeyEnabled && !otpId && ( -
- -
setPasskeySignupScreen(true)}>I don't have a passkey
+
setPasskeySignupScreen(true)} + > + I don't have a passkey +
)} {!otpId && @@ -386,28 +399,26 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => {
{step === "otpEmail" ? ( - + ) : ( - + )}
- Enter the 6-digit code we sent to{" "} + Enter the 6-digit code we sent to{" "}
{step === "otpEmail" ? email : formatPhoneNumber(phone)}
)} -
- {otpError ? otpError : " "} -
+
{otpError ? otpError : " "}
{!otpId && (authConfig.googleEnabled || @@ -455,8 +466,8 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => {
)} - {!otpId ? ( -
+ {!otpId ? ( + - ) : ( -
+
+ ) : ( +
= ({ onHandleAuthSuccess, authConfig }) => { {resendText} -
- )} - +
+ )} -
window.location.href = "https://www.turnkey.com/"} className={styles.poweredBy}> +
(window.location.href = "https://www.turnkey.com/")} + className={styles.poweredBy} + > Secured by - +
)} diff --git a/packages/sdk-react/src/components/auth/Facebook.tsx b/packages/sdk-react/src/components/auth/Facebook.tsx index a75d08e49..06b7a82c3 100644 --- a/packages/sdk-react/src/components/auth/Facebook.tsx +++ b/packages/sdk-react/src/components/auth/Facebook.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import styles from "./Socials.module.css"; -import { exchangeCodeForToken, generateChallengePair } from "./facebook-utils"; +import { exchangeCodeForToken, generateChallengePair } from "./facebookUtils"; import { sha256 } from "@noble/hashes/sha256"; import { bytesToHex } from "@noble/hashes/utils"; import facebookIcon from "assets/facebook.svg"; @@ -93,7 +93,7 @@ const FacebookAuthButton: React.FC = ({ return (
- + Continue with Facebook
); diff --git a/packages/sdk-react/src/components/auth/Google.tsx b/packages/sdk-react/src/components/auth/Google.tsx index 68c9361b7..aaa41c3e3 100644 --- a/packages/sdk-react/src/components/auth/Google.tsx +++ b/packages/sdk-react/src/components/auth/Google.tsx @@ -15,13 +15,11 @@ declare global { } } - const GoogleAuthButton: React.FC = ({ iframePublicKey, clientId, onSuccess, }) => { - const handleLogin = async () => { const nonce = bytesToHex(sha256(iframePublicKey)); await window.google?.accounts.id.initialize({ @@ -34,10 +32,10 @@ const GoogleAuthButton: React.FC = ({ return ( -
- - Continue with Google -
+
+ + Continue with Google +
); }; diff --git a/packages/sdk-react/src/components/auth/PhoneInput.tsx b/packages/sdk-react/src/components/auth/PhoneInput.tsx index 75b64bac5..a3932522a 100644 --- a/packages/sdk-react/src/components/auth/PhoneInput.tsx +++ b/packages/sdk-react/src/components/auth/PhoneInput.tsx @@ -10,10 +10,12 @@ import { import { CountryIso2, defaultCountries, - FlagImage, parseCountry, usePhoneInput, } from "react-international-phone"; +import { FlagImage as OriginalFlagImage } from "react-international-phone"; + +const FlagImage = OriginalFlagImage as React.ElementType; // fix for typecheck issue const countries = defaultCountries.filter((country) => { const { iso2 } = parseCountry(country); diff --git a/packages/sdk-react/src/components/auth/facebook-utils.ts b/packages/sdk-react/src/components/auth/facebookUtils.ts similarity index 100% rename from packages/sdk-react/src/components/auth/facebook-utils.ts rename to packages/sdk-react/src/components/auth/facebookUtils.ts diff --git a/packages/sdk-react/src/components/auth/index.ts b/packages/sdk-react/src/components/auth/index.ts index 6856c482e..4adba6e6c 100644 --- a/packages/sdk-react/src/components/auth/index.ts +++ b/packages/sdk-react/src/components/auth/index.ts @@ -1 +1 @@ -export { default as Auth } from "./Auth"; \ No newline at end of file +export { default as Auth } from "./Auth"; diff --git a/packages/sdk-react/src/components/auth/otp.tsx b/packages/sdk-react/src/components/auth/otp.tsx index cc5b49267..6c906601f 100644 --- a/packages/sdk-react/src/components/auth/otp.tsx +++ b/packages/sdk-react/src/components/auth/otp.tsx @@ -63,7 +63,7 @@ const OtpInput = forwardRef( {otp.map((digit, index) => ( Date: Tue, 12 Nov 2024 15:13:25 -0500 Subject: [PATCH 30/73] draggable config --- examples/react-components/package.json | 1 + examples/react-components/src/app/page.tsx | 177 +++--- .../sdk-react/src/components/auth/Auth.tsx | 558 ++++++++---------- pnpm-lock.yaml | 83 ++- 4 files changed, 412 insertions(+), 407 deletions(-) diff --git a/examples/react-components/package.json b/examples/react-components/package.json index dd9490b8b..d39ce6569 100644 --- a/examples/react-components/package.json +++ b/examples/react-components/package.json @@ -12,6 +12,7 @@ "dependencies": { "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", + "@hello-pangea/dnd": "^17.0.0", "@mui/icons-material": "^6.1.5", "@mui/material": "^6.1.5", "@turnkey/sdk-react": "workspace:*", diff --git a/examples/react-components/src/app/page.tsx b/examples/react-components/src/app/page.tsx index 6cc851342..455ef4e7d 100644 --- a/examples/react-components/src/app/page.tsx +++ b/examples/react-components/src/app/page.tsx @@ -6,6 +6,7 @@ import { useTurnkey, Auth } from "@turnkey/sdk-react"; import { Typography } from "@mui/material"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; import CustomSwitch from "./Switch"; +import { DragDropContext, Droppable, Draggable, DropResult } from "@hello-pangea/dnd"; import "./index.css"; // Define types for config and socials @@ -34,10 +35,12 @@ export default function AuthPage() { setOrgData(whoamiResponse as any); }; + const [configOrder, setConfigOrder] = useState(["email", "phone", "passkey", "socials"]); + const [config, setConfig] = useState({ email: true, - passkey: true, phone: true, + passkey: true, socials: { enabled: false, google: false, @@ -49,11 +52,9 @@ export default function AuthPage() { const toggleConfig = (key: keyof Config) => { setConfig((prev) => { const newConfig = { ...prev }; - if (key !== "socials") { newConfig[key] = !prev[key]; } - return newConfig; }); }; @@ -72,7 +73,6 @@ export default function AuthPage() { }, }; } - if (prev.socials.enabled) { return { ...prev, @@ -82,7 +82,6 @@ export default function AuthPage() { }, }; } - return prev; }); }; @@ -109,6 +108,17 @@ export default function AuthPage() { facebookEnabled: config.socials.facebook, }; + const onDragEnd = (result: DropResult) => { + const { destination, source } = result; + if (!destination) return; + + const reorderedConfig = Array.from(configOrder); + const [movedItem] = reorderedConfig.splice(source.index, 1); + reorderedConfig.splice(destination.index, 0, movedItem); + + setConfigOrder(reorderedConfig); + }; + return (
{!orgData && ( @@ -117,82 +127,88 @@ export default function AuthPage() { Authentication config -
-
-
- - Email -
- toggleConfig("email")} - /> -
- -
-
- - Passkey -
- toggleConfig("passkey")} - /> -
- -
-
- - Phone -
- toggleConfig("phone")} - /> -
-
-
-
- - Socials + + + {(provided) => ( +
+ {configOrder.map((key, index) => ( + key === "socials" ? ( + + {(provided) => ( +
+
+
+ Drag handle + Socials +
+ toggleSocials("enabled")} + /> +
+
+
+ + Google +
+ toggleSocials("google")} + /> +
+
+
+ + Apple +
+ toggleSocials("apple")} + /> +
+
+
+ + Facebook +
+ toggleSocials("facebook")} + /> +
+
+ )} +
+ ) : ( + + {(provided) => ( +
+
+ Drag handle + {key.charAt(0).toUpperCase() + key.slice(1)} +
+ toggleConfig(key as keyof Config)} + /> +
+ )} +
+ ) + ))} + {provided.placeholder}
- toggleSocials("enabled")} - /> -
-
-
- - Google -
- toggleSocials("google")} - /> -
-
-
- - Apple -
- toggleSocials("apple")} - /> -
-
-
- - Facebook -
- toggleSocials("facebook")} - /> -
-
-
+ )} + +
@@ -224,6 +240,7 @@ export default function AuthPage() {
diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 38f041d21..2cc2cdaf9 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -26,6 +26,7 @@ import fingerprintIcon from "assets/fingerprint.svg"; import checkboxIcon from "assets/checkbox.svg"; import clockIcon from "assets/clock.svg"; import keyholeIcon from "assets/keyhole.svg"; +import React from "react"; interface AuthProps { onHandleAuthSuccess: () => Promise; @@ -37,9 +38,10 @@ interface AuthProps { facebookEnabled: boolean; googleEnabled: boolean; }; + configOrder: string[]; } -const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { +const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrder }) => { const { passkeyClient, authIframeClient } = useTurnkey(); const [error, setError] = useState(null); const [otpError, setOtpError] = useState(null); @@ -53,6 +55,16 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const [passkeySignupScreen, setPasskeySignupScreen] = useState(false); const otpInputRef = useRef(null); + const handleResendCode = async () => { + setOtpError(null); + if (step === "otpEmail") { + await handleOtpLogin("EMAIL", email, "OTP_TYPE_EMAIL"); + } else if (step === "otpPhone") { + await handleOtpLogin("PHONE_NUMBER", phone, "OTP_TYPE_SMS"); + } + setResendText("Code Sent ✓"); + }; + const formatPhoneNumber = (phone: string) => { const phoneNumber = parsePhoneNumberFromString(phone); return phoneNumber ? phoneNumber.formatInternational() : phone; @@ -64,15 +76,9 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { } }, [error]); - const isValidEmail = (email: string) => { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(email); - }; + const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); - const isValidPhone = (phone: string) => { - const usCanadaRegex = /^\+1\d{10}$/; - return usCanadaRegex.test(phone); - }; + const isValidPhone = (phone: string) => /^\+1\d{10}$/.test(phone); const handleGetOrCreateSuborg = async ( filterType: string, @@ -80,23 +86,16 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { additionalData = {} ) => { const getSuborgsResponse = await getSuborgs({ filterType, filterValue }); - let suborgId = getSuborgsResponse!.organizationIds[0]; + let suborgId = getSuborgsResponse?.organizationIds[0]; if (!suborgId) { - const createSuborgData: Record = { - ...additionalData, - }; - - if (filterType === "EMAIL") { - createSuborgData.email = filterValue; - } else if (filterType === "PHONE_NUMBER") { - createSuborgData.phoneNumber = filterValue; - } + const createSuborgData: Record = { ...additionalData }; + if (filterType === "EMAIL") createSuborgData.email = filterValue; + else if (filterType === "PHONE_NUMBER") createSuborgData.phoneNumber = filterValue; const createSuborgResponse = await createSuborg(createSuborgData); suborgId = createSuborgResponse?.subOrganizationId!; } - return suborgId; }; @@ -126,7 +125,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { if (encodedChallenge && attestation) { // Use the generated passkey to create a new suborg - const createSuborgResponse = await createSuborg({ + await createSuborg({ email, passkey: { authenticatorName: "First Passkey", @@ -134,14 +133,12 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { attestation, }, }); - - const suborgId = createSuborgResponse?.subOrganizationId; - console.log(suborgId); } else { setError("Failed to create user passkey."); } const sessionResponse = await passkeyClient?.createReadWriteSession({ targetPublicKey: authIframeClient?.iframePublicKey!, + organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID! }); if (sessionResponse?.credentialBundle) { await handleAuthSuccess(sessionResponse.credentialBundle); @@ -152,6 +149,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { const handleLoginWithPasskey = async () => { const sessionResponse = await passkeyClient?.createReadWriteSession({ targetPublicKey: authIframeClient?.iframePublicKey!, + organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID! }); if (sessionResponse?.credentialBundle) { await handleAuthSuccess(sessionResponse.credentialBundle); @@ -160,46 +158,11 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { } }; - // if (existingSuborgId) { - // // If a suborg exists, use it to create a read/write session without a new passkey - // const sessionResponse = await passkeyClient?.createReadWriteSession({ - // organizationId: existingSuborgId, - // targetPublicKey: authIframeClient?.iframePublicKey!, - // }); - - // if (sessionResponse?.credentialBundle) { - // await handleAuthSuccess(sessionResponse.credentialBundle); - // } else { - // setError("Failed to complete passkey login."); - // } - // } else { - // If no suborg exists, first create a user passkey - - // } else { - // setError("Failed to complete passkey login with new suborg."); - // } - // } else { - // setError("Failed to create suborg with passkey."); - // } - // } else { - // setError("Failed to create user passkey."); - // } - // } - - const handleOtpLogin = async ( - type: "EMAIL" | "PHONE_NUMBER", - value: string, - otpType: string - ) => { + const handleOtpLogin = async (type: "EMAIL" | "PHONE_NUMBER", value: string, otpType: string) => { const suborgId = await handleGetOrCreateSuborg(type, value); - console.log(suborgId); - const initAuthResponse = await initOtpAuth({ - suborgID: suborgId, - otpType, - contact: value, - }); + const initAuthResponse = await initOtpAuth({ suborgID: suborgId, otpType, contact: value }); setSuborgId(suborgId); - setOtpId(initAuthResponse!.otpId); + setOtpId(initAuthResponse?.otpId!); setStep(type === "EMAIL" ? "otpEmail" : "otpPhone"); }; @@ -211,39 +174,14 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { otpCode: otp, targetPublicKey: authIframeClient!.iframePublicKey!, }); - if (authResponse?.credentialBundle) { - await handleAuthSuccess(authResponse.credentialBundle); - } else { - setOtpError("Invalid code. Please try again"); - otpInputRef.current.resetOtp(); - } - }; - - const handleGoogleLogin = async (response: any) => { - const credential = response.credential; - setOauthLoading("Google"); - await handleOAuthLogin(credential, "Google OIDC"); - }; - - const handleAppleLogin = async (response: any) => { - const appleToken = response.authorization?.id_token; - setOauthLoading("Apple"); - if (appleToken) { - await handleOAuthLogin(appleToken, "Apple OIDC"); - } - }; - - const handleFacebookLogin = async (response: any) => { - const facebookToken = response?.id_token; - setOauthLoading("Facebook"); - if (facebookToken) { - await handleOAuthLogin(facebookToken, "Facebook OIDC"); - } else { - setError("Facebook login failed: No token returned."); - } + authResponse?.credentialBundle + ? await handleAuthSuccess(authResponse.credentialBundle) + : setOtpError("Invalid code. Please try again"); + otpInputRef.current.resetOtp(); }; const handleOAuthLogin = async (credential: string, providerName: string) => { + setOauthLoading(providerName); const suborgId = await handleGetOrCreateSuborg("OIDC_TOKEN", credential, { oauthProviders: [{ providerName, oidcToken: credential }], }); @@ -255,259 +193,228 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { await handleAuthSuccess(oauthResponse!.credentialBundle); }; - const handleResendCode = async () => { - setOtpError(null); - if (step === "otpEmail") { - await handleOtpLogin("EMAIL", email, "OTP_TYPE_EMAIL"); - } else if (step === "otpPhone") { - await handleOtpLogin("PHONE_NUMBER", phone, "OTP_TYPE_SMS"); + const renderSection = (section: string) => { + + switch (section) { + case "email": + return authConfig.emailEnabled && !otpId ? ( +
+
+ setEmail(e.target.value)} + /> +
+ +
+ ) : null; + + case "passkey": + return authConfig.passkeyEnabled && !otpId ? ( +
+ +
setPasskeySignupScreen(true)}> + I don't have a passkey +
+
+ ) : null; + + case "phone": + return authConfig.phoneEnabled && !otpId ? ( +
+
+ setPhone(value)} value={phone} /> +
+ +
+ ) : null; + + case "socials": + return (authConfig.googleEnabled || authConfig.appleEnabled || authConfig.facebookEnabled) ? ( + +
+ + {authConfig.googleEnabled && ( +
+
+ handleOAuthLogin(response.credential, "Google")} + /> +
+ )} + {authConfig.appleEnabled && ( +
+
+ handleOAuthLogin(response.authorization.id_token, "Apple")} + /> +
+ )} + {authConfig.facebookEnabled && ( +
+
+ handleOAuthLogin(response.id_token, "Facebook")} + /> +
+ )} +
+ ) : null; + + default: + return null; } - setResendText("Code Sent ✓"); }; return ( + <> + {passkeySignupScreen ? +
+
+ + +
+
+

Secure your account with a passkey

+
+ +
+
+ + Log in with Touch ID, Face ID, or a security key +
+
+ + More secure than a password +
+
+ + Takes seconds to set up and use +
+
+ +
:
- {oauthLoading != "" ? ( + {oauthLoading !== "" ? (
-

- Verifying with {oauthLoading} -

+

Verifying with {oauthLoading}

- - {oauthLoading === "Google" && ( - - )} - {oauthLoading === "Facebook" && ( - - )} - {oauthLoading === "Apple" && ( - - )} -
-
- Powered by - + + {oauthLoading === "Google" && } + {oauthLoading === "Facebook" && } + {oauthLoading === "Apple" && }
-
- ) : passkeySignupScreen ? ( -
-
- - -
-
-

Secure your account with a passkey

-
- -
-
- - Log in with Touch ID, Face ID, or a security key -
-
- - More secure than a password -
-
- - Takes seconds to set up and use -
-
- +
Powered by
) : (

{otpId ? "Enter verification code" : "Log in or sign up"}

- {authConfig.emailEnabled && !otpId && ( -
-
- setEmail(e.target.value)} - /> -
- -
- )} - - {authConfig.emailEnabled && authConfig.passkeyEnabled && !otpId && ( -
- OR -
- )} - {authConfig.passkeyEnabled && !otpId && ( -
- -
setPasskeySignupScreen(true)} - > - I don't have a passkey -
-
- )} - {!otpId && - (authConfig.passkeyEnabled || authConfig.emailEnabled) && - (authConfig.googleEnabled || - authConfig.appleEnabled || - authConfig.facebookEnabled || - authConfig.phoneEnabled) && ( -
- OR -
- )} - {authConfig.phoneEnabled && !otpId && ( -
-
- setPhone(value)} - value={phone} - /> -
- -
- )} - - {otpId && ( -
-
- {step === "otpEmail" ? ( - - ) : ( - + {!otpId && configOrder + .filter((section) => renderSection(section) !== null) + .map((section, index, visibleSections) => ( + + {renderSection(section)} + {index < visibleSections.length - 1 && ( +
+ OR +
)} -
- - - Enter the 6-digit code we sent to{" "} -
- {step === "otpEmail" ? email : formatPhoneNumber(phone)} -
-
- -
- )} -
{otpError ? otpError : " "}
-
- {!otpId && - (authConfig.googleEnabled || - authConfig.appleEnabled || - authConfig.facebookEnabled) && - authConfig.phoneEnabled && ( -
- OR -
- )} - - {!otpId && authConfig.googleEnabled && authIframeClient && ( -
-
- -
-
- )} + + + ))} - {!otpId && authConfig.appleEnabled && authIframeClient && ( -
-
- -
-
- )} - - {!otpId && authConfig.facebookEnabled && authIframeClient && ( -
-
- +{otpId && ( +
+
+
+ {step === "otpEmail" ? ( + + ) : ( + + )}
-
- )} - {!otpId ? ( -
- By continuing, you agree to our{" "} - - Terms of Service - {" "} - &{" "} - - Privacy Policy - - -
- ) : ( -
- - - {resendText} - + Enter the 6-digit code we sent to{" "} +
+ {step === "otpEmail" ? email : formatPhoneNumber(phone)} +
+
+
{otpError ? otpError : " "}
+
)} + {!otpId ? +
+ + By continuing, you agree to our{" "} + + Terms of Service + {" "} + &{" "} + + Privacy Policy + + +
: +
+ + + {resendText} + + +
+ } + +
(window.location.href = "https://www.turnkey.com/")} className={styles.poweredBy} @@ -515,10 +422,11 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig }) => { Secured by
+
)} -
+
} ); }; -export default Auth; +export default Auth; \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdf942eba..c00a4666e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -478,6 +478,9 @@ importers: '@emotion/styled': specifier: ^11.13.0 version: 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.14)(react@18.2.0) + '@hello-pangea/dnd': + specifier: ^17.0.0 + version: 17.0.0(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) '@mui/icons-material': specifier: ^6.1.5 version: 6.1.5(@mui/material@6.1.5)(@types/react@18.2.14)(react@18.2.0) @@ -6090,6 +6093,25 @@ packages: '@hapi/hoek': 9.3.0 dev: false + /@hello-pangea/dnd@17.0.0(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-LDDPOix/5N0j5QZxubiW9T0M0+1PR0rTDWeZF5pu1Tz91UQnuVK4qQ/EjY83Qm2QeX0eM8qDXANfDh3VVqtR4Q==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + '@babel/runtime': 7.26.0 + css-box-model: 1.2.1 + memoize-one: 6.0.0 + raf-schd: 4.0.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-redux: 9.1.2(@types/react@18.2.14)(react@18.2.0)(redux@5.0.1) + redux: 5.0.1 + use-memo-one: 1.1.3(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + dev: false + /@humanwhocodes/config-array@0.11.10: resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} engines: {node: '>=10.10.0'} @@ -7187,10 +7209,10 @@ packages: dependencies: '@babel/runtime': 7.26.0 '@emotion/cache': 11.13.1 - '@emotion/react': 11.13.3(@types/react@18.2.75)(react@18.2.0) + '@emotion/react': 11.13.3(@types/react@18.2.14)(react@18.2.0) '@emotion/serialize': 1.3.2 '@emotion/sheet': 1.4.0 - '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.75)(react@18.2.0) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.14)(react@18.2.0) csstype: 3.1.3 prop-types: 15.8.1 react: 18.2.0 @@ -13021,6 +13043,10 @@ packages: resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} dev: true + /@types/use-sync-external-store@0.0.3: + resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==} + dev: false + /@types/uuid@8.3.4: resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} @@ -15736,6 +15762,12 @@ packages: deprecated: This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in. dev: true + /css-box-model@1.2.1: + resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==} + dependencies: + tiny-invariant: 1.3.1 + dev: false + /css-declaration-sorter@6.4.1(postcss@8.4.38): resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} engines: {node: ^10 || ^12 || >=14} @@ -19972,6 +20004,10 @@ packages: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} dev: false + /memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + dev: false + /memory-level@1.0.0: resolution: {integrity: sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og==} engines: {node: '>=12'} @@ -22064,6 +22100,10 @@ packages: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} dev: false + /raf-schd@4.0.3: + resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} + dev: false + /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: @@ -22293,6 +22333,25 @@ packages: webrtc-adapter: 7.7.1 dev: false + /react-redux@9.1.2(@types/react@18.2.14)(react@18.2.0)(redux@5.0.1): + resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==} + peerDependencies: + '@types/react': ^18.2.25 + react: ^18.0 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + dependencies: + '@types/react': 18.2.14 + '@types/use-sync-external-store': 0.0.3 + react: 18.2.0 + redux: 5.0.1 + use-sync-external-store: 1.2.2(react@18.2.0) + dev: false + /react-refresh@0.14.0: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} engines: {node: '>=0.10.0'} @@ -22568,6 +22627,10 @@ packages: engines: {node: '>=6'} dev: true + /redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + dev: false + /reflect.getprototypeof@1.0.6: resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} engines: {node: '>= 0.4'} @@ -24605,6 +24668,14 @@ packages: tslib: 2.8.1 dev: false + /use-memo-one@1.1.3(react@18.2.0): + resolution: {integrity: sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /use-sidecar@1.1.2(@types/react@18.2.14)(react@18.2.0): resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} @@ -24637,6 +24708,14 @@ packages: tslib: 2.8.1 dev: false + /use-sync-external-store@1.2.2(react@18.2.0): + resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /usehooks-ts@3.1.0(react@18.2.0): resolution: {integrity: sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==} engines: {node: '>=16.15.0'} From dd75d5831620165e13b0424e4c1ecc6bc71e6f7b Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Mon, 18 Nov 2024 18:17:30 -0500 Subject: [PATCH 31/73] working signature verification - design still needed --- examples/react-components/package.json | 4 + examples/react-components/public/eth.svg | 4 + examples/react-components/public/solana.svg | 6 + .../src/app/dashboard/dashboard.css | 218 ++++++++++++++++++ .../src/app/dashboard/page.tsx | 217 +++++++++++++++++ examples/react-components/src/app/page.tsx | 8 +- examples/react-components/src/app/utils.ts | 69 ++++++ .../sdk-react/src/actions/createSuborg.ts | 11 +- .../sdk-react/src/components/auth/Apple.tsx | 1 + .../src/components/auth/Auth.module.css | 7 - pnpm-lock.yaml | 30 ++- 11 files changed, 553 insertions(+), 22 deletions(-) create mode 100644 examples/react-components/public/eth.svg create mode 100644 examples/react-components/public/solana.svg create mode 100644 examples/react-components/src/app/dashboard/dashboard.css create mode 100644 examples/react-components/src/app/dashboard/page.tsx create mode 100644 examples/react-components/src/app/utils.ts diff --git a/examples/react-components/package.json b/examples/react-components/package.json index d39ce6569..29ecf32b8 100644 --- a/examples/react-components/package.json +++ b/examples/react-components/package.json @@ -15,21 +15,25 @@ "@hello-pangea/dnd": "^17.0.0", "@mui/icons-material": "^6.1.5", "@mui/material": "^6.1.5", + "@solana/web3.js": "^1.95.4", "@turnkey/sdk-react": "workspace:*", "@turnkey/sdk-server": "workspace:*", "@types/node": "20.3.1", "@types/react": "18.2.14", "@types/react-dom": "18.2.6", "axios": "^1.7.4", + "buffer": "^6.0.3", "encoding": "^0.1.13", "eslint": "8.43.0", "eslint-config-next": "14.2.10", "esm": "^3.2.25", + "ethers": "^6.10.0", "install": "^0.13.0", "next": "^14.2.10", "npm": "^9.7.2", "react": "18.2.0", "react-dom": "18.2.0", + "tweetnacl": "^1.0.3", "typescript": "5.1.3" } } diff --git a/examples/react-components/public/eth.svg b/examples/react-components/public/eth.svg new file mode 100644 index 000000000..4142f57ad --- /dev/null +++ b/examples/react-components/public/eth.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/react-components/public/solana.svg b/examples/react-components/public/solana.svg new file mode 100644 index 000000000..fc0ae4936 --- /dev/null +++ b/examples/react-components/public/solana.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/react-components/src/app/dashboard/dashboard.css b/examples/react-components/src/app/dashboard/dashboard.css new file mode 100644 index 000000000..0b6429ddc --- /dev/null +++ b/examples/react-components/src/app/dashboard/dashboard.css @@ -0,0 +1,218 @@ +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("../../../public/fonts/inter/Inter-Regular.woff2?v=3.19") + format("woff2"); + } + + @font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url("../../../public/fonts/inter/Inter-SemiBold.woff2?v=3.19") + format("woff2"); + } + + .main { + font-family: Inter; + display: flex; + justify-content: center; + align-items: center; + padding: 6rem; + gap: 60px; + position: relative; + } + + .authConfigContainer { + position: absolute; + left: 0; + display: flex; + justify-content: flex-start; + } + + .authConfigCard { + background: var(--Greyscale-20, #f5f7fb); + padding: 8px 24px; + border-radius: 8px; + width: 542px; + height: 642px; + border: 1px solid var(--Greyscale-100, #ebedf2); + font-family: Arial, sans-serif; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + display: flex; + flex-direction: column; + justify-content: flex-start; + } + .configTitle { + font-size: 32px; + font-weight: 700; + line-height: 36px; + letter-spacing: -0.01em; + text-align: left; + margin-bottom: 16px; + margin-top: 16px; + } + + .accountContainer { + display: flex; + flex-direction: column; + gap: 12px; + border-bottom: 1px solid var(--Greyscale-200, #ebedf2); + } + + .labelContainer { + display: flex; + align-items: center; + gap: 8px; + } + + .accountRow { + background-color: #ffffff; + border-radius: 8px; + align-items: center; + display: flex; + justify-content: space-between; + padding: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + .accountAddress { + flex: 1; + margin-left: 8px; + } + + .radioButton { + margin-left: auto; + } + + .toggleSocialRow { + background-color: #ffffff; + border-radius: 8px; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + } + + .toggleSocialIndividualRow { + background-color: var(--Greyscale-20, #f5f7fb); + display: flex; + margin-left: 16px; + padding-left: 16px; + padding-top: 4px; + padding-bottom: 4px; + justify-content: space-between; + align-items: center; + } + + .copyConfigButton { + display: flex; + align-items: center; + cursor: pointer; + color: #666; + font-size: 14px; + margin-top: auto; + padding: 8px 16px; + } + .copyConfigText { + padding-left: 4px; + } + + .authComponent { + display: flex; + justify-content: center; + align-items: center; + flex: 1; + } + + .success { + justify-items: center; + } + + .iconSmall { + width: 18px; + height: 18px; + } + + .socialContainer { + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + padding: 8px 16px; + padding-bottom: 16px; + background-color: #ffffff; + } + + .signMessage { + margin-bottom: 24px; + margin-top: 24px; + } + .exportImportGroup { + margin-top: 24px; + display: flex; + gap: 12px; + } + + button { + padding: 10px 16px 10px 16px; + gap: 8px; + color: #ffffff; + width: 100%; + font-size: 1rem; + background: var(--Greyscale-900, #2b2f33); + border: 1px solid var(--Greyscale-800, #3f464b); + border-radius: 8px; + cursor: pointer; + } + + button:disabled { + color: var(--Greyscale-700, #a2a7ae); + background: #ffffff; + border-color: var(--Greyscale-400, #a2a7ae); + cursor: not-allowed; + } + + + .authFooter { + display: flex; + justify-content: space-between; + align-items: flex-end; + padding-top: 16px; + border-top: 1px solid var(--Greyscale-200, #ebedf2); + margin-top: auto; + } + + .authFooterLeft, + .authFooterRight { + display: flex; + flex-direction: column; + align-items: center; + width: 50%; + margin-bottom: 12px; + } + + .authFooterLeft { + justify-content: flex-start; + text-align: center; + } + + .authFooterRight { + justify-content: flex-start; + text-align: center; + } + + .authFooterSeparatorVertical { + border-left: 1px solid var(--Greyscale-200, #ebedf2); + height: 100%; + margin: 0 16px; + } + + .authFooterButton { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + cursor: pointer; + } \ No newline at end of file diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx new file mode 100644 index 000000000..31b399841 --- /dev/null +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -0,0 +1,217 @@ +"use client"; + +import { Auth, useTurnkey } from "@turnkey/sdk-react"; +import { useEffect, useState } from "react"; +import "./dashboard.css"; +import { + Typography, + Radio, + RadioGroup, + FormControlLabel, + Button, + Modal, + Box, + TextField, +} from "@mui/material"; +import LogoutIcon from '@mui/icons-material/Logout'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import { verifyEthSignature, verifySolSignatureWithAddress } from "../utils"; +import { keccak256, toUtf8Bytes } from "ethers"; + +export default function Dashboard() { + const { authIframeClient } = useTurnkey(); + const [orgData, setOrgData] = useState(); + const [loading, setLoading] = useState(true); + const [accounts, setAccounts] = useState([]); + const [selectedAccount, setSelectedAccount] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + const [messageToSign, setMessageToSign] = useState(""); + const [suborgId, setSuborgId] = useState("") + useEffect(() => { + const fetchWhoami = async () => { + try { + if (authIframeClient) { + const whoamiResponse = await authIframeClient.getWhoami({ + organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, + }); + setSuborgId(whoamiResponse.organizationId!) + const wallets = await authIframeClient.getWallets({ + organizationId: whoamiResponse.organizationId, + }); + const accountsResponse = await authIframeClient.getWalletAccounts({ + organizationId: whoamiResponse.organizationId, + walletId: wallets.wallets[0].walletId, + }); + setAccounts(accountsResponse.accounts); + } + } catch (error) { + console.error("Error fetching Whoami:", error); + } finally { + setLoading(false); + } + }; + + fetchWhoami(); + }, [authIframeClient]); + + const handleAccountSelect = (event: React.ChangeEvent) => { + setSelectedAccount(event.target.value); // Save the full address (untruncated) + }; + + const handleSignMessageClick = () => { + if (!selectedAccount) { + alert("Please select an account first!"); + return; + } + setIsModalOpen(true); // Open the modal + }; + + const handleModalClose = () => { + setIsModalOpen(false); // Close the modal + }; + + const handleSign = async () => { + console.log("Message to sign:", messageToSign); + console.log("Using address:", selectedAccount); + const addressType = selectedAccount?.startsWith("0x") ? "ETH" : "SOL" + + const hashedMessage = + addressType === "ETH" + ? keccak256(toUtf8Bytes(messageToSign)) // Ethereum requires keccak256 hash + : Buffer.from(messageToSign, "utf8").toString("hex"); // Solana doesn't require hashing + + + const resp = await authIframeClient?.signRawPayload({organizationId: suborgId, signWith: selectedAccount!, payload: hashedMessage, encoding: "PAYLOAD_ENCODING_HEXADECIMAL", hashFunction: addressType === "ETH" ? "HASH_FUNCTION_NO_OP" : "HASH_FUNCTION_NOT_APPLICABLE"}) + if (addressType === "ETH"){ + console.log(verifyEthSignature(messageToSign, resp?.r!, resp?.s!, resp?.v!)) + } + else { + console.log(verifySolSignatureWithAddress(messageToSign, resp?.r!, resp?.s!, selectedAccount!)) + } + setIsModalOpen(false); // Close the modal after signing + }; + + + return ( +
+
+ + Login Methods + +
+
+
+

Wallets

+ +
+ {accounts.map((account: any, index: number) => ( +
+ {account.addressFormat === "ADDRESS_FORMAT_ETHEREUM" && ( + + )} + {account.addressFormat === "ADDRESS_FORMAT_SOLANA" && ( + + )} + {`${account.address.slice( + 0, + 5 + )}...${account.address.slice(-5)}`} + + } + label="" + className="radioButton" + /> +
+ ))} + +
+
+ +
+ + +
+
+
+
+ + Logout +
+
+
+
+
+ + Delete Account +
+
+
+
+
+ + {/* Modal */} + + + + Sign a Message + + setMessageToSign(e.target.value)} + /> + + + +
+ ); +} diff --git a/examples/react-components/src/app/page.tsx b/examples/react-components/src/app/page.tsx index 455ef4e7d..15e7256c4 100644 --- a/examples/react-components/src/app/page.tsx +++ b/examples/react-components/src/app/page.tsx @@ -8,6 +8,7 @@ import ContentCopyIcon from "@mui/icons-material/ContentCopy"; import CustomSwitch from "./Switch"; import { DragDropContext, Droppable, Draggable, DropResult } from "@hello-pangea/dnd"; import "./index.css"; +import { useRouter } from "next/navigation"; // Define types for config and socials interface SocialConfig { @@ -27,12 +28,9 @@ interface Config { export default function AuthPage() { const { authIframeClient } = useTurnkey(); const [orgData, setOrgData] = useState(); - + const router = useRouter(); const handleAuthSuccess = async () => { - const whoamiResponse = await authIframeClient!.getWhoami({ - organizationId: "51fb75bf-c044-4f9f-903b-4fba6bfedab9", - }); - setOrgData(whoamiResponse as any); + router.push("/dashboard"); }; const [configOrder, setConfigOrder] = useState(["email", "phone", "passkey", "socials"]); diff --git a/examples/react-components/src/app/utils.ts b/examples/react-components/src/app/utils.ts new file mode 100644 index 000000000..7eed30a51 --- /dev/null +++ b/examples/react-components/src/app/utils.ts @@ -0,0 +1,69 @@ + +import { PublicKey, PublicKeyInitData } from "@solana/web3.js"; +import nacl from "tweetnacl"; +import { Buffer } from "buffer"; + +import { hashMessage, keccak256, recoverAddress, toUtf8Bytes } from "ethers"; + +/** + * Verifies an Ethereum signature and returns the address it was signed with. + * @param {string} message - The original message that was signed. + * @param {string} r - The r value of the signature. + * @param {string} s - The s value of the signature. + * @param {string} v - The v value of the signature. + * @returns {string} - The recovered Ethereum address. + */ +export function verifyEthSignature( + message: string, + r: string, + s: string, + v: string + ): string { + try { + // Construct the full signature + const signature = `0x${r}${s}${v === "00" ? "1b" : "1c"}`; // 1b/1c corresponds to v for Ethereum + const hashedMessage = keccak256(toUtf8Bytes(message)) + + // Recover the address from the signature + return recoverAddress(hashedMessage, signature); + } catch (error) { + console.error("Ethereum signature verification failed:", error); + return ""; + } + } + +/** + * Verifies a Solana signature using the address (treated as the public key). + * @param {string} message - The original message that was signed. + * @param {string} r - The r value of the signature. + * @param {string} s - The s value of the signature. + * @param {string} address - The Solana address of the signer. + * @returns {boolean} - True if the signature is valid, false otherwise. + */ +export function verifySolSignatureWithAddress( + message: string, + r: string, + s: string, + address: string + ) { + try { + // Combine r and s as the full signature (64 bytes for Solana) + const signature = Buffer.from(r + s, "hex"); + + // Convert the message to a buffer + const messageBuffer = Buffer.from(message); + + // Treat the address as the public key (if valid) + const pubKey = new PublicKey(address); + + // Verify the signature + return nacl.sign.detached.verify( + messageBuffer, + signature, + pubKey.toBytes() + ); + } catch (error) { + console.error("Solana signature verification failed:", error); + return false; + } + } \ No newline at end of file diff --git a/packages/sdk-react/src/actions/createSuborg.ts b/packages/sdk-react/src/actions/createSuborg.ts index b6e1b0e79..2644b64d7 100644 --- a/packages/sdk-react/src/actions/createSuborg.ts +++ b/packages/sdk-react/src/actions/createSuborg.ts @@ -1,5 +1,6 @@ "use server"; +import { DEFAULT_ETHEREUM_ACCOUNTS, DEFAULT_SOLANA_ACCOUNTS } from "@turnkey/sdk-browser"; import { Turnkey } from "@turnkey/sdk-server"; type CreateSuborgRequest = { @@ -43,12 +44,20 @@ export async function createSuborg( { userName: request.email ?? "", userEmail: request.email ?? "", - userPhoneNumber: request.phoneNumber ?? "", + ...(request.phoneNumber ? { userPhoneNumber: request.phoneNumber } : {}), apiKeys: [], authenticators: request.passkey ? [request.passkey] : [], oauthProviders: request.oauthProviders ?? [], }, ], + wallet: { + walletName:String(Date.now()) , + accounts: [ + ...DEFAULT_ETHEREUM_ACCOUNTS, + ...DEFAULT_SOLANA_ACCOUNTS, + ], + }, + }); const { subOrganizationId } = suborgResponse; diff --git a/packages/sdk-react/src/components/auth/Apple.tsx b/packages/sdk-react/src/components/auth/Apple.tsx index 5d29061b0..e06ee2c0d 100644 --- a/packages/sdk-react/src/components/auth/Apple.tsx +++ b/packages/sdk-react/src/components/auth/Apple.tsx @@ -44,6 +44,7 @@ const AppleAuthButton: React.FC = ({ } return ( + =11'} dependencies: '@coral-xyz/borsh': 0.26.0(@solana/web3.js@1.95.4) - '@solana/web3.js': 1.95.4 + '@solana/web3.js': 1.95.4(encoding@0.1.13) base64-js: 1.5.1 bn.js: 5.2.1 bs58: 4.0.1 @@ -11056,7 +11068,7 @@ packages: engines: {node: '>= 10'} dependencies: '@solana/buffer-layout': 4.0.1 - '@solana/web3.js': 1.95.4 + '@solana/web3.js': 1.95.4(encoding@0.1.13) bigint-buffer: 1.1.5 bignumber.js: 9.1.2 transitivePeerDependencies: @@ -11976,7 +11988,7 @@ packages: - encoding - utf-8-validate - /@solana/web3.js@1.95.4: + /@solana/web3.js@1.95.4(encoding@0.1.13): resolution: {integrity: sha512-sdewnNEA42ZSMxqkzdwEWi6fDgzwtJHaQa5ndUGEJYtoOnM6X5cvPmjoTUp7/k7bRrVAxfBgDnvQQHD6yhlLYw==} dependencies: '@babel/runtime': 7.26.0 @@ -12383,7 +12395,7 @@ packages: '@babel/runtime': 7.x dependencies: '@babel/runtime': 7.26.0 - '@solana/web3.js': 1.95.4 + '@solana/web3.js': 1.95.4(encoding@0.1.13) '@toruslabs/base-controllers': 2.9.0(@babel/runtime@7.26.0) '@toruslabs/http-helpers': 3.4.0(@babel/runtime@7.26.0) '@toruslabs/openlogin-jrpc': 3.2.0(@babel/runtime@7.26.0) @@ -12420,7 +12432,7 @@ packages: peerDependencies: tslib: ^2.6.2 dependencies: - '@solana/web3.js': 1.95.4 + '@solana/web3.js': 1.95.4(encoding@0.1.13) '@trezor/type-utils': 1.1.0 '@trezor/utxo-lib': 2.1.0(tslib@2.8.1) socks-proxy-agent: 6.1.1 @@ -12438,7 +12450,7 @@ packages: tslib: ^2.6.2 dependencies: '@mobily/ts-belt': 3.13.1 - '@solana/web3.js': 1.95.4 + '@solana/web3.js': 1.95.4(encoding@0.1.13) '@trezor/env-utils': 1.1.0(react-native@0.74.0)(tslib@2.8.1) '@trezor/utils': 9.1.0(tslib@2.8.1) tslib: 2.8.1 @@ -12457,7 +12469,7 @@ packages: tslib: ^2.6.2 dependencies: '@solana/buffer-layout': 4.0.1 - '@solana/web3.js': 1.95.4 + '@solana/web3.js': 1.95.4(encoding@0.1.13) '@trezor/blockchain-link-types': 1.1.0(tslib@2.8.1) '@trezor/blockchain-link-utils': 1.1.0(react-native@0.74.0)(tslib@2.8.1) '@trezor/utils': 9.1.0(tslib@2.8.1) From e3d9c6b7caa33210add821b58c772fc349729b32 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Tue, 19 Nov 2024 16:08:10 -0500 Subject: [PATCH 32/73] tonnes of updates --- .../src/app/dashboard/dashboard.css | 8 + .../src/app/dashboard/page.tsx | 223 +++++++++++++----- examples/react-components/src/app/page.tsx | 28 +-- examples/react-components/src/app/utils.ts | 12 +- .../sdk-react/src/components/auth/Apple.tsx | 2 +- .../src/components/auth/Auth.module.css | 48 +--- .../sdk-react/src/components/auth/Auth.tsx | 61 +++-- .../src/components/auth/Facebook.tsx | 2 +- .../sdk-react/src/components/auth/Google.tsx | 2 +- .../src/components/auth/PhoneInput.tsx | 12 + .../src/components/auth/Socials.module.css | 2 +- 11 files changed, 257 insertions(+), 143 deletions(-) diff --git a/examples/react-components/src/app/dashboard/dashboard.css b/examples/react-components/src/app/dashboard/dashboard.css index 0b6429ddc..0be8c87cc 100644 --- a/examples/react-components/src/app/dashboard/dashboard.css +++ b/examples/react-components/src/app/dashboard/dashboard.css @@ -56,6 +56,14 @@ margin-top: 16px; } + .modalTitle { + font-size: 18px; + line-height: 24px; + font-weight: 600; + letter-spacing: -0.01em; + text-align: left; + } + .accountContainer { display: flex; flex-direction: column; diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index 31b399841..2472de73a 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -1,6 +1,6 @@ -"use client"; +"use client" -import { Auth, useTurnkey } from "@turnkey/sdk-react"; +import { useTurnkey } from "@turnkey/sdk-react"; import { useEffect, useState } from "react"; import "./dashboard.css"; import { @@ -13,20 +13,24 @@ import { Box, TextField, } from "@mui/material"; -import LogoutIcon from '@mui/icons-material/Logout'; -import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; +import LogoutIcon from "@mui/icons-material/Logout"; +import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import { verifyEthSignature, verifySolSignatureWithAddress } from "../utils"; import { keccak256, toUtf8Bytes } from "ethers"; export default function Dashboard() { const { authIframeClient } = useTurnkey(); - const [orgData, setOrgData] = useState(); const [loading, setLoading] = useState(true); const [accounts, setAccounts] = useState([]); const [selectedAccount, setSelectedAccount] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const [messageToSign, setMessageToSign] = useState(""); - const [suborgId, setSuborgId] = useState("") + const [signature, setSignature] = useState(null); + const [verificationResult, setVerificationResult] = useState( + null + ); + + useEffect(() => { const fetchWhoami = async () => { try { @@ -34,7 +38,6 @@ export default function Dashboard() { const whoamiResponse = await authIframeClient.getWhoami({ organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, }); - setSuborgId(whoamiResponse.organizationId!) const wallets = await authIframeClient.getWallets({ organizationId: whoamiResponse.organizationId, }); @@ -43,6 +46,12 @@ export default function Dashboard() { walletId: wallets.wallets[0].walletId, }); setAccounts(accountsResponse.accounts); + if (accountsResponse.accounts.length > 0) { + setSelectedAccount(accountsResponse.accounts[0].address); + } + if (authIframeClient && authIframeClient.config) { + authIframeClient.config.organizationId = whoamiResponse.organizationId!; + } } } catch (error) { console.error("Error fetching Whoami:", error); @@ -68,29 +77,61 @@ export default function Dashboard() { const handleModalClose = () => { setIsModalOpen(false); // Close the modal + setMessageToSign(""); + setSignature(null); + setVerificationResult(null); }; const handleSign = async () => { - console.log("Message to sign:", messageToSign); - console.log("Using address:", selectedAccount); - const addressType = selectedAccount?.startsWith("0x") ? "ETH" : "SOL" + try { + const addressType = selectedAccount?.startsWith("0x") ? "ETH" : "SOL"; + const hashedMessage = + addressType === "ETH" + ? keccak256(toUtf8Bytes(messageToSign)) // Ethereum requires keccak256 hash + : Buffer.from(messageToSign, "utf8").toString("hex"); // Solana doesn't require hashing - const hashedMessage = - addressType === "ETH" - ? keccak256(toUtf8Bytes(messageToSign)) // Ethereum requires keccak256 hash - : Buffer.from(messageToSign, "utf8").toString("hex"); // Solana doesn't require hashing + const resp = await authIframeClient?.signRawPayload({ + signWith: selectedAccount!, + payload: hashedMessage, + encoding: "PAYLOAD_ENCODING_HEXADECIMAL", + hashFunction: + addressType === "ETH" + ? "HASH_FUNCTION_NO_OP" + : "HASH_FUNCTION_NOT_APPLICABLE", + }); - - const resp = await authIframeClient?.signRawPayload({organizationId: suborgId, signWith: selectedAccount!, payload: hashedMessage, encoding: "PAYLOAD_ENCODING_HEXADECIMAL", hashFunction: addressType === "ETH" ? "HASH_FUNCTION_NO_OP" : "HASH_FUNCTION_NOT_APPLICABLE"}) - if (addressType === "ETH"){ - console.log(verifyEthSignature(messageToSign, resp?.r!, resp?.s!, resp?.v!)) - } - else { - console.log(verifySolSignatureWithAddress(messageToSign, resp?.r!, resp?.s!, selectedAccount!)) + setSignature({ r: resp?.r, s: resp?.s, v: resp?.v }); + } catch (error) { + console.error("Error signing message:", error); } - setIsModalOpen(false); // Close the modal after signing }; + const handleVerify = () => { + if (!signature) return; + + const addressType = selectedAccount?.startsWith("0x") ? "ETH" : "SOL"; + const verificationPassed = + addressType === "ETH" + ? verifyEthSignature( + messageToSign, + signature.r, + signature.s, + signature.v, + selectedAccount! + ) + : verifySolSignatureWithAddress( + messageToSign, + signature.r, + signature.s, + selectedAccount! + ); + + setVerificationResult( + verificationPassed + ? `Signed with: ${selectedAccount}` + : "Verification failed" + ); + }; return (
@@ -114,7 +155,14 @@ export default function Dashboard() { height: "32px", marginLeft: "8px", marginRight: "8px", + cursor: "pointer", }} + onClick={() => + window.open( + `https://etherscan.io/address/${account.address}`, + "_blank" + ) + } /> )} {account.addressFormat === "ADDRESS_FORMAT_SOLANA" && ( @@ -125,7 +173,14 @@ export default function Dashboard() { height: "32px", marginLeft: "8px", marginRight: "8px", + cursor: "pointer", }} + onClick={() => + window.open( + `https://solscan.io/account/${account.address}`, + "_blank" + ) + } /> )} {`${account.address.slice( @@ -156,62 +211,112 @@ export default function Dashboard() {
- - + +
- Logout + Log out
- Delete Account + Delete account
- - {/* Modal */} - + + Sign a Message + + + Signing this message will not cost you any fees. + + setMessageToSign(e.target.value)} + sx={{ + bgcolor: "#ffffff", + "& .MuiOutlinedInput-root": { + height: "80px", // Set the height of the input field + alignItems: "flex-start", // Align text to the top + "& fieldset": { + borderColor: "#D0D5DD", // Default border color + }, + "&:hover fieldset": { + borderColor: "#8A929E", // Hover border color + }, + "&.Mui-focused fieldset": { + borderColor: "#D0D5DD", // Focus highlight color + border: "1px solid" + }, + }, + }} + /> + + {signature && ( + <> + {`Signature: ${JSON.stringify(signature)}`} + - - + Verify + + + )} + {verificationResult && ( + + {verificationResult} + + )} + + +
); } diff --git a/examples/react-components/src/app/page.tsx b/examples/react-components/src/app/page.tsx index 15e7256c4..97dc8c259 100644 --- a/examples/react-components/src/app/page.tsx +++ b/examples/react-components/src/app/page.tsx @@ -26,22 +26,20 @@ interface Config { } export default function AuthPage() { - const { authIframeClient } = useTurnkey(); - const [orgData, setOrgData] = useState(); const router = useRouter(); const handleAuthSuccess = async () => { router.push("/dashboard"); }; - const [configOrder, setConfigOrder] = useState(["email", "phone", "passkey", "socials"]); + const [configOrder, setConfigOrder] = useState(["socials", "email", "phone", "passkey"]); const [config, setConfig] = useState({ email: true, phone: true, passkey: true, socials: { - enabled: false, - google: false, + enabled: true, + google: true, apple: false, facebook: false, }, @@ -119,7 +117,6 @@ export default function AuthPage() { return (
- {!orgData && (
Authentication config @@ -217,24 +214,6 @@ export default function AuthPage() {
- )} - {orgData ? ( -
- YOU ARE AUTHENTICATED ON TURNKEY! -
- Organization Id: {orgData.organizationId} -
-
- User Id: {orgData.userId} -
-
- Username: {orgData.username} -
-
- Organization Name: {orgData.organizationName} -
-
- ) : (
- )}
); } diff --git a/examples/react-components/src/app/utils.ts b/examples/react-components/src/app/utils.ts index 7eed30a51..61dde00a7 100644 --- a/examples/react-components/src/app/utils.ts +++ b/examples/react-components/src/app/utils.ts @@ -11,24 +11,26 @@ import { hashMessage, keccak256, recoverAddress, toUtf8Bytes } from "ethers"; * @param {string} r - The r value of the signature. * @param {string} s - The s value of the signature. * @param {string} v - The v value of the signature. - * @returns {string} - The recovered Ethereum address. + * @param {string} address - The Ethereum address of the signer. + * @returns {boolean} - The recovered Ethereum address. */ export function verifyEthSignature( message: string, r: string, s: string, - v: string - ): string { + v: string, + address: string + ): boolean { try { // Construct the full signature const signature = `0x${r}${s}${v === "00" ? "1b" : "1c"}`; // 1b/1c corresponds to v for Ethereum const hashedMessage = keccak256(toUtf8Bytes(message)) // Recover the address from the signature - return recoverAddress(hashedMessage, signature); + return address == recoverAddress(hashedMessage, signature); } catch (error) { console.error("Ethereum signature verification failed:", error); - return ""; + return false } } diff --git a/packages/sdk-react/src/components/auth/Apple.tsx b/packages/sdk-react/src/components/auth/Apple.tsx index e06ee2c0d..7983c2052 100644 --- a/packages/sdk-react/src/components/auth/Apple.tsx +++ b/packages/sdk-react/src/components/auth/Apple.tsx @@ -54,7 +54,7 @@ const AppleAuthButton: React.FC = ({ render={({ onClick }) => (
- Continue with Apple + Apple
)} callback={(response) => { diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index 2bf411476..cfcc96315 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -82,57 +82,35 @@ margin-bottom: 12px; } -.inputGroup input { - width: 100%; - padding: 12px; - font-size: 1rem; - border: 1px solid var(--Greyscale-200, #d8dbe3); - border-radius: 4px; - box-sizing: border-box; - &::placeholder { - color: rgba(0, 0, 0, 0.4); - } -} - button { padding: 10px 16px 10px 16px; gap: 8px; - color: #ffffff; + color: var(--Greyscale-900, #2b2f33); width: 100%; font-size: 1rem; - background: var(--Greyscale-900, #2b2f33); - border: 1px solid var(--Greyscale-800, #3f464b); + background: #ffffff; + border: 1px solid var(--Greyscale-400, #a2a7ae); border-radius: 8px; cursor: pointer; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.2s ease; } -button:disabled { - color: var(--Greyscale-700, #a2a7ae); - background: #ffffff; - border-color: var(--Greyscale-400, #a2a7ae); - cursor: not-allowed; +button:hover { + background-color: #F4E8FF; } -.passkeyButton { - margin-top: 12px; - padding: 10px 16px 10px 16px; - gap: 8px; - color: var(--Greyscale-600, #6c727e); - - width: 100%; - font-size: 1rem; - background: var(--Greyscale-1, #ffffff); - border: 1px solid var(--Greyscale-400, #a2a7ae); - border-radius: 8px; - cursor: pointer; -} -.passkeyButton:disabled { - color: var(--Greyscale-500, #a2a7ae); +button:disabled { + color: var(--Greyscale-700, #a2a7ae); background: #ffffff; border-color: var(--Greyscale-400, #a2a7ae); cursor: not-allowed; } + /* Separator */ .separator { text-align: center; diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 2cc2cdaf9..1a0f10a64 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -13,7 +13,7 @@ import OtpInput from "./otp"; import GoogleAuthButton from "./Google"; import AppleAuthButton from "./Apple"; import FacebookAuthButton from "./Facebook"; -import { CircularProgress } from "@mui/material"; +import { CircularProgress, TextField } from "@mui/material"; import { parsePhoneNumberFromString } from "libphonenumber-js"; import turnkeyIcon from "assets/turnkey.svg"; import googleIcon from "assets/google.svg"; @@ -51,8 +51,9 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde const [step, setStep] = useState("auth"); const [oauthLoading, setOauthLoading] = useState(""); const [suborgId, setSuborgId] = useState(""); - const [resendText, setResendText] = useState("Re-send Code"); + const [resendText, setResendText] = useState("Resend code"); const [passkeySignupScreen, setPasskeySignupScreen] = useState(false); + const [loading, setLoading] = useState(true); const otpInputRef = useRef(null); const handleResendCode = async () => { @@ -62,7 +63,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde } else if (step === "otpPhone") { await handleOtpLogin("PHONE_NUMBER", phone, "OTP_TYPE_SMS"); } - setResendText("Code Sent ✓"); + setResendText("Code sent ✓"); }; const formatPhoneNumber = (phone: string) => { @@ -76,6 +77,16 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde } }, [error]); + useEffect(() => { + if (authIframeClient) { + setLoading(false); + } + }, [authIframeClient]); + + if (loading) { + return <> + } + const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); const isValidPhone = (phone: string) => /^\+1\d{10}$/.test(phone); @@ -200,19 +211,39 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde return authConfig.emailEnabled && !otpId ? (
- setEmail(e.target.value)} - /> + setEmail(e.target.value)} + fullWidth + sx={{ + "& .MuiOutlinedInput-root": { + "& fieldset": { + borderColor: "#D0D5DD", + }, + "&:hover fieldset": { + borderColor: "#8A929E", + }, + "&.Mui-focused fieldset": { + borderColor: "#D0D5DD", + border: "1px solid" + }, + }, + "& .MuiInputBase-input": { + padding: "12px" + }, + backgroundColor: "white", + }} + variant="outlined" +/>
) : null; @@ -220,9 +251,9 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde case "passkey": return authConfig.passkeyEnabled && !otpId ? (
- +
setPasskeySignupScreen(true)}> - I don't have a passkey + Sign up with passkey
) : null; @@ -238,7 +269,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde onClick={() => handleOtpLogin("PHONE_NUMBER", phone, "OTP_TYPE_SMS")} disabled={!isValidPhone(phone)} > - Continue with phone + Continue
) : null; @@ -400,11 +431,11 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde diff --git a/packages/sdk-react/src/components/auth/Facebook.tsx b/packages/sdk-react/src/components/auth/Facebook.tsx index 06b7a82c3..df37da13b 100644 --- a/packages/sdk-react/src/components/auth/Facebook.tsx +++ b/packages/sdk-react/src/components/auth/Facebook.tsx @@ -94,7 +94,7 @@ const FacebookAuthButton: React.FC = ({ return (
- Continue with Facebook + Facebook
); }; diff --git a/packages/sdk-react/src/components/auth/Google.tsx b/packages/sdk-react/src/components/auth/Google.tsx index aaa41c3e3..d31e1d8fc 100644 --- a/packages/sdk-react/src/components/auth/Google.tsx +++ b/packages/sdk-react/src/components/auth/Google.tsx @@ -34,7 +34,7 @@ const GoogleAuthButton: React.FC = ({
- Continue with Google + Google
); diff --git a/packages/sdk-react/src/components/auth/PhoneInput.tsx b/packages/sdk-react/src/components/auth/PhoneInput.tsx index a3932522a..dabb0f76e 100644 --- a/packages/sdk-react/src/components/auth/PhoneInput.tsx +++ b/packages/sdk-react/src/components/auth/PhoneInput.tsx @@ -54,6 +54,18 @@ export const MuiPhone: React.FC = ({ inputRef={inputRef} fullWidth sx={{ + "& .MuiOutlinedInput-root": { + "& fieldset": { + borderColor: "#D0D5DD", + }, + "&:hover fieldset": { + borderColor: "#8A929E", + }, + "&.Mui-focused fieldset": { + borderColor: "#D0D5DD", + border: "1px solid" + }, + }, "& .MuiInputBase-input": { padding: "10px 4px", }, diff --git a/packages/sdk-react/src/components/auth/Socials.module.css b/packages/sdk-react/src/components/auth/Socials.module.css index 72397d4c3..d9310014f 100644 --- a/packages/sdk-react/src/components/auth/Socials.module.css +++ b/packages/sdk-react/src/components/auth/Socials.module.css @@ -16,7 +16,7 @@ } .socialButton:hover { - background-color: #f0f8ff; + background-color: #F4E8FF; } .iconSmall { From 28a52f3b423a7e4839e19769920478a8d9d8d77f Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Tue, 19 Nov 2024 19:47:52 -0500 Subject: [PATCH 33/73] stackable social button section --- .../src/app/dashboard/dashboard.css | 4 + .../src/app/dashboard/page.tsx | 10 +- examples/react-components/src/app/page.tsx | 2 +- .../sdk-react/src/components/auth/Apple.tsx | 16 ++- .../src/components/auth/Auth.module.css | 19 ++- .../sdk-react/src/components/auth/Auth.tsx | 114 ++++++++++++------ .../src/components/auth/Facebook.tsx | 17 +-- .../sdk-react/src/components/auth/Google.tsx | 15 ++- .../src/components/auth/Socials.module.css | 28 +++++ 9 files changed, 162 insertions(+), 63 deletions(-) diff --git a/examples/react-components/src/app/dashboard/dashboard.css b/examples/react-components/src/app/dashboard/dashboard.css index 0be8c87cc..45d0e1404 100644 --- a/examples/react-components/src/app/dashboard/dashboard.css +++ b/examples/react-components/src/app/dashboard/dashboard.css @@ -182,6 +182,10 @@ cursor: not-allowed; } +button:hover { + background-color: var(--Greyscale-700, #4a4f55); + } + .authFooter { display: flex; diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index 2472de73a..733597d1a 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -264,16 +264,16 @@ export default function Dashboard() { sx={{ bgcolor: "#ffffff", "& .MuiOutlinedInput-root": { - height: "80px", // Set the height of the input field - alignItems: "flex-start", // Align text to the top + height: "80px", + alignItems: "flex-start", "& fieldset": { - borderColor: "#D0D5DD", // Default border color + borderColor: "#D0D5DD", }, "&:hover fieldset": { - borderColor: "#8A929E", // Hover border color + borderColor: "#8A929E", }, "&.Mui-focused fieldset": { - borderColor: "#D0D5DD", // Focus highlight color + borderColor: "#D0D5DD", border: "1px solid" }, }, diff --git a/examples/react-components/src/app/page.tsx b/examples/react-components/src/app/page.tsx index 97dc8c259..32fcf4cc2 100644 --- a/examples/react-components/src/app/page.tsx +++ b/examples/react-components/src/app/page.tsx @@ -35,7 +35,7 @@ export default function AuthPage() { const [config, setConfig] = useState({ email: true, - phone: true, + phone: false, passkey: true, socials: { enabled: true, diff --git a/packages/sdk-react/src/components/auth/Apple.tsx b/packages/sdk-react/src/components/auth/Apple.tsx index 7983c2052..e0c9549ba 100644 --- a/packages/sdk-react/src/components/auth/Apple.tsx +++ b/packages/sdk-react/src/components/auth/Apple.tsx @@ -15,13 +15,15 @@ declare global { } } -const AppleAuthButton: React.FC = ({ +const AppleAuthButton: React.FC = ({ iframePublicKey, onSuccess, clientId, + layout, }) => { const [appleSDKLoaded, setAppleSDKLoaded] = useState(false); const redirectURI = process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI!; + useEffect(() => { const loadAppleSDK = () => { const script = document.createElement("script"); @@ -40,11 +42,10 @@ const AppleAuthButton: React.FC = ({ }, []); if (!appleSDKLoaded) { - return null; // Or render a loading spinner + return null; } return ( - = ({ responseType="code id_token" responseMode="fragment" render={({ onClick }) => ( -
- - Apple +
+ + {layout === "stacked" && Apple}
)} callback={(response) => { diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index cfcc96315..489dc2385 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -104,10 +104,11 @@ button:hover { } + button:disabled { color: var(--Greyscale-700, #a2a7ae); background: #ffffff; - border-color: var(--Greyscale-400, #a2a7ae); + border-color: var(--Greyscale-100, #f5f7fb); cursor: not-allowed; } @@ -289,3 +290,19 @@ button:disabled { gap: 8px; margin-top: 16px; } + +.socialButtonContainerInline { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; /* Space between buttons */ +} + +.socialButtonContainerStacked { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + text-align: center +} diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 1a0f10a64..3ef1b5b88 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -27,6 +27,8 @@ import checkboxIcon from "assets/checkbox.svg"; import clockIcon from "assets/clock.svg"; import keyholeIcon from "assets/keyhole.svg"; import React from "react"; +import IconButton from "@mui/material/IconButton"; +import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; interface AuthProps { onHandleAuthSuccess: () => Promise; @@ -54,6 +56,8 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde const [resendText, setResendText] = useState("Resend code"); const [passkeySignupScreen, setPasskeySignupScreen] = useState(false); const [loading, setLoading] = useState(true); + const [isAuthScreen, setIsAuthScreen] = useState(true); + const otpInputRef = useRef(null); const handleResendCode = async () => { @@ -204,6 +208,70 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde await handleAuthSuccess(oauthResponse!.credentialBundle); }; + const renderBackButton = () => ( + setIsAuthScreen(true)} + style={{ + position: "absolute", + top: 16, + left: 16, + zIndex: 10, + }} + > + + + ); + + const renderSocialButtons = () => { + const { googleEnabled, appleEnabled, facebookEnabled } = authConfig; + const layout = + [googleEnabled, appleEnabled, facebookEnabled].filter(Boolean).length >= 2 + ? "inline" + : "stacked"; + + return ( +
+ {googleEnabled && ( + + handleOAuthLogin(response.credential, "Google") + } + /> + )} + {appleEnabled && ( + + handleOAuthLogin(response.authorization.id_token, "Apple") + } + /> + )} + {facebookEnabled && ( + + handleOAuthLogin(response.id_token, "Facebook") + } + /> + )} +
+ ); + }; + + const renderSection = (section: string) => { switch (section) { @@ -274,43 +342,10 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde
) : null; - case "socials": - return (authConfig.googleEnabled || authConfig.appleEnabled || authConfig.facebookEnabled) ? ( - -
- - {authConfig.googleEnabled && ( -
-
- handleOAuthLogin(response.credential, "Google")} - /> -
- )} - {authConfig.appleEnabled && ( -
-
- handleOAuthLogin(response.authorization.id_token, "Apple")} - /> -
- )} - {authConfig.facebookEnabled && ( -
-
- handleOAuthLogin(response.id_token, "Facebook")} - /> -
- )} -
- ) : null; + case "socials": + return (authConfig.googleEnabled || authConfig.appleEnabled || authConfig.facebookEnabled) + ? renderSocialButtons() + : null; default: return null; @@ -460,4 +495,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde ); }; -export default Auth; \ No newline at end of file +export default Auth; + + + diff --git a/packages/sdk-react/src/components/auth/Facebook.tsx b/packages/sdk-react/src/components/auth/Facebook.tsx index df37da13b..d4259a2c7 100644 --- a/packages/sdk-react/src/components/auth/Facebook.tsx +++ b/packages/sdk-react/src/components/auth/Facebook.tsx @@ -12,10 +12,11 @@ interface FacebookAuthButtonProps { onSuccess: (response: any) => void; } -const FacebookAuthButton: React.FC = ({ +const FacebookAuthButton: React.FC = ({ iframePublicKey, onSuccess, clientId, + layout, }) => { const [tokenExchanged, setTokenExchanged] = useState(false); const redirectURI = process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI!; @@ -82,19 +83,21 @@ const FacebookAuthButton: React.FC = ({ authCode, verifier ); - sessionStorage.removeItem("facebook_verifier"); // Remove verifier after use + sessionStorage.removeItem("facebook_verifier"); onSuccess(tokenData); - setTokenExchanged(true); // Prevent further exchanges + setTokenExchanged(true); } catch (error) { console.error("Error during token exchange:", error); - } finally { } }; return ( -
- - Facebook +
+ + {layout === "stacked" && Facebook}
); }; diff --git a/packages/sdk-react/src/components/auth/Google.tsx b/packages/sdk-react/src/components/auth/Google.tsx index d31e1d8fc..40fff5543 100644 --- a/packages/sdk-react/src/components/auth/Google.tsx +++ b/packages/sdk-react/src/components/auth/Google.tsx @@ -15,10 +15,11 @@ declare global { } } -const GoogleAuthButton: React.FC = ({ +const GoogleAuthButton: React.FC = ({ iframePublicKey, clientId, onSuccess, + layout, }) => { const handleLogin = async () => { const nonce = bytesToHex(sha256(iframePublicKey)); @@ -32,12 +33,16 @@ const GoogleAuthButton: React.FC = ({ return ( -
- - Google -
+
+ + {layout === "stacked" && Continue with Google} +
); }; + export default GoogleAuthButton; diff --git a/packages/sdk-react/src/components/auth/Socials.module.css b/packages/sdk-react/src/components/auth/Socials.module.css index d9310014f..5ce427c6f 100644 --- a/packages/sdk-react/src/components/auth/Socials.module.css +++ b/packages/sdk-react/src/components/auth/Socials.module.css @@ -15,12 +15,40 @@ transition: background-color 0.2s ease; } +.iconButton { + padding: 10px 16px; + gap: 8px; + color: var(--Greyscale-900, #2b2f33); + width: 100%; + font-size: 1rem; + background: #ffffff; + border: 1px solid var(--Greyscale-400, #a2a7ae); + border-radius: 8px; + cursor: pointer; + text-align: center; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.2s ease; +} + .socialButton:hover { background-color: #F4E8FF; } +.iconButton:hover { + background-color: #F4E8FF; +} + .iconSmall { width: 20x; height: 20px; margin-right: 4px; } + +.iconLarge { + width: 24px; + height: 24px; +} + + From 675fa618e4ce3f8bc0d2d8e4853a303decab4b0c Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Wed, 20 Nov 2024 11:56:17 -0500 Subject: [PATCH 34/73] add back button, add signup flow --- .../sdk-react/src/assets/fingerprintred.svg | 3 + packages/sdk-react/src/assets/redcircle.svg | 3 + .../src/components/auth/Auth.module.css | 2 +- .../sdk-react/src/components/auth/Auth.tsx | 475 +++++++++++------- 4 files changed, 290 insertions(+), 193 deletions(-) create mode 100644 packages/sdk-react/src/assets/fingerprintred.svg create mode 100644 packages/sdk-react/src/assets/redcircle.svg diff --git a/packages/sdk-react/src/assets/fingerprintred.svg b/packages/sdk-react/src/assets/fingerprintred.svg new file mode 100644 index 000000000..5229f4450 --- /dev/null +++ b/packages/sdk-react/src/assets/fingerprintred.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/sdk-react/src/assets/redcircle.svg b/packages/sdk-react/src/assets/redcircle.svg new file mode 100644 index 000000000..18d0cc013 --- /dev/null +++ b/packages/sdk-react/src/assets/redcircle.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index 489dc2385..c0aa8ddae 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -37,7 +37,7 @@ .circularProgress { position: absolute; - color: #007aff; + color: #4C48FF; } .oauthIcon { diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 3ef1b5b88..fd767827a 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -1,5 +1,5 @@ import styles from "./Auth.module.css"; -import { useEffect, useRef, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { useTurnkey } from "../../hooks/useTurnkey"; import { initOtpAuth, @@ -23,11 +23,11 @@ import emailIcon from "assets/email.svg"; import smsIcon from "assets/sms.svg"; import faceidIcon from "assets/faceid.svg"; import fingerprintIcon from "assets/fingerprint.svg"; +import redcircleIcon from "assets/redcircle.svg" +import fingerprintredIcon from "assets/fingerprintred.svg" import checkboxIcon from "assets/checkbox.svg"; import clockIcon from "assets/clock.svg"; import keyholeIcon from "assets/keyhole.svg"; -import React from "react"; -import IconButton from "@mui/material/IconButton"; import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; interface AuthProps { @@ -55,8 +55,10 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde const [suborgId, setSuborgId] = useState(""); const [resendText, setResendText] = useState("Resend code"); const [passkeySignupScreen, setPasskeySignupScreen] = useState(false); + const [passkeyCreationScreen, setPasskeyCreationScreen] = useState(false); + const [passkeySignupError, setPasskeySignupError] = useState(""); const [loading, setLoading] = useState(true); - const [isAuthScreen, setIsAuthScreen] = useState(true); + const [passkeyCreated, setPasskeyCreated] = useState(false); const otpInputRef = useRef(null); @@ -88,7 +90,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde }, [authIframeClient]); if (loading) { - return <> + return <>; } const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); @@ -122,6 +124,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde }; const handleSignupWithPasskey = async () => { + setPasskeySignupError("") const siteInfo = `${window.location.href} - ${new Date().toLocaleString( undefined, { @@ -133,13 +136,16 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde second: "2-digit", } )}`; + setPasskeySignupScreen(false) + setPasskeyCreationScreen(true) + try { + if (!passkeyCreated){ const { encodedChallenge, attestation } = (await passkeyClient?.createUserPasskey({ publicKey: { user: { name: siteInfo, displayName: siteInfo } }, })) || {}; if (encodedChallenge && attestation) { - // Use the generated passkey to create a new suborg await createSuborg({ email, passkey: { @@ -148,23 +154,32 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde attestation, }, }); + setPasskeyCreated(true) } else { - setError("Failed to create user passkey."); + setPasskeySignupError("Failed to create user passkey. Please try again") } + } const sessionResponse = await passkeyClient?.createReadWriteSession({ targetPublicKey: authIframeClient?.iframePublicKey!, - organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID! + organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, }); if (sessionResponse?.credentialBundle) { await handleAuthSuccess(sessionResponse.credentialBundle); } else { - setError("Failed to complete passkey login."); + setPasskeySignupError("Failed to login with passkey. Please try again") } + setPasskeyCreationScreen(false) + setPasskeySignupError("") + } + catch { + setPasskeySignupError("Passkey request timed out or rejected by user. Please try again") + } }; + const handleLoginWithPasskey = async () => { const sessionResponse = await passkeyClient?.createReadWriteSession({ targetPublicKey: authIframeClient?.iframePublicKey!, - organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID! + organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, }); if (sessionResponse?.credentialBundle) { await handleAuthSuccess(sessionResponse.credentialBundle); @@ -209,25 +224,38 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde }; const renderBackButton = () => ( - setIsAuthScreen(true)} - style={{ - position: "absolute", - top: 16, - left: 16, - zIndex: 10, - }} - > - - + + { + setPasskeyCreationScreen(false); + setPasskeySignupError(""); + setPasskeySignupScreen(false); + setOtpId(null); + }} + sx={{ + color: "#868c95", + position: "absolute", + top: 16, + left: 16, + zIndex: 10, + cursor: "pointer", + borderRadius: "50%", + padding: "6px", + transition: "background-color 0.3s ease", + "&:hover": { + backgroundColor: "#e0e3ea", + }, + }} +/> + ); - + const renderSocialButtons = () => { const { googleEnabled, appleEnabled, facebookEnabled } = authConfig; const layout = - [googleEnabled, appleEnabled, facebookEnabled].filter(Boolean).length >= 2 - ? "inline" - : "stacked"; + [googleEnabled, appleEnabled, facebookEnabled].filter(Boolean).length >= 2 + ? "inline" + : "stacked"; return (
= ({ onHandleAuthSuccess, authConfig, configOrde > {googleEnabled && ( @@ -249,7 +277,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde )} {appleEnabled && ( @@ -259,7 +287,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde )} {facebookEnabled && ( @@ -271,40 +299,38 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde ); }; - const renderSection = (section: string) => { - switch (section) { case "email": return authConfig.emailEnabled && !otpId ? (
- setEmail(e.target.value)} - fullWidth - sx={{ - "& .MuiOutlinedInput-root": { - "& fieldset": { - borderColor: "#D0D5DD", - }, - "&:hover fieldset": { - borderColor: "#8A929E", - }, - "&.Mui-focused fieldset": { - borderColor: "#D0D5DD", - border: "1px solid" - }, - }, - "& .MuiInputBase-input": { - padding: "12px" - }, - backgroundColor: "white", - }} - variant="outlined" -/> + setEmail(e.target.value)} + fullWidth + sx={{ + "& .MuiOutlinedInput-root": { + "& fieldset": { + borderColor: "#D0D5DD", + }, + "&:hover fieldset": { + borderColor: "#8A929E", + }, + "&.Mui-focused fieldset": { + borderColor: "#D0D5DD", + border: "1px solid", + }, + }, + "& .MuiInputBase-input": { + padding: "12px", + }, + backgroundColor: "white", + }} + variant="outlined" + />
-
setPasskeySignupScreen(true)}> + +
setPasskeySignupScreen(true)} + > Sign up with passkey
@@ -342,10 +373,12 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde
) : null; - case "socials": - return (authConfig.googleEnabled || authConfig.appleEnabled || authConfig.facebookEnabled) - ? renderSocialButtons() - : null; + case "socials": + return authConfig.googleEnabled || + authConfig.appleEnabled || + authConfig.facebookEnabled + ? renderSocialButtons() + : null; default: return null; @@ -354,148 +387,206 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde return ( <> - {passkeySignupScreen ? -
-
- - + {passkeySignupScreen ? ( +
+ {renderBackButton()} +
+ + +
+
+

Secure your account with a passkey

+
+
+
+ + Log in with Touch ID, Face ID, or a security key +
+
+ + More secure than a password +
+
+ + Takes seconds to set up and use +
+
+ +
+ ) : passkeyCreationScreen ?
+ {renderBackButton()} +
+
+ { !passkeySignupError ? + <> + + + : +<> + + + +}
-
-

Secure your account with a passkey

-
+
+
+

{passkeySignupError ? "Something went wrong" : passkeyCreated ? "Logging in with passkey" : "Creating passkey"}

+
+
+
+ + {passkeySignupError ? passkeySignupError : "Please follow prompts to verify your passkey"} -
-
- - Log in with Touch ID, Face ID, or a security key -
-
- - More secure than a password -
-
- - Takes seconds to set up and use -
+
+
+ { passkeySignupError && + +} +
:( +
+ {oauthLoading !== "" ? ( +
+

+ Verifying with {oauthLoading} +

+
+ + {oauthLoading === "Google" && ( + + )} + {oauthLoading === "Facebook" && ( + + )} + {oauthLoading === "Apple" && ( + + )}
- -
: -
- {oauthLoading !== "" ? ( -
-

Verifying with {oauthLoading}

-
- - {oauthLoading === "Google" && } - {oauthLoading === "Facebook" && } - {oauthLoading === "Apple" && } -
-
Powered by
-
- ) : ( -
-

{otpId ? "Enter verification code" : "Log in or sign up"}

-
- {!otpId && configOrder - .filter((section) => renderSection(section) !== null) - .map((section, index, visibleSections) => ( - - {renderSection(section)} - {index < visibleSections.length - 1 && ( -
- OR +
+ Powered by + +
+
+ ) : ( +
+ {otpId && renderBackButton()} +

{otpId ? "Enter verification code" : "Log in or sign up"}

+
+ {!otpId && + configOrder + .filter((section) => renderSection(section) !== null) + .map((section, index, visibleSections) => ( + + {renderSection(section)} + {index < visibleSections.length - 1 && ( +
+ OR +
+ )} +
+ ))} + + {otpId && ( +
+
+
+ {step === "otpEmail" ? ( + + ) : ( + + )} +
+ + + Enter the 6-digit code we sent to{" "} +
+ {step === "otpEmail" + ? email + : formatPhoneNumber(phone)} +
+
+ +
+
+ {otpError ? otpError : " "}
- )} - - - ))} - -{otpId && ( -
-
-
- {step === "otpEmail" ? ( - +
+ )} + + {!otpId ? ( +
+ + By continuing, you agree to our{" "} + + Terms of Service + {" "} + &{" "} + + Privacy Policy + + +
) : ( - +
+ + + {resendText} + + +
)}
- - - Enter the 6-digit code we sent to{" "} -
- {step === "otpEmail" ? email : formatPhoneNumber(phone)} -
-
- +
(window.location.href = "https://www.turnkey.com/")} + className={styles.poweredBy} + > + Secured by + +
-
{otpError ? otpError : " "}
-
)} - - {!otpId ? -
- - By continuing, you agree to our{" "} - - Terms of Service - {" "} - &{" "} - - Privacy Policy - - -
: -
- - - {resendText} - - -
- } - -
-
(window.location.href = "https://www.turnkey.com/")} - className={styles.poweredBy} - > - Secured by - -
-
)} -
} + ); }; export default Auth; - - - From 8e25b952f926468cf9f2eb59d4e0b0b9b6ef4405 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Wed, 20 Nov 2024 13:16:35 -0500 Subject: [PATCH 35/73] change google flow to popup --- .../sdk-react/src/components/auth/Apple.tsx | 3 +- .../sdk-react/src/components/auth/Auth.tsx | 6 +- .../src/components/auth/Facebook.tsx | 3 +- .../sdk-react/src/components/auth/Google.tsx | 58 +++++++++++++++---- 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/packages/sdk-react/src/components/auth/Apple.tsx b/packages/sdk-react/src/components/auth/Apple.tsx index e0c9549ba..7417cba42 100644 --- a/packages/sdk-react/src/components/auth/Apple.tsx +++ b/packages/sdk-react/src/components/auth/Apple.tsx @@ -23,7 +23,6 @@ const AppleAuthButton: React.FC { const [appleSDKLoaded, setAppleSDKLoaded] = useState(false); const redirectURI = process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI!; - useEffect(() => { const loadAppleSDK = () => { const script = document.createElement("script"); @@ -58,7 +57,7 @@ const AppleAuthButton: React.FC - {layout === "stacked" && Apple} + {layout === "stacked" && Continue with Apple}
)} callback={(response) => { diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index fd767827a..4da83cdc5 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -271,7 +271,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde clientId={process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!} iframePublicKey={authIframeClient!.iframePublicKey!} onSuccess={(response: any) => - handleOAuthLogin(response.credential, "Google") + handleOAuthLogin(response.idToken, "Google") } /> )} @@ -422,7 +422,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde { !passkeySignupError ? <> @@ -459,7 +459,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde
diff --git a/packages/sdk-react/src/components/auth/Facebook.tsx b/packages/sdk-react/src/components/auth/Facebook.tsx index d4259a2c7..ea125c0c7 100644 --- a/packages/sdk-react/src/components/auth/Facebook.tsx +++ b/packages/sdk-react/src/components/auth/Facebook.tsx @@ -68,6 +68,7 @@ const FacebookAuthButton: React.FC { + const redirectURI = process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI!; const verifier = sessionStorage.getItem("facebook_verifier"); if (!verifier || tokenExchanged) { console.error( @@ -97,7 +98,7 @@ const FacebookAuthButton: React.FC - {layout === "stacked" && Facebook} + {layout === "stacked" && Continue with Facebook}
); }; diff --git a/packages/sdk-react/src/components/auth/Google.tsx b/packages/sdk-react/src/components/auth/Google.tsx index 40fff5543..5e7778b1d 100644 --- a/packages/sdk-react/src/components/auth/Google.tsx +++ b/packages/sdk-react/src/components/auth/Google.tsx @@ -1,4 +1,3 @@ -import { GoogleOAuthProvider } from "@react-oauth/google"; import { sha256 } from "@noble/hashes/sha2"; import { bytesToHex } from "@noble/hashes/utils"; import styles from "./Socials.module.css"; @@ -23,26 +22,63 @@ const GoogleAuthButton: React.FC { const handleLogin = async () => { const nonce = bytesToHex(sha256(iframePublicKey)); - await window.google?.accounts.id.initialize({ - client_id: clientId, - callback: onSuccess, - nonce: nonce, - }); - window.google?.accounts.id.prompt(); + const redirectURI = process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI!.replace(/\/$/, ''); + // Construct the Google OIDC URL + const googleAuthUrl = new URL("https://accounts.google.com/o/oauth2/v2/auth"); + googleAuthUrl.searchParams.set("client_id", clientId); + googleAuthUrl.searchParams.set("redirect_uri", redirectURI); // Replace with your actual redirect URI + googleAuthUrl.searchParams.set("response_type", "id_token"); // Use id_token for OpenID Connect + googleAuthUrl.searchParams.set("scope", "openid email profile"); // Scopes required for OpenID + googleAuthUrl.searchParams.set("nonce", nonce); + + // Open the login flow in a new window + const authWindow = window.open( + googleAuthUrl.toString(), + "_blank", + "width=500,height=600,scrollbars=yes,resizable=yes" + ); + + if (!authWindow) { + console.error("Failed to open Google login window."); + return; + } + + // Monitor the child window for redirect and extract tokens + const interval = setInterval(() => { + try { + const url = authWindow?.location.href || ""; + if (url.startsWith(window.location.origin)) { + const hashParams = new URLSearchParams(url.split("#")[1]); + const idToken = hashParams.get("id_token"); + if (idToken) { + authWindow?.close(); + clearInterval(interval); + onSuccess({ idToken }); + } + } + } catch (error) { + // Ignore cross-origin errors until redirected + } + + if (authWindow?.closed) { + clearInterval(interval); + } + }, 500); }; return ( -
- + Google {layout === "stacked" && Continue with Google}
-
); }; - export default GoogleAuthButton; From 862d92cf397515bcc29c6dc8ab31cca27a1c22c4 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Wed, 20 Nov 2024 14:11:24 -0500 Subject: [PATCH 36/73] add grid, resend code logic, styling updates --- examples/react-components/public/grid.svg | 9 +++++++++ .../src/app/dashboard/dashboard.css | 2 ++ .../react-components/src/app/dashboard/page.tsx | 14 ++++++++------ examples/react-components/src/app/index.css | 4 ++++ .../sdk-react/src/components/auth/Auth.module.css | 13 ++++++------- packages/sdk-react/src/components/auth/Auth.tsx | 7 ++++++- 6 files changed, 35 insertions(+), 14 deletions(-) create mode 100644 examples/react-components/public/grid.svg diff --git a/examples/react-components/public/grid.svg b/examples/react-components/public/grid.svg new file mode 100644 index 000000000..0a75c8f9d --- /dev/null +++ b/examples/react-components/public/grid.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/examples/react-components/src/app/dashboard/dashboard.css b/examples/react-components/src/app/dashboard/dashboard.css index 45d0e1404..ae280834e 100644 --- a/examples/react-components/src/app/dashboard/dashboard.css +++ b/examples/react-components/src/app/dashboard/dashboard.css @@ -90,6 +90,8 @@ .accountAddress { flex: 1; margin-left: 8px; + font-family: monospace; + font-size: 16px; } .radioButton { diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index 733597d1a..1ea4b1aba 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -85,10 +85,11 @@ export default function Dashboard() { const handleSign = async () => { try { const addressType = selectedAccount?.startsWith("0x") ? "ETH" : "SOL"; + const message = messageToSign ? messageToSign : "Signing within Turnkey Demo" const hashedMessage = addressType === "ETH" - ? keccak256(toUtf8Bytes(messageToSign)) // Ethereum requires keccak256 hash - : Buffer.from(messageToSign, "utf8").toString("hex"); // Solana doesn't require hashing + ? keccak256(toUtf8Bytes(message)) // Ethereum requires keccak256 hash + : Buffer.from(message, "utf8").toString("hex"); // Solana doesn't require hashing const resp = await authIframeClient?.signRawPayload({ signWith: selectedAccount!, @@ -108,19 +109,19 @@ export default function Dashboard() { const handleVerify = () => { if (!signature) return; - + const message = messageToSign ? messageToSign : "Signing within Turnkey Demo" const addressType = selectedAccount?.startsWith("0x") ? "ETH" : "SOL"; const verificationPassed = addressType === "ETH" ? verifyEthSignature( - messageToSign, + message, signature.r, signature.s, signature.v, selectedAccount! ) : verifySolSignatureWithAddress( - messageToSign, + message, signature.r, signature.s, selectedAccount! @@ -254,13 +255,14 @@ export default function Dashboard() { color: "#6C727E", }} > - Signing this message will not cost you any fees. + This helps prove you signed a message using your address setMessageToSign(e.target.value)} + placeholder="Signing within Turnkey Demo" sx={{ bgcolor: "#ffffff", "& .MuiOutlinedInput-root": { diff --git a/examples/react-components/src/app/index.css b/examples/react-components/src/app/index.css index 4699a527b..0f12c7594 100644 --- a/examples/react-components/src/app/index.css +++ b/examples/react-components/src/app/index.css @@ -24,6 +24,10 @@ padding: 6rem; gap: 60px; position: relative; + background-image: url("../../public/grid.svg"); + background-repeat: repeat; + background-size: auto; + background-position: center; } .authConfigContainer { diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index c0aa8ddae..c36a84830 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -109,7 +109,7 @@ button:disabled { color: var(--Greyscale-700, #a2a7ae); background: #ffffff; border-color: var(--Greyscale-100, #f5f7fb); - cursor: not-allowed; + cursor: default; } /* Separator */ @@ -189,13 +189,9 @@ button:disabled { color: var(--Greyscale-900, #2b2f33); font-weight: 700; cursor: pointer; + text-decoration: none; } -.tosBold { - color: var(--Greyscale-900, #2b2f33); - font-weight: 700; - cursor: pointer; -} .authButton { display: flex; @@ -281,8 +277,11 @@ button:disabled { align-items: center; text-align: center; display: inline-block; + transition: color 0.2s ease; +} +.noPasskeyLink:hover { + color: #000000; } - .passkeyContainer { display: flex; flex-direction: column; diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 4da83cdc5..7862db945 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -70,8 +70,12 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde await handleOtpLogin("PHONE_NUMBER", phone, "OTP_TYPE_SMS"); } setResendText("Code sent ✓"); + + setTimeout(() => { + setResendText("Resend code"); + }, 15000); }; - + const formatPhoneNumber = (phone: string) => { const phoneNumber = parsePhoneNumberFromString(phone); return phoneNumber ? phoneNumber.formatInternational() : phone; @@ -159,6 +163,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde setPasskeySignupError("Failed to create user passkey. Please try again") } } + const sessionResponse = await passkeyClient?.createReadWriteSession({ targetPublicKey: authIframeClient?.iframePublicKey!, organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, From d8a117b3dd99a3dc850cbc13e90919ae61c23ec6 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Wed, 20 Nov 2024 20:46:55 -0500 Subject: [PATCH 37/73] import export components almost done --- .../src/app/dashboard/page.tsx | 24 +- packages/sdk-react/package.json | 1 + .../src/components/export/Export.module.css | 54 +++++ .../src/components/export/Export.tsx | 225 ++++++++++++++++++ .../sdk-react/src/components/export/index.ts | 1 + .../src/components/import/Import.module.css | 54 +++++ .../src/components/import/Import.tsx | 200 ++++++++++++++++ .../sdk-react/src/components/import/index.ts | 1 + packages/sdk-react/src/components/index.ts | 2 + .../sdk-react/src/contexts/TurnkeyContext.tsx | 1 - packages/sdk-react/src/index.ts | 2 + pnpm-lock.yaml | 7 +- 12 files changed, 566 insertions(+), 6 deletions(-) create mode 100644 packages/sdk-react/src/components/export/Export.module.css create mode 100644 packages/sdk-react/src/components/export/Export.tsx create mode 100644 packages/sdk-react/src/components/export/index.ts create mode 100644 packages/sdk-react/src/components/import/Import.module.css create mode 100644 packages/sdk-react/src/components/import/Import.tsx create mode 100644 packages/sdk-react/src/components/import/index.ts diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index 1ea4b1aba..885aaffcd 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useTurnkey } from "@turnkey/sdk-react"; +import { Export, Import, useTurnkey } from "@turnkey/sdk-react"; import { useEffect, useState } from "react"; import "./dashboard.css"; import { @@ -23,6 +23,7 @@ export default function Dashboard() { const [loading, setLoading] = useState(true); const [accounts, setAccounts] = useState([]); const [selectedAccount, setSelectedAccount] = useState(null); + const [selectedWallet, setSelectedWallet] = useState(null) const [isModalOpen, setIsModalOpen] = useState(false); const [messageToSign, setMessageToSign] = useState(""); const [signature, setSignature] = useState(null); @@ -49,6 +50,7 @@ export default function Dashboard() { if (accountsResponse.accounts.length > 0) { setSelectedAccount(accountsResponse.accounts[0].address); } + setSelectedWallet(wallets.wallets[0].walletId) if (authIframeClient && authIframeClient.config) { authIframeClient.config.organizationId = whoamiResponse.organizationId!; } @@ -212,8 +214,8 @@ export default function Dashboard() {
- - + +
@@ -246,6 +248,22 @@ export default function Dashboard() { borderRadius: 2, }} > +
+ × +
Sign a Message diff --git a/packages/sdk-react/package.json b/packages/sdk-react/package.json index 5010096d8..689372375 100644 --- a/packages/sdk-react/package.json +++ b/packages/sdk-react/package.json @@ -58,6 +58,7 @@ "@turnkey/sdk-browser": "workspace:*", "@turnkey/wallet-stamper": "workspace:*", "usehooks-ts": "^3.1.0", + "@turnkey/crypto": "workspace:*", "@turnkey/sdk-server": "workspace:*", "libphonenumber-js": "^1.11.14", "next": "^15.0.2", diff --git a/packages/sdk-react/src/components/export/Export.module.css b/packages/sdk-react/src/components/export/Export.module.css new file mode 100644 index 000000000..4646ed3a2 --- /dev/null +++ b/packages/sdk-react/src/components/export/Export.module.css @@ -0,0 +1,54 @@ +.modalTitle { + font-size: 18px; + line-height: 24px; + font-weight: 600; + letter-spacing: -0.01em; + text-align: left; + } + +.exportCard { + font-family: Inter; + position: relative; + width: 100%; + max-width: 450px; + min-width: 375px; + margin: 0 auto; + padding: 20px; + background: var(--Greyscale-20, #f5f7fb); + border: 1px solid var(--Greyscale-100, #ebedf2); + border-radius: 15px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + } + + .exportCard h2 { + font-size: 20px; + font-weight: 700; + line-height: 26px; + letter-spacing: -0.01em; + text-align: center; + font-size: 1.5rem; + margin-bottom: 16px; + } + + .exportButton { + padding: 10px 16px 10px 16px; + gap: 8px; + color: #ffffff; + width: 100%; + font-size: 1rem; + background: var(--Greyscale-900, #2b2f33); + border: 1px solid var(--Greyscale-800, #3f464b); + border-radius: 8px; + cursor: pointer; + } + + .exportButton:disabled { + color: var(--Greyscale-700, #a2a7ae); + background: #ffffff; + border-color: var(--Greyscale-400, #a2a7ae); + cursor: not-allowed; + } + +.exportButton:hover { + background-color: var(--Greyscale-700, #4a4f55); + } diff --git a/packages/sdk-react/src/components/export/Export.tsx b/packages/sdk-react/src/components/export/Export.tsx new file mode 100644 index 000000000..f2dfcb306 --- /dev/null +++ b/packages/sdk-react/src/components/export/Export.tsx @@ -0,0 +1,225 @@ +import React, { useState } from "react"; +import { Modal, Box, Typography } from "@mui/material"; +import { useTurnkey } from "../../hooks/useTurnkey"; +import { IframeStamper } from "@turnkey/sdk-browser"; +import styles from "./Export.module.css"; + +type ExportProps = { + onCancel?: () => void; + exportType?: "EXPORT_TYPE_PRIVATE_KEY" | "EXPORT_TYPE_SEED_PHRASE"; + address?: string; + walletId?: string; +}; + +const Export: React.FC = ({ + onCancel = () => undefined, + exportType, + address, + walletId, +}) => { + const { authIframeClient } = useTurnkey(); + const [exportIframeStamper, setExportIframeStamper] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isIframeVisible, setIsIframeVisible] = useState(false); + const TurnkeyExportIframeContainerId = "turnkey-export-iframe-container-id"; + const TurnkeyIframeElementId = "turnkey-export-iframe-element-id"; + + const handleOpenModal = async () => { + setIsModalOpen(true); + + // Wait for the modal and its content to render + requestAnimationFrame(async () => { + const iframeContainer = document.getElementById(TurnkeyExportIframeContainerId); + if (!iframeContainer) { + console.error("Iframe container not found."); + return; + } + + const existingIframe = document.getElementById(TurnkeyIframeElementId); + + if (!existingIframe) { + try { + const iframeStamper = new IframeStamper({ + iframeContainer, + iframeUrl: "https://export.preprod.turnkey.engineering", + iframeElementId: TurnkeyIframeElementId, + }); + + await iframeStamper.init(); + const styles = { + padding: "20px", + fontFamily: "monospace", + color: "#333", + height: "240px", + width: "280px", + borderStyle: "none", + backgroundColor: "#ffffff", + overflowWrap: "break-word", + wordWrap: "break-word", + resize: "none", + }; + iframeStamper.applySettings({ styles }); + + setExportIframeStamper(iframeStamper); + console.log("IframeStamper initialized successfully."); + } catch (error) { + console.error("Error initializing IframeStamper:", error); + } + } + }); + }; + + const handleCloseModal = () => { + // Clear the iframe stamper + if (exportIframeStamper) { + exportIframeStamper.clear(); + setExportIframeStamper(null); + + const existingIframe = document.getElementById(TurnkeyIframeElementId); + if (existingIframe) { + existingIframe.remove(); + } + } + + // Reset modal and iframe states + setIsModalOpen(false); + setIsIframeVisible(false); + }; + + const handleExport = async () => { + if (!exportIframeStamper || !exportIframeStamper.iframePublicKey) { + console.error("IframeStamper is not initialized or missing public key."); + return; + } + + if (exportType === "EXPORT_TYPE_PRIVATE_KEY") { + await exportWalletAccount(); + } else { + await exportWallet(); + } + + setIsIframeVisible(true); + }; + + const exportWallet = async () => { + const exportResponse = await authIframeClient?.exportWallet({ + walletId: walletId!, + targetPublicKey: exportIframeStamper!.iframePublicKey!, + }); + const whoami = await authIframeClient!.getWhoami(); + if (exportResponse?.exportBundle) { + await exportIframeStamper?.injectWalletExportBundle( + exportResponse.exportBundle, + whoami.organizationId + ); + } + }; + + const exportWalletAccount = async () => { + const exportResponse = await authIframeClient?.exportWalletAccount({ + address: address!, + targetPublicKey: exportIframeStamper!.iframePublicKey!, + }); + const whoami = await authIframeClient!.getWhoami(); + if (exportResponse?.exportBundle) { + await exportIframeStamper?.injectKeyExportBundle( + exportResponse.exportBundle, + whoami.organizationId + ); + } + }; + + return ( + <> + {/* Button to open the modal */} + + + {/* Combined Modal */} + + + + {/* Close Button */} +
+ × +
+ +{!isIframeVisible && +<> + + Export wallet + + + Exporting your seed phrase poses significant security risks. Ensure it is stored securely and never shared with anyone. Anyone with access to your seed phrase can gain full control over your wallet and funds. Proceed with caution. + + +} + {/* Confirmation Buttons */} + {!isIframeVisible && ( + <> +
+ + Are you sure you want to export? + +
+ + + )} +
+ + + + + + ); +}; + +export default Export; diff --git a/packages/sdk-react/src/components/export/index.ts b/packages/sdk-react/src/components/export/index.ts new file mode 100644 index 000000000..585761b9f --- /dev/null +++ b/packages/sdk-react/src/components/export/index.ts @@ -0,0 +1 @@ +export { default as Export } from "./Export"; diff --git a/packages/sdk-react/src/components/import/Import.module.css b/packages/sdk-react/src/components/import/Import.module.css new file mode 100644 index 000000000..704e0d4c7 --- /dev/null +++ b/packages/sdk-react/src/components/import/Import.module.css @@ -0,0 +1,54 @@ +.modalTitle { + font-size: 18px; + line-height: 24px; + font-weight: 600; + letter-spacing: -0.01em; + text-align: left; + } + +.importCard { + font-family: Inter; + position: relative; + width: 100%; + max-width: 450px; + min-width: 375px; + margin: 0 auto; + padding: 20px; + background: var(--Greyscale-20, #f5f7fb); + border: 1px solid var(--Greyscale-100, #ebedf2); + border-radius: 15px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + } + + .importCard h2 { + font-size: 20px; + font-weight: 700; + line-height: 26px; + letter-spacing: -0.01em; + text-align: center; + font-size: 1.5rem; + margin-bottom: 16px; + } + + .importBUtton { + padding: 10px 16px 10px 16px; + gap: 8px; + color: #ffffff; + width: 100%; + font-size: 1rem; + background: var(--Greyscale-900, #2b2f33); + border: 1px solid var(--Greyscale-800, #3f464b); + border-radius: 8px; + cursor: pointer; + } + + .importBUtton:disabled { + color: var(--Greyscale-700, #a2a7ae); + background: #ffffff; + border-color: var(--Greyscale-400, #a2a7ae); + cursor: not-allowed; + } + +.importBUtton:hover { + background-color: var(--Greyscale-700, #4a4f55); + } diff --git a/packages/sdk-react/src/components/import/Import.tsx b/packages/sdk-react/src/components/import/Import.tsx new file mode 100644 index 000000000..c2a1c614a --- /dev/null +++ b/packages/sdk-react/src/components/import/Import.tsx @@ -0,0 +1,200 @@ +import React, { useState } from "react"; +import { Modal, Box, Typography } from "@mui/material"; +import { useTurnkey } from "../../hooks/useTurnkey"; +import { DEFAULT_ETHEREUM_ACCOUNTS, DEFAULT_SOLANA_ACCOUNTS, IframeStamper } from "@turnkey/sdk-browser"; +import styles from "./Import.module.css"; + +type ImportProps = { + onCancel?: () => void; + onSuccess?: () => void; +}; + +const Import: React.FC = ({ + onCancel = () => undefined, + onSuccess = () => undefined, +}) => { + const { authIframeClient } = useTurnkey(); + const [importIframeStamper, setImportIframeStamper] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isIframeVisible, setIsIframeVisible] = useState(false); + const TurnkeyImportIframeContainerId = "turnkey-import-iframe-container-id"; + const TurnkeyIframeElementId = "turnkey-import-iframe-element-id"; + + const handleOpenModal = async () => { + setIsModalOpen(true); + + requestAnimationFrame(async () => { + const iframeContainer = document.getElementById(TurnkeyImportIframeContainerId); + if (!iframeContainer) { + console.error("Iframe container not found."); + return; + } + + const existingIframe = document.getElementById(TurnkeyIframeElementId); + + if (!existingIframe) { + try { + const iframeStamper = new IframeStamper({ + iframeContainer, + iframeUrl: "https://import.preprod.turnkey.engineering", + iframeElementId: TurnkeyIframeElementId, + }); + + await iframeStamper.init(); + const styles = { + padding: "20px", + fontFamily: "monospace", + color: "#333", + height: "240px", + width: "280px", + borderStyle: "none", + backgroundColor: "#ffffff", + overflowWrap: "break-word", + wordWrap: "break-word", + resize: "none", + }; + iframeStamper.applySettings({ styles }); + + setImportIframeStamper(iframeStamper); + console.log("IframeStamper initialized successfully."); + } catch (error) { + console.error("Error initializing IframeStamper:", error); + } + } + }); + }; + + const handleCloseModal = () => { + if (importIframeStamper) { + importIframeStamper.clear(); + setImportIframeStamper(null); + + const existingIframe = document.getElementById(TurnkeyIframeElementId); + if (existingIframe) { + existingIframe.remove(); + } + } + + setIsModalOpen(false); + setIsIframeVisible(false); + }; + + const handleImport = async () => { + if (!importIframeStamper) { + console.error("IframeStamper is not initialized."); + return; + } + + const encryptedBundle = await importIframeStamper.extractWalletEncryptedBundle(); + if (!encryptedBundle || encryptedBundle.trim() === "") { + alert("Failed to retrieve encrypted bundle."); + return; + } + const whoami = await authIframeClient!.getWhoami(); + const response = await authIframeClient?.importWallet({ + organizationId: whoami.organizationId, + userId: whoami.userId, + walletName: "TEST", + encryptedBundle, + accounts: [ + ...DEFAULT_ETHEREUM_ACCOUNTS, + ...DEFAULT_SOLANA_ACCOUNTS, + ], + }); + + if (response) { + console.log("Wallet imported successfully!"); + setIsIframeVisible(false); + onSuccess(); + } else { + alert("Failed to import wallet! Please try again."); + } + }; + + return ( + <> + {/* Button to open the modal */} + + + {/* Combined Modal */} + + + {/* Close Button */} +
+ × +
+ + {!isIframeVisible && ( + <> + + Import Wallet + + + Import an existing wallet using your secret recovery phrase. + Ensure that the phrase is entered securely. + + + )} + + {/* Import Flow */} + {!isIframeVisible ? ( + + ) : ( +
+ )} + + + + ); +}; + +export default Import; diff --git a/packages/sdk-react/src/components/import/index.ts b/packages/sdk-react/src/components/import/index.ts new file mode 100644 index 000000000..476acb2f7 --- /dev/null +++ b/packages/sdk-react/src/components/import/index.ts @@ -0,0 +1 @@ +export { default as Import } from "./Import"; diff --git a/packages/sdk-react/src/components/index.ts b/packages/sdk-react/src/components/index.ts index 97ccf7649..947a63053 100644 --- a/packages/sdk-react/src/components/index.ts +++ b/packages/sdk-react/src/components/index.ts @@ -1 +1,3 @@ export * from "./auth"; +export * from "./export"; +export * from "./import"; \ No newline at end of file diff --git a/packages/sdk-react/src/contexts/TurnkeyContext.tsx b/packages/sdk-react/src/contexts/TurnkeyContext.tsx index e185a7d85..5f6417fbe 100644 --- a/packages/sdk-react/src/contexts/TurnkeyContext.tsx +++ b/packages/sdk-react/src/contexts/TurnkeyContext.tsx @@ -168,7 +168,6 @@ export const TurnkeyProvider: React.FC = ({
); diff --git a/packages/sdk-react/src/index.ts b/packages/sdk-react/src/index.ts index 9a3e12a47..f1f4e0dae 100644 --- a/packages/sdk-react/src/index.ts +++ b/packages/sdk-react/src/index.ts @@ -1,5 +1,7 @@ import "./components/auth/Auth.module.css"; import "./components/auth/PhoneInput.css"; +import "./components/export/Export.module.css" +import "./components/import/Import.module.css" import { TurnkeyContext, TurnkeyProvider } from "./contexts/TurnkeyContext"; import { useTurnkey } from "./hooks/useTurnkey"; export * from "./components"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19918cc70..5acd6bbfd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1828,6 +1828,9 @@ importers: '@react-oauth/google': specifier: ^0.12.1 version: 0.12.1(react-dom@18.3.1)(react@18.2.0) + '@turnkey/crypto': + specifier: workspace:* + version: link:../crypto '@turnkey/sdk-browser': specifier: workspace:* version: link:../sdk-browser @@ -7221,10 +7224,10 @@ packages: dependencies: '@babel/runtime': 7.26.0 '@emotion/cache': 11.13.1 - '@emotion/react': 11.13.3(@types/react@18.2.14)(react@18.2.0) + '@emotion/react': 11.13.3(@types/react@18.2.75)(react@18.2.0) '@emotion/serialize': 1.3.2 '@emotion/sheet': 1.4.0 - '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.14)(react@18.2.0) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.75)(react@18.2.0) csstype: 3.1.3 prop-types: 15.8.1 react: 18.2.0 From 190c6482746fac1ba59faa2862a5a01461a046c2 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Wed, 20 Nov 2024 22:25:05 -0500 Subject: [PATCH 38/73] working export and import components and styling --- .../src/components/export/Export.tsx | 40 +------ .../src/components/import/Import.tsx | 113 ++++++++++++------ 2 files changed, 77 insertions(+), 76 deletions(-) diff --git a/packages/sdk-react/src/components/export/Export.tsx b/packages/sdk-react/src/components/export/Export.tsx index f2dfcb306..5bcb82593 100644 --- a/packages/sdk-react/src/components/export/Export.tsx +++ b/packages/sdk-react/src/components/export/Export.tsx @@ -5,16 +5,10 @@ import { IframeStamper } from "@turnkey/sdk-browser"; import styles from "./Export.module.css"; type ExportProps = { - onCancel?: () => void; - exportType?: "EXPORT_TYPE_PRIVATE_KEY" | "EXPORT_TYPE_SEED_PHRASE"; - address?: string; walletId?: string; }; const Export: React.FC = ({ - onCancel = () => undefined, - exportType, - address, walletId, }) => { const { authIframeClient } = useTurnkey(); @@ -51,7 +45,7 @@ const Export: React.FC = ({ fontFamily: "monospace", color: "#333", height: "240px", - width: "280px", + width: "240px", borderStyle: "none", backgroundColor: "#ffffff", overflowWrap: "break-word", @@ -87,17 +81,7 @@ const Export: React.FC = ({ }; const handleExport = async () => { - if (!exportIframeStamper || !exportIframeStamper.iframePublicKey) { - console.error("IframeStamper is not initialized or missing public key."); - return; - } - - if (exportType === "EXPORT_TYPE_PRIVATE_KEY") { - await exportWalletAccount(); - } else { - await exportWallet(); - } - + await exportWallet() setIsIframeVisible(true); }; @@ -115,28 +99,12 @@ const Export: React.FC = ({ } }; - const exportWalletAccount = async () => { - const exportResponse = await authIframeClient?.exportWalletAccount({ - address: address!, - targetPublicKey: exportIframeStamper!.iframePublicKey!, - }); - const whoami = await authIframeClient!.getWhoami(); - if (exportResponse?.exportBundle) { - await exportIframeStamper?.injectKeyExportBundle( - exportResponse.exportBundle, - whoami.organizationId - ); - } - }; - return ( <> - {/* Button to open the modal */} - {/* Combined Modal */} = ({ boxShadow: 24, p: 4, borderRadius: 2, - width: "340px" + width: "336px" }} > - {/* Close Button */}
= ({ } - {/* Confirmation Buttons */} {!isIframeVisible && ( <>
diff --git a/packages/sdk-react/src/components/import/Import.tsx b/packages/sdk-react/src/components/import/Import.tsx index c2a1c614a..16885a84e 100644 --- a/packages/sdk-react/src/components/import/Import.tsx +++ b/packages/sdk-react/src/components/import/Import.tsx @@ -1,22 +1,20 @@ import React, { useState } from "react"; -import { Modal, Box, Typography } from "@mui/material"; +import { Modal, Box, Typography, TextField } from "@mui/material"; import { useTurnkey } from "../../hooks/useTurnkey"; import { DEFAULT_ETHEREUM_ACCOUNTS, DEFAULT_SOLANA_ACCOUNTS, IframeStamper } from "@turnkey/sdk-browser"; import styles from "./Import.module.css"; type ImportProps = { - onCancel?: () => void; onSuccess?: () => void; }; const Import: React.FC = ({ - onCancel = () => undefined, onSuccess = () => undefined, }) => { const { authIframeClient } = useTurnkey(); const [importIframeStamper, setImportIframeStamper] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); - const [isIframeVisible, setIsIframeVisible] = useState(false); + const [walletName, setWalletName] = useState(""); const TurnkeyImportIframeContainerId = "turnkey-import-iframe-container-id"; const TurnkeyIframeElementId = "turnkey-import-iframe-element-id"; @@ -46,7 +44,7 @@ const Import: React.FC = ({ fontFamily: "monospace", color: "#333", height: "240px", - width: "280px", + width: "240px", borderStyle: "none", backgroundColor: "#ffffff", overflowWrap: "break-word", @@ -76,25 +74,35 @@ const Import: React.FC = ({ } setIsModalOpen(false); - setIsIframeVisible(false); }; const handleImport = async () => { + const whoami = await authIframeClient!.getWhoami(); if (!importIframeStamper) { console.error("IframeStamper is not initialized."); return; } - + const initResult = await authIframeClient!.initImportWallet({ + userId: whoami.userId, + }); + const injected = await importIframeStamper!.injectImportBundle( + initResult.importBundle, + whoami.organizationId, + whoami.userId + ); + if (!injected){ + console.error("error injecting import bundle") + return; + } const encryptedBundle = await importIframeStamper.extractWalletEncryptedBundle(); if (!encryptedBundle || encryptedBundle.trim() === "") { - alert("Failed to retrieve encrypted bundle."); + console.error("failed to retrieve encrypted bundle.") return; } - const whoami = await authIframeClient!.getWhoami(); const response = await authIframeClient?.importWallet({ organizationId: whoami.organizationId, userId: whoami.userId, - walletName: "TEST", + walletName: walletName, encryptedBundle, accounts: [ ...DEFAULT_ETHEREUM_ACCOUNTS, @@ -103,22 +111,19 @@ const Import: React.FC = ({ }); if (response) { - console.log("Wallet imported successfully!"); - setIsIframeVisible(false); + console.log("Wallet imported successfully!");; onSuccess(); } else { - alert("Failed to import wallet! Please try again."); + console.error("Failed to import wallet") } }; return ( <> - {/* Button to open the modal */} - {/* Combined Modal */} = ({ boxShadow: 24, p: 4, borderRadius: 2, - width: "340px", + width: "336px" }} > {/* Close Button */} @@ -151,8 +156,6 @@ const Import: React.FC = ({ ×
- {!isIframeVisible && ( - <> Import Wallet @@ -163,34 +166,66 @@ const Import: React.FC = ({ mb: 2, }} > - Import an existing wallet using your secret recovery phrase. - Ensure that the phrase is entered securely. + Import an existing wallet with your secret recovery phrase. Only + you should know your secret recovery phrase. A secret recovery + phrase can be 12, 15, 18, 21, or 24 words. - - )} - {/* Import Flow */} - {!isIframeVisible ? ( + +
+ + setWalletName(e.target.value)} + fullWidth + style = {{ + "marginTop": "12px", + "marginBottom": "12px" + }} + sx={{ + "& .MuiOutlinedInput-root": { + "& fieldset": { + borderColor: "#D0D5DD", + }, + "&:hover fieldset": { + borderColor: "#8A929E", + }, + "&.Mui-focused fieldset": { + borderColor: "#D0D5DD", + border: "1px solid", + }, + }, + "& .MuiInputBase-input": { + padding: "12px", + }, + backgroundColor: "white", + }} + variant="outlined" + /> + + + - ) : ( -
- )} + From c4c3ce7514bed522496a51c20f199f98ffb06b97 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Thu, 21 Nov 2024 15:11:05 -0500 Subject: [PATCH 39/73] import export styling and sessions --- .../src/app/dashboard/page.tsx | 54 +++++++++------- packages/sdk-browser/src/sdk-client.ts | 2 +- packages/sdk-react/src/assets/caution.svg | 3 + packages/sdk-react/src/assets/eye.svg | 3 + packages/sdk-react/src/assets/unlock.svg | 3 + .../sdk-react/src/components/auth/Auth.tsx | 1 + .../src/components/export/Export.module.css | 40 ++++++++++++ .../src/components/export/Export.tsx | 62 +++++++++++++------ .../src/components/import/Import.module.css | 25 +++++++- .../src/components/import/Import.tsx | 8 +++ 10 files changed, 156 insertions(+), 45 deletions(-) create mode 100644 packages/sdk-react/src/assets/caution.svg create mode 100644 packages/sdk-react/src/assets/eye.svg create mode 100644 packages/sdk-react/src/assets/unlock.svg diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index 885aaffcd..932e96dae 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -17,33 +17,47 @@ import LogoutIcon from "@mui/icons-material/Logout"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import { verifyEthSignature, verifySolSignatureWithAddress } from "../utils"; import { keccak256, toUtf8Bytes } from "ethers"; +import { useRouter } from "next/navigation"; export default function Dashboard() { - const { authIframeClient } = useTurnkey(); + const router = useRouter(); + const { turnkey, getActiveClient, authIframeClient } = useTurnkey(); const [loading, setLoading] = useState(true); + const [iframeClient, setIframeClient] = useState(); const [accounts, setAccounts] = useState([]); const [selectedAccount, setSelectedAccount] = useState(null); const [selectedWallet, setSelectedWallet] = useState(null) const [isModalOpen, setIsModalOpen] = useState(false); const [messageToSign, setMessageToSign] = useState(""); const [signature, setSignature] = useState(null); + const [suborgId, setSuborgId] = useState("") const [verificationResult, setVerificationResult] = useState( null ); - +const handleLogout: any = async () => { + turnkey?.logoutUser() + router.push("/"); +} useEffect(() => { - const fetchWhoami = async () => { + const manageSession = async () => { try { - if (authIframeClient) { - const whoamiResponse = await authIframeClient.getWhoami({ - organizationId: process.env.NEXT_PUBLIC_ORGANIZATION_ID!, - }); - const wallets = await authIframeClient.getWallets({ - organizationId: whoamiResponse.organizationId, + if (turnkey && authIframeClient) { + const session = await turnkey?.getReadWriteSession() + if (!session || Date.now() > session!.sessionExpiry){ + await handleLogout() + } + + const iframeClient = await getActiveClient(); + setIframeClient(iframeClient) + const whoami = await iframeClient?.getWhoami() + const suborgId = whoami?.organizationId + setSuborgId(suborgId!) + const wallets = await iframeClient!.getWallets({ + organizationId: suborgId! }); - const accountsResponse = await authIframeClient.getWalletAccounts({ - organizationId: whoamiResponse.organizationId, + const accountsResponse = await iframeClient!.getWalletAccounts({ + organizationId: suborgId!, walletId: wallets.wallets[0].walletId, }); setAccounts(accountsResponse.accounts); @@ -51,19 +65,16 @@ export default function Dashboard() { setSelectedAccount(accountsResponse.accounts[0].address); } setSelectedWallet(wallets.wallets[0].walletId) - if (authIframeClient && authIframeClient.config) { - authIframeClient.config.organizationId = whoamiResponse.organizationId!; - } } } catch (error) { - console.error("Error fetching Whoami:", error); + console.error(error); } finally { setLoading(false); } }; - fetchWhoami(); - }, [authIframeClient]); + manageSession(); + }, [authIframeClient, turnkey]); const handleAccountSelect = (event: React.ChangeEvent) => { setSelectedAccount(event.target.value); // Save the full address (untruncated) @@ -93,7 +104,8 @@ export default function Dashboard() { ? keccak256(toUtf8Bytes(message)) // Ethereum requires keccak256 hash : Buffer.from(message, "utf8").toString("hex"); // Solana doesn't require hashing - const resp = await authIframeClient?.signRawPayload({ + const resp = await iframeClient?.signRawPayload({ + organizationId: suborgId!, signWith: selectedAccount!, payload: hashedMessage, encoding: "PAYLOAD_ENCODING_HEXADECIMAL", @@ -214,12 +226,12 @@ export default function Dashboard() {
- - + +
-
+
Log out
diff --git a/packages/sdk-browser/src/sdk-client.ts b/packages/sdk-browser/src/sdk-client.ts index e94ad9320..a62156120 100644 --- a/packages/sdk-browser/src/sdk-client.ts +++ b/packages/sdk-browser/src/sdk-client.ts @@ -554,4 +554,4 @@ export class TurnkeyWalletClient extends TurnkeyBrowserClient { getWalletInterface(): WalletInterface { return this.wallet; } -} +} \ No newline at end of file diff --git a/packages/sdk-react/src/assets/caution.svg b/packages/sdk-react/src/assets/caution.svg new file mode 100644 index 000000000..fdcbafecb --- /dev/null +++ b/packages/sdk-react/src/assets/caution.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/sdk-react/src/assets/eye.svg b/packages/sdk-react/src/assets/eye.svg new file mode 100644 index 000000000..d46144b1b --- /dev/null +++ b/packages/sdk-react/src/assets/eye.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/sdk-react/src/assets/unlock.svg b/packages/sdk-react/src/assets/unlock.svg new file mode 100644 index 000000000..c92e3b410 --- /dev/null +++ b/packages/sdk-react/src/assets/unlock.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 7862db945..ac8711853 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -123,6 +123,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde const handleAuthSuccess = async (credentialBundle: any) => { if (credentialBundle) { await authIframeClient!.injectCredentialBundle(credentialBundle); + await authIframeClient!.loginWithAuthBundle(credentialBundle) await onHandleAuthSuccess(); } }; diff --git a/packages/sdk-react/src/components/export/Export.module.css b/packages/sdk-react/src/components/export/Export.module.css index 4646ed3a2..f30962fc2 100644 --- a/packages/sdk-react/src/components/export/Export.module.css +++ b/packages/sdk-react/src/components/export/Export.module.css @@ -52,3 +52,43 @@ .exportButton:hover { background-color: var(--Greyscale-700, #4a4f55); } + + .rowsContainer { + font-family: Inter; + display: flex; + flex-direction: column; + justify-content: center; + gap: 16px; + margin-top: 16px; + width: 95%; + margin-right: auto; + margin-bottom: 32px; + } + + .row { + display: flex; + align-items: center; + } + + .rowIcon { + padding-right: 8px; + } + + .poweredBy { + font-family: Inter; + display: flex; + align-items: center; + justify-content: center; + gap: 2px; + font-size: 12px; + font-weight: 400; + line-height: 16px; + letter-spacing: -0.01em; + color: var(--Greyscale-500, #868c95); + margin-top: 16px; + cursor: pointer; + } + + .poweredBy span { + position: relative; + } \ No newline at end of file diff --git a/packages/sdk-react/src/components/export/Export.tsx b/packages/sdk-react/src/components/export/Export.tsx index 5bcb82593..767b9804a 100644 --- a/packages/sdk-react/src/components/export/Export.tsx +++ b/packages/sdk-react/src/components/export/Export.tsx @@ -3,9 +3,13 @@ import { Modal, Box, Typography } from "@mui/material"; import { useTurnkey } from "../../hooks/useTurnkey"; import { IframeStamper } from "@turnkey/sdk-browser"; import styles from "./Export.module.css"; +import unlockIcon from "assets/unlock.svg"; +import eyeIcon from "assets/eye.svg"; +import cautionIcon from "assets/caution.svg"; +import turnkeyIcon from "assets/turnkey.svg"; type ExportProps = { - walletId?: string; + walletId: string; }; const Export: React.FC = ({ @@ -86,11 +90,12 @@ const Export: React.FC = ({ }; const exportWallet = async () => { + const whoami = await authIframeClient!.getWhoami(); const exportResponse = await authIframeClient?.exportWallet({ + organizationId: whoami.organizationId, walletId: walletId!, targetPublicKey: exportIframeStamper!.iframePublicKey!, }); - const whoami = await authIframeClient!.getWhoami(); if (exportResponse?.exportBundle) { await exportIframeStamper?.injectWalletExportBundle( exportResponse.exportBundle, @@ -137,34 +142,44 @@ const Export: React.FC = ({ ×
-{!isIframeVisible && -<> Export wallet - - Exporting your seed phrase poses significant security risks. Ensure it is stored securely and never shared with anyone. Anyone with access to your seed phrase can gain full control over your wallet and funds. Proceed with caution. - - + + {!isIframeVisible ? +
+
+ + Keep your seed phrase private. +
+
+ + Anyone who has your seed phrase can access your wallet. +
+
+ + Make sure nobody can see your screen when viewing your seed phrase +
+
: + + +Your seed phrase is the key to your wallet. Save it in a secure location. + + } {!isIframeVisible && ( <> -
- - Are you sure you want to export? - -
)} @@ -180,6 +195,13 @@ const Export: React.FC = ({ padding: "16px", }} /> +
(window.location.href = "https://www.turnkey.com/")} + className={styles.poweredBy} + > + Secured by + +
diff --git a/packages/sdk-react/src/components/import/Import.module.css b/packages/sdk-react/src/components/import/Import.module.css index 704e0d4c7..b6e8b3b00 100644 --- a/packages/sdk-react/src/components/import/Import.module.css +++ b/packages/sdk-react/src/components/import/Import.module.css @@ -30,7 +30,7 @@ margin-bottom: 16px; } - .importBUtton { + .importButton { padding: 10px 16px 10px 16px; gap: 8px; color: #ffffff; @@ -42,13 +42,32 @@ cursor: pointer; } - .importBUtton:disabled { + .importButton:disabled { color: var(--Greyscale-700, #a2a7ae); background: #ffffff; border-color: var(--Greyscale-400, #a2a7ae); cursor: not-allowed; } -.importBUtton:hover { +.importButton:hover { background-color: var(--Greyscale-700, #4a4f55); } + + .poweredBy { + font-family: Inter; + display: flex; + align-items: center; + justify-content: center; + gap: 2px; + font-size: 12px; + font-weight: 400; + line-height: 16px; + letter-spacing: -0.01em; + color: var(--Greyscale-500, #868c95); + margin-top: 16px; + cursor: pointer; + } + + .poweredBy span { + position: relative; + } diff --git a/packages/sdk-react/src/components/import/Import.tsx b/packages/sdk-react/src/components/import/Import.tsx index 16885a84e..a31000f08 100644 --- a/packages/sdk-react/src/components/import/Import.tsx +++ b/packages/sdk-react/src/components/import/Import.tsx @@ -3,6 +3,7 @@ import { Modal, Box, Typography, TextField } from "@mui/material"; import { useTurnkey } from "../../hooks/useTurnkey"; import { DEFAULT_ETHEREUM_ACCOUNTS, DEFAULT_SOLANA_ACCOUNTS, IframeStamper } from "@turnkey/sdk-browser"; import styles from "./Import.module.css"; +import turnkeyIcon from "assets/turnkey.svg"; type ImportProps = { onSuccess?: () => void; @@ -226,6 +227,13 @@ const Import: React.FC = ({ Import +
(window.location.href = "https://www.turnkey.com/")} + className={styles.poweredBy} + > + Secured by + +
From 1fe265acabf94aaaf1533c17c35cd9fa6bd3c740 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Thu, 21 Nov 2024 16:11:27 -0500 Subject: [PATCH 40/73] more styling updates to passkey screen, export and import --- .../src/app/dashboard/page.tsx | 9 ++-- packages/sdk-react/src/assets/export.svg | 3 ++ packages/sdk-react/src/assets/import.svg | 3 ++ packages/sdk-react/src/assets/lockshield.svg | 4 ++ packages/sdk-react/src/assets/passkey.svg | 1 + .../sdk-react/src/assets/shieldfinger.svg | 16 ++++++ packages/sdk-react/src/assets/timer.svg | 3 ++ .../src/components/auth/Auth.module.css | 16 ++---- .../sdk-react/src/components/auth/Auth.tsx | 50 ++++++------------- .../src/components/export/Export.module.css | 26 ++++++---- .../src/components/export/Export.tsx | 3 +- .../src/components/import/Import.module.css | 26 ++++++---- .../src/components/import/Import.tsx | 6 ++- 13 files changed, 89 insertions(+), 77 deletions(-) create mode 100644 packages/sdk-react/src/assets/export.svg create mode 100644 packages/sdk-react/src/assets/import.svg create mode 100644 packages/sdk-react/src/assets/lockshield.svg create mode 100644 packages/sdk-react/src/assets/passkey.svg create mode 100644 packages/sdk-react/src/assets/shieldfinger.svg create mode 100644 packages/sdk-react/src/assets/timer.svg diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index 932e96dae..65868c94e 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -143,7 +143,10 @@ const handleLogout: any = async () => { setVerificationResult( verificationPassed - ? `Signed with: ${selectedAccount}` + ? `Signed with: ${`${selectedAccount!.slice( + 0, + 5 + )}...${selectedAccount!.slice(-5)}`}` : "Verification failed" ); }; @@ -226,8 +229,8 @@ const handleLogout: any = async () => {
- - + +
diff --git a/packages/sdk-react/src/assets/export.svg b/packages/sdk-react/src/assets/export.svg new file mode 100644 index 000000000..33f0d7936 --- /dev/null +++ b/packages/sdk-react/src/assets/export.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/sdk-react/src/assets/import.svg b/packages/sdk-react/src/assets/import.svg new file mode 100644 index 000000000..17efdebb9 --- /dev/null +++ b/packages/sdk-react/src/assets/import.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/sdk-react/src/assets/lockshield.svg b/packages/sdk-react/src/assets/lockshield.svg new file mode 100644 index 000000000..2885aa86e --- /dev/null +++ b/packages/sdk-react/src/assets/lockshield.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/sdk-react/src/assets/passkey.svg b/packages/sdk-react/src/assets/passkey.svg new file mode 100644 index 000000000..e993c4119 --- /dev/null +++ b/packages/sdk-react/src/assets/passkey.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/sdk-react/src/assets/shieldfinger.svg b/packages/sdk-react/src/assets/shieldfinger.svg new file mode 100644 index 000000000..1ada4f554 --- /dev/null +++ b/packages/sdk-react/src/assets/shieldfinger.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/packages/sdk-react/src/assets/timer.svg b/packages/sdk-react/src/assets/timer.svg new file mode 100644 index 000000000..302f1fd6d --- /dev/null +++ b/packages/sdk-react/src/assets/timer.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index c36a84830..8401283fc 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -82,7 +82,7 @@ margin-bottom: 12px; } -button { +.authButton { padding: 10px 16px 10px 16px; gap: 8px; color: var(--Greyscale-900, #2b2f33); @@ -99,13 +99,11 @@ button { transition: background-color 0.2s ease; } -button:hover { +.authButton:hover { background-color: #F4E8FF; } - - -button:disabled { +.authButton:disabled { color: var(--Greyscale-700, #a2a7ae); background: #ffffff; border-color: var(--Greyscale-100, #f5f7fb); @@ -192,14 +190,6 @@ button:disabled { text-decoration: none; } - -.authButton { - display: flex; - justify-content: center; - width: 100%; - margin-bottom: 12px; -} - .socialButtonContainer { width: 100%; display: flex; diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index ac8711853..90927aba8 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -21,13 +21,7 @@ import facebookIcon from "assets/facebook.svg"; import appleIcon from "assets/apple.svg"; import emailIcon from "assets/email.svg"; import smsIcon from "assets/sms.svg"; -import faceidIcon from "assets/faceid.svg"; -import fingerprintIcon from "assets/fingerprint.svg"; -import redcircleIcon from "assets/redcircle.svg" -import fingerprintredIcon from "assets/fingerprintred.svg" -import checkboxIcon from "assets/checkbox.svg"; -import clockIcon from "assets/clock.svg"; -import keyholeIcon from "assets/keyhole.svg"; +import passkeyIcon from "assets/passkey.svg"; import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; interface AuthProps { @@ -161,7 +155,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde }); setPasskeyCreated(true) } else { - setPasskeySignupError("Failed to create user passkey. Please try again") + setPasskeySignupError("Passkey not created. Please try again.") } } @@ -339,6 +333,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde />
= ({ onHandleAuthSuccess, authConfig, configOrde setPhone(value)} value={phone} />
) : passkeyCreationScreen ?
{renderBackButton()}
+ { !passkeySignupError &&
- { !passkeySignupError ? - <> - - : -<> - - - + +
} -

{passkeySignupError ? "Something went wrong" : passkeyCreated ? "Logging in with passkey" : "Creating passkey"}

@@ -452,7 +430,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde
{ passkeySignupError && - } diff --git a/packages/sdk-react/src/components/export/Export.module.css b/packages/sdk-react/src/components/export/Export.module.css index f30962fc2..ebb7bb322 100644 --- a/packages/sdk-react/src/components/export/Export.module.css +++ b/packages/sdk-react/src/components/export/Export.module.css @@ -30,27 +30,31 @@ margin-bottom: 16px; } - .exportButton { - padding: 10px 16px 10px 16px; + .exportButton { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 10px 16px; gap: 8px; - color: #ffffff; + color: var(--Greyscale-900, #2b2f33); width: 100%; font-size: 1rem; - background: var(--Greyscale-900, #2b2f33); - border: 1px solid var(--Greyscale-800, #3f464b); + background: #ffffff; + border: 1px solid var(--Greyscale-400, #a2a7ae); border-radius: 8px; cursor: pointer; + transition: background-color 0.3s ease; + } + + .exportButton:hover { + background-color: #F4E8FF; } .exportButton:disabled { color: var(--Greyscale-700, #a2a7ae); background: #ffffff; - border-color: var(--Greyscale-400, #a2a7ae); - cursor: not-allowed; - } - -.exportButton:hover { - background-color: var(--Greyscale-700, #4a4f55); + border-color: var(--Greyscale-100, #f5f7fb); + cursor: default; } .rowsContainer { diff --git a/packages/sdk-react/src/components/export/Export.tsx b/packages/sdk-react/src/components/export/Export.tsx index 767b9804a..02cb972f8 100644 --- a/packages/sdk-react/src/components/export/Export.tsx +++ b/packages/sdk-react/src/components/export/Export.tsx @@ -7,7 +7,7 @@ import unlockIcon from "assets/unlock.svg"; import eyeIcon from "assets/eye.svg"; import cautionIcon from "assets/caution.svg"; import turnkeyIcon from "assets/turnkey.svg"; - +import exportIcon from "assets/export.svg" type ExportProps = { walletId: string; }; @@ -108,6 +108,7 @@ const Export: React.FC = ({ <> diff --git a/packages/sdk-react/src/components/import/Import.module.css b/packages/sdk-react/src/components/import/Import.module.css index b6e8b3b00..a8400348a 100644 --- a/packages/sdk-react/src/components/import/Import.module.css +++ b/packages/sdk-react/src/components/import/Import.module.css @@ -30,27 +30,31 @@ margin-bottom: 16px; } - .importButton { - padding: 10px 16px 10px 16px; + .importButton { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 10px 16px; gap: 8px; - color: #ffffff; + color: var(--Greyscale-900, #2b2f33); width: 100%; font-size: 1rem; - background: var(--Greyscale-900, #2b2f33); - border: 1px solid var(--Greyscale-800, #3f464b); + background: #ffffff; + border: 1px solid var(--Greyscale-400, #a2a7ae); border-radius: 8px; cursor: pointer; + transition: background-color 0.3s ease; + } + + .importButton:hover { + background-color: #F4E8FF; } .importButton:disabled { color: var(--Greyscale-700, #a2a7ae); background: #ffffff; - border-color: var(--Greyscale-400, #a2a7ae); - cursor: not-allowed; - } - -.importButton:hover { - background-color: var(--Greyscale-700, #4a4f55); + border-color: var(--Greyscale-100, #f5f7fb); + cursor: default; } .poweredBy { diff --git a/packages/sdk-react/src/components/import/Import.tsx b/packages/sdk-react/src/components/import/Import.tsx index a31000f08..841f71b85 100644 --- a/packages/sdk-react/src/components/import/Import.tsx +++ b/packages/sdk-react/src/components/import/Import.tsx @@ -4,7 +4,7 @@ import { useTurnkey } from "../../hooks/useTurnkey"; import { DEFAULT_ETHEREUM_ACCOUNTS, DEFAULT_SOLANA_ACCOUNTS, IframeStamper } from "@turnkey/sdk-browser"; import styles from "./Import.module.css"; import turnkeyIcon from "assets/turnkey.svg"; - +import importIcon from "assets/import.svg" type ImportProps = { onSuccess?: () => void; }; @@ -113,6 +113,7 @@ const Import: React.FC = ({ if (response) { console.log("Wallet imported successfully!");; + handleCloseModal() onSuccess(); } else { console.error("Failed to import wallet") @@ -122,7 +123,8 @@ const Import: React.FC = ({ return ( <> From 225e866e8d5b3a6e32fe95c97581ffc6b42b164b Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Thu, 21 Nov 2024 19:25:23 -0500 Subject: [PATCH 41/73] login methods styling and wallet selection styling and functionality --- examples/react-components/package.json | 1 + examples/react-components/public/key.svg | 10 + examples/react-components/public/mail.svg | 10 + examples/react-components/public/phone.svg | 10 + .../src/app/dashboard/dashboard.css | 56 ++- .../src/app/dashboard/page.tsx | 327 ++++++++++++++++-- .../sdk-react/src/actions/createSuborg.ts | 2 +- .../src/components/import/Import.tsx | 1 + pnpm-lock.yaml | 7 +- 9 files changed, 385 insertions(+), 39 deletions(-) create mode 100644 examples/react-components/public/key.svg create mode 100644 examples/react-components/public/mail.svg create mode 100644 examples/react-components/public/phone.svg diff --git a/examples/react-components/package.json b/examples/react-components/package.json index 29ecf32b8..51fae1bed 100644 --- a/examples/react-components/package.json +++ b/examples/react-components/package.json @@ -16,6 +16,7 @@ "@mui/icons-material": "^6.1.5", "@mui/material": "^6.1.5", "@solana/web3.js": "^1.95.4", + "@turnkey/sdk-browser": "^1.10.0", "@turnkey/sdk-react": "workspace:*", "@turnkey/sdk-server": "workspace:*", "@types/node": "20.3.1", diff --git a/examples/react-components/public/key.svg b/examples/react-components/public/key.svg new file mode 100644 index 000000000..7925349be --- /dev/null +++ b/examples/react-components/public/key.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/react-components/public/mail.svg b/examples/react-components/public/mail.svg new file mode 100644 index 000000000..30601754d --- /dev/null +++ b/examples/react-components/public/mail.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/react-components/public/phone.svg b/examples/react-components/public/phone.svg new file mode 100644 index 000000000..2ac948c0c --- /dev/null +++ b/examples/react-components/public/phone.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/react-components/src/app/dashboard/dashboard.css b/examples/react-components/src/app/dashboard/dashboard.css index ae280834e..1fb6f0911 100644 --- a/examples/react-components/src/app/dashboard/dashboard.css +++ b/examples/react-components/src/app/dashboard/dashboard.css @@ -24,16 +24,17 @@ padding: 6rem; gap: 60px; position: relative; + background-image: url("../../../public/grid.svg"); } - .authConfigContainer { + .dashboardContainer { position: absolute; left: 0; display: flex; justify-content: flex-start; } - .authConfigCard { + .dashboardCard { background: var(--Greyscale-20, #f5f7fb); padding: 8px 24px; border-radius: 8px; @@ -46,6 +47,17 @@ flex-direction: column; justify-content: flex-start; } + + .socialsTitle { + font-size: 20px; + font-weight: 700; + line-height: 26px; + letter-spacing: -0.01em; + text-align: left; + margin-top: 16px; + } + + .configTitle { font-size: 32px; font-weight: 700; @@ -71,12 +83,45 @@ border-bottom: 1px solid var(--Greyscale-200, #ebedf2); } + .loginMethodContainer { + display: flex; + flex-direction: column; + gap: 12px; + } + + .loginMethodRow { + background-color: #ffffff; + border-radius: 8px; + align-items: center; + display: flex; + justify-content: space-between; + padding: 12px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + .labelContainer { display: flex; align-items: center; gap: 8px; } + .loginMethodRow { + background-color: #ffffff; + border-radius: 8px; + align-items: center; + display: flex; + justify-content: space-between; + padding: 12px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + .loginMethodDetails { + font-size: 14px; + line-height: 20px; + color: #868c95; + margin-left: 32px; + } + .accountRow { background-color: #ffffff; border-radius: 8px; @@ -143,8 +188,8 @@ } .iconSmall { - width: 18px; - height: 18px; + width: 24px; + height: 24px; } .socialContainer { @@ -229,4 +274,5 @@ button:hover { justify-content: center; gap: 8px; cursor: pointer; - } \ No newline at end of file + } + diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index 65868c94e..ee8addf04 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -12,12 +12,21 @@ import { Modal, Box, TextField, + FormControl, + InputLabel, + Select, + MenuItem, + Menu, } from "@mui/material"; import LogoutIcon from "@mui/icons-material/Logout"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import { verifyEthSignature, verifySolSignatureWithAddress } from "../utils"; import { keccak256, toUtf8Bytes } from "ethers"; import { useRouter } from "next/navigation"; +import {TurnkeyPasskeyClient} from "@turnkey/sdk-browser" +import AddCircleIcon from '@mui/icons-material/AddCircle'; +import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; export default function Dashboard() { const router = useRouter(); @@ -25,57 +34,93 @@ export default function Dashboard() { const [loading, setLoading] = useState(true); const [iframeClient, setIframeClient] = useState(); const [accounts, setAccounts] = useState([]); + const [wallets, setWallets] = useState([]); const [selectedAccount, setSelectedAccount] = useState(null); const [selectedWallet, setSelectedWallet] = useState(null) const [isModalOpen, setIsModalOpen] = useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [messageToSign, setMessageToSign] = useState(""); const [signature, setSignature] = useState(null); const [suborgId, setSuborgId] = useState("") + const [user, setUser] = useState("") const [verificationResult, setVerificationResult] = useState( null ); + const [anchorEl, setAnchorEl] = useState(null); + + const handleDropdownClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleDropdownClose = () => { + setAnchorEl(null); + }; + + const handleDeleteAccount: any = async () => { + authIframeClient?.deleteSubOrganization({organizationId: suborgId, deleteWithoutExport: true}) + await handleLogout() + } const handleLogout: any = async () => { turnkey?.logoutUser() router.push("/"); } - useEffect(() => { - const manageSession = async () => { - try { - if (turnkey && authIframeClient) { - const session = await turnkey?.getReadWriteSession() - if (!session || Date.now() > session!.sessionExpiry){ - await handleLogout() - } +useEffect(() => { + const manageSession = async () => { + try { + if (turnkey && authIframeClient) { + const session = await turnkey?.getReadWriteSession(); + if (!session || Date.now() > session!.sessionExpiry) { + await handleLogout(); + } + + const iframeClient = await getActiveClient(); + setIframeClient(iframeClient); + + const whoami = await iframeClient?.getWhoami(); + const suborgId = whoami?.organizationId; + setSuborgId(suborgId!) + + const userResponse = await iframeClient!.getUser({organizationId: suborgId!, userId: whoami?.userId!}) + setUser(userResponse.user) + const walletsResponse = await iframeClient!.getWallets({ + organizationId: suborgId!, + }); + setWallets(walletsResponse.wallets); + + // Default to the first wallet if available + if (walletsResponse.wallets.length > 0) { + const defaultWalletId = walletsResponse.wallets[0].walletId; + setSelectedWallet(defaultWalletId); - const iframeClient = await getActiveClient(); - setIframeClient(iframeClient) - const whoami = await iframeClient?.getWhoami() - const suborgId = whoami?.organizationId - setSuborgId(suborgId!) - const wallets = await iframeClient!.getWallets({ - organizationId: suborgId! - }); const accountsResponse = await iframeClient!.getWalletAccounts({ organizationId: suborgId!, - walletId: wallets.wallets[0].walletId, + walletId: defaultWalletId, }); setAccounts(accountsResponse.accounts); if (accountsResponse.accounts.length > 0) { setSelectedAccount(accountsResponse.accounts[0].address); } - setSelectedWallet(wallets.wallets[0].walletId) } - } catch (error) { - console.error(error); - } finally { - setLoading(false); } - }; + } catch (error) { + console.error(error); + } finally { + setLoading(false); + } + }; - manageSession(); - }, [authIframeClient, turnkey]); + manageSession(); +}, [authIframeClient, turnkey]); + + const getWallets = async () => { + const walletsResponse = await iframeClient!.getWallets({ + organizationId: suborgId!, + }); + setWallets(walletsResponse.wallets); + + } const handleAccountSelect = (event: React.ChangeEvent) => { setSelectedAccount(event.target.value); // Save the full address (untruncated) }; @@ -95,6 +140,24 @@ const handleLogout: any = async () => { setVerificationResult(null); }; + + const handleWalletSelect = async (walletId: string) => { + setSelectedWallet(walletId); + setAnchorEl(null); // Close the dropdown + + // Fetch accounts for the selected wallet + const accountsResponse = await iframeClient!.getWalletAccounts({ + organizationId: suborgId!, + walletId, + }); + setAccounts(accountsResponse.accounts); + if (accountsResponse.accounts.length > 0) { + setSelectedAccount(accountsResponse.accounts[0].address); + } else { + setSelectedAccount(null); // Clear selected account if no accounts found + } + }; + const handleSign = async () => { try { const addressType = selectedAccount?.startsWith("0x") ? "ETH" : "SOL"; @@ -153,14 +216,146 @@ const handleLogout: any = async () => { return (
-
+
Login Methods +
+
+
+ + Email + {user && user.userEmail && ( + {user.userEmail} + )} +
+ {user && user.userEmail ? ( + + ) : ( + + )} +
+ + +
+
+ + Phone + {user && user.userPhoneNumber && ( + {user.userPhoneNumber} + )} +
+ {user && user.userPhoneNumber ? ( + + ) : ( + + )} +
+ +
+
+ + Passkey + {user && user.authenticators && user.authenticators.length > 0 && ( + {user.authenticators[0].credentialId} + )} +
+ {user && user.authenticators && user.authenticators.length > 0 ? ( + + ) : ( + + )} +
+ + + Socials + +
+
+ + Google +
+ {user && user.oauthProviders && user.oauthProviders.some( + (provider: { providerName: string; }) => provider.providerName === "Google" +) ? ( + + ) : ( + + )} +
+
+
+ + Apple +
+ {user && user.oauthProviders && user.oauthProviders.some( + (provider: { providerName: string; }) => provider.providerName === "Apple" +) ? ( + + ) : ( + + )} +
+
+
+ + Facebook +
+ {user && user.oauthProviders && user.oauthProviders.some( + (provider: { providerName: string; }) => provider.providerName === "Facebook" +) ? ( + + ) : ( + + )} +
+
-
-
-

Wallets

+
+
+
+ + {wallets.find((wallet) => wallet.walletId === selectedWallet)?.walletName || + "Select Wallet"} + + +
+ + + {wallets.map((wallet) => ( + handleWalletSelect(wallet.walletId)} + > + {wallet.walletName || wallet.walletId} + + ))} +
{accounts.map((account: any, index: number) => ( @@ -230,7 +425,7 @@ const handleLogout: any = async () => {
- +
@@ -241,7 +436,7 @@ const handleLogout: any = async () => {
-
+
setIsDeleteModalOpen(true)}className="authFooterButton"> Delete account
@@ -249,9 +444,79 @@ const handleLogout: any = async () => {
+ + +
setIsDeleteModalOpen(false)} + style={{ + position: "absolute", + top: "16px", + right: "16px", + background: "none", + border: "none", + fontSize: "20px", + fontWeight: "bold", + cursor: "pointer", + color: "#6C727E", + }} + > + × +
+ + Are you sure you would like to delete your account? + + + If there are any funds on your wallet, please ensure you have exported your seed phrase before deleting your account. This action cannot be undone. + +
+ +
+
+
+ + = ({ return; } const initResult = await authIframeClient!.initImportWallet({ + organizationId: whoami.organizationId, userId: whoami.userId, }); const injected = await importIframeStamper!.injectImportBundle( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5acd6bbfd..874e6531d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -490,6 +490,9 @@ importers: '@solana/web3.js': specifier: ^1.95.4 version: 1.95.4(encoding@0.1.13) + '@turnkey/sdk-browser': + specifier: ^1.10.0 + version: link:../../packages/sdk-browser '@turnkey/sdk-react': specifier: workspace:* version: link:../../packages/sdk-react @@ -7224,10 +7227,10 @@ packages: dependencies: '@babel/runtime': 7.26.0 '@emotion/cache': 11.13.1 - '@emotion/react': 11.13.3(@types/react@18.2.75)(react@18.2.0) + '@emotion/react': 11.13.3(@types/react@18.2.14)(react@18.2.0) '@emotion/serialize': 1.3.2 '@emotion/sheet': 1.4.0 - '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.75)(react@18.2.0) + '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.2.14)(react@18.2.0) csstype: 3.1.3 prop-types: 15.8.1 react: 18.2.0 From 5127aa8dd1a5fcbe6656fd55216c9a38b06226ff Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Tue, 26 Nov 2024 15:33:00 -0500 Subject: [PATCH 42/73] first batch of changes requested --- .../src/app/dashboard/page.tsx | 2 +- .../sdk-react/src/actions/createSuborg.ts | 2 +- .../sdk-react/src/components/auth/Apple.tsx | 84 +++++++++---- .../src/components/auth/Auth.module.css | 2 +- .../sdk-react/src/components/auth/Auth.tsx | 17 +-- .../src/components/auth/Facebook.tsx | 8 +- .../sdk-react/src/components/auth/Google.tsx | 11 +- .../src/components/auth/PhoneInput.tsx | 115 ++++++++++-------- 8 files changed, 150 insertions(+), 91 deletions(-) diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index ee8addf04..ea007d1f5 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -218,7 +218,7 @@ useEffect(() => {
- Login Methods + Login methods
diff --git a/packages/sdk-react/src/actions/createSuborg.ts b/packages/sdk-react/src/actions/createSuborg.ts index 1745f2c10..c49e5b5c0 100644 --- a/packages/sdk-react/src/actions/createSuborg.ts +++ b/packages/sdk-react/src/actions/createSuborg.ts @@ -51,7 +51,7 @@ export async function createSuborg( }, ], wallet: { - walletName:`wallet-${String(Date.now())}` , + walletName:`Wallet 1` , accounts: [ ...DEFAULT_ETHEREUM_ACCOUNTS, ...DEFAULT_SOLANA_ACCOUNTS, diff --git a/packages/sdk-react/src/components/auth/Apple.tsx b/packages/sdk-react/src/components/auth/Apple.tsx index 7417cba42..dce4862d4 100644 --- a/packages/sdk-react/src/components/auth/Apple.tsx +++ b/packages/sdk-react/src/components/auth/Apple.tsx @@ -1,9 +1,9 @@ import { useEffect, useState } from "react"; import { sha256 } from "@noble/hashes/sha2"; import { bytesToHex } from "@noble/hashes/utils"; -import AppleLogin from "react-apple-login"; import styles from "./Socials.module.css"; import appleIcon from "assets/apple.svg"; + interface AppleAuthButtonProps { iframePublicKey: string; clientId: string; @@ -23,6 +23,7 @@ const AppleAuthButton: React.FC { const [appleSDKLoaded, setAppleSDKLoaded] = useState(false); const redirectURI = process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI!; + useEffect(() => { const loadAppleSDK = () => { const script = document.createElement("script"); @@ -40,35 +41,68 @@ const AppleAuthButton: React.FC { + const nonce = bytesToHex(sha256(iframePublicKey)); + const appleAuthUrl = new URL("https://appleid.apple.com/auth/authorize"); + appleAuthUrl.searchParams.set("client_id", clientId); + appleAuthUrl.searchParams.set("redirect_uri", redirectURI); + appleAuthUrl.searchParams.set("response_type", "code id_token"); + appleAuthUrl.searchParams.set("response_mode", "fragment"); + appleAuthUrl.searchParams.set("nonce", nonce); + + // Calculate popup dimensions and position for centering + const width = 500; + const height = 600; + const left = window.screenX + (window.innerWidth - width) / 2; + const top = window.screenY + (window.innerHeight - height) / 2; + + // Open the Apple login popup + const authWindow = window.open( + appleAuthUrl.toString(), + "_blank", + `width=${width},height=${height},top=${top},left=${left},scrollbars=yes,resizable=yes` + ); + + if (!authWindow) { + console.error("Failed to open Apple login window."); + return; + } + + // Monitor the popup for redirect and extract tokens + const interval = setInterval(() => { + try { + const url = authWindow?.location.href || ""; + if (url.startsWith(window.location.origin)) { + const hashParams = new URLSearchParams(url.split("#")[1]); + const idToken = hashParams.get("id_token"); + if (idToken) { + authWindow?.close(); + clearInterval(interval); + onSuccess({ idToken }); + } + } + } catch (error) { + // Ignore cross-origin errors until redirected + } + + if (authWindow?.closed) { + clearInterval(interval); + } + }, 500); + }; + if (!appleSDKLoaded) { return null; } return ( - ( -
- - {layout === "stacked" && Continue with Apple} -
- )} - callback={(response) => { - if (response.error) { - console.error("Apple login error:", response.error); - } else { - onSuccess(response); - } - }} - usePopup - /> +
+ + {layout === "stacked" && Continue with Apple} +
); }; diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index 8401283fc..fa6e95740 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -171,7 +171,7 @@ .resendCode { font-size: 12px; - font-weight: 400; + line-height: 16px; letter-spacing: -0.01em; text-align: center; color: var(--Greyscale-500, #868c95); diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 90927aba8..8e0b8e6ab 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -165,11 +165,11 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde }); if (sessionResponse?.credentialBundle) { await handleAuthSuccess(sessionResponse.credentialBundle); + setPasskeyCreationScreen(false) + setPasskeySignupError("") } else { setPasskeySignupError("Failed to login with passkey. Please try again") } - setPasskeyCreationScreen(false) - setPasskeySignupError("") } catch { setPasskeySignupError("Passkey request timed out or rejected by user. Please try again") @@ -281,7 +281,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde clientId={process.env.NEXT_PUBLIC_APPLE_CLIENT_ID!} iframePublicKey={authIframeClient!.iframePublicKey!} onSuccess={(response: any) => - handleOAuthLogin(response.authorization.id_token, "Apple") + handleOAuthLogin(response.idToken, "Apple") } /> )} @@ -399,7 +399,9 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde

Create a Passkey

- Set up with fingerprint, face, or screen lock. Make your logins secure and easy. +
+ Make your logins secure and easy. Set up with fingerprint, face, or screen lock. +
- Powered by + Secured by
@@ -493,7 +495,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde
- Enter the 6-digit code we sent to{" "} + Enter the 6-digit code we {step === "otpEmail" ? "emailed" : "texted"} to{" "}
{step === "otpEmail" ? email @@ -533,6 +535,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde > Privacy Policy + {"."}
) : ( diff --git a/packages/sdk-react/src/components/auth/Facebook.tsx b/packages/sdk-react/src/components/auth/Facebook.tsx index ea125c0c7..d380a7da3 100644 --- a/packages/sdk-react/src/components/auth/Facebook.tsx +++ b/packages/sdk-react/src/components/auth/Facebook.tsx @@ -38,10 +38,16 @@ const FacebookAuthButton: React.FC = ({ position="start" style={{ marginRight: "2px", marginLeft: "-8px" }} > - + + ), }} From 40c8260f1ab08193d323c4de1784cf89d30cf36a Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Tue, 26 Nov 2024 19:43:46 -0500 Subject: [PATCH 43/73] sign message modal update, export import update, update icons --- .../react-components/public/eth-hover.svg | 4 + examples/react-components/public/key.svg | 11 +- .../react-components/public/solana-hover.svg | 6 + .../src/app/dashboard/dashboard.css | 170 +++++++++++++- .../src/app/dashboard/page.tsx | 207 +++++++++--------- .../src/components/export/Export.module.css | 5 + .../src/components/export/Export.tsx | 12 + .../src/components/import/Import.tsx | 7 +- 8 files changed, 302 insertions(+), 120 deletions(-) create mode 100644 examples/react-components/public/eth-hover.svg create mode 100644 examples/react-components/public/solana-hover.svg diff --git a/examples/react-components/public/eth-hover.svg b/examples/react-components/public/eth-hover.svg new file mode 100644 index 000000000..6c831b0c9 --- /dev/null +++ b/examples/react-components/public/eth-hover.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/react-components/public/key.svg b/examples/react-components/public/key.svg index 7925349be..e993c4119 100644 --- a/examples/react-components/public/key.svg +++ b/examples/react-components/public/key.svg @@ -1,10 +1 @@ - - - - - - - - - - + \ No newline at end of file diff --git a/examples/react-components/public/solana-hover.svg b/examples/react-components/public/solana-hover.svg new file mode 100644 index 000000000..bca516ef0 --- /dev/null +++ b/examples/react-components/public/solana-hover.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/react-components/src/app/dashboard/dashboard.css b/examples/react-components/src/app/dashboard/dashboard.css index 1fb6f0911..d0d542388 100644 --- a/examples/react-components/src/app/dashboard/dashboard.css +++ b/examples/react-components/src/app/dashboard/dashboard.css @@ -133,10 +133,16 @@ } .accountAddress { - flex: 1; - margin-left: 8px; + margin-left: 4px; font-family: monospace; font-size: 16px; + display: flex; + align-items: center; + gap: 4px; + transition: color 0.3s ease; + } + .accountAddress:hover { + color: #8a8a8a;; } .radioButton { @@ -220,6 +226,7 @@ border: 1px solid var(--Greyscale-800, #3f464b); border-radius: 8px; cursor: pointer; + transition: opacity 0.3s ease; } button:disabled { @@ -276,3 +283,162 @@ button:hover { cursor: pointer; } + + .eth-icon { + position: relative; + width: 32px; + height: 32px; + display: inline-block; + background-image: url("/eth.svg"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + cursor: pointer; + overflow: hidden; + } + + .eth-icon::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: url("/eth-hover.svg"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + opacity: 0; /* Hover state starts hidden */ + transition: opacity 0.3s ease; /* Smooth fade-in effect */ + } + + .eth-icon:hover::after { + opacity: 1; /* Fade in hover image */ + } + + + .sol-icon { + position: relative; + width: 32px; + height: 32px; + display: inline-block; + background-image: url("/solana.svg"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + cursor: pointer; + overflow: hidden; + } + + .sol-icon::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: url("/solana-hover.svg"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + opacity: 0; + transition: opacity 0.3s ease; + } + + .sol-icon:hover::after { + opacity: 1; + } + + +/* HoverContainer for the clickable elements */ +.hoverContainer { + display: flex; + align-items: center; + gap: 8px; + transition: color 0.3s ease; /* Smooth hover transition */ +} + +.hoverContainer:hover .eth-icon::after, +.hoverContainer:hover .sol-icon::after { + opacity: 1; /* Smooth transition for hover icons */ +} + +.hoverContainer:hover .accountAddress, +.hoverContainer:hover .launchIcon { + color: #8a8a8a; /* Lighter gray on hover */ +} + +/* AccountRow */ +.accountRow { + background-color: #ffffff; /* No background hover effect */ + border-radius: 8px; + align-items: center; + display: flex; + justify-content: space-between; + padding: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +/* Shared styling for address text and icons */ +.accountAddress { + font-family: monospace; + font-size: 16px; + transition: color 0.3s ease; /* Smooth text color transition */ +} + +.launchIcon { + width: 16px; + transition: color 0.3s ease; /* Smooth icon color transition */ +} + +/* Eth and Sol hover icon transitions */ +.eth-icon, +.sol-icon { + position: relative; + width: 32px; + height: 32px; + display: inline-block; + background-size: cover; + background-repeat: no-repeat; + background-position: center; + cursor: pointer; + overflow: hidden; +} + +.eth-icon { + background-image: url("/eth.svg"); +} + +.eth-icon::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: url("/eth-hover.svg"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + opacity: 0; + transition: opacity 0.3s ease; +} + +.sol-icon { + background-image: url("/solana.svg"); +} + +.sol-icon::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: url("/solana-hover.svg"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + opacity: 0; + transition: opacity 0.3s ease; +} diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index ea007d1f5..325e49d59 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -23,10 +23,10 @@ import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import { verifyEthSignature, verifySolSignatureWithAddress } from "../utils"; import { keccak256, toUtf8Bytes } from "ethers"; import { useRouter } from "next/navigation"; -import {TurnkeyPasskeyClient} from "@turnkey/sdk-browser" import AddCircleIcon from '@mui/icons-material/AddCircle'; import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import LaunchIcon from '@mui/icons-material/Launch'; export default function Dashboard() { const router = useRouter(); @@ -39,11 +39,11 @@ export default function Dashboard() { const [selectedWallet, setSelectedWallet] = useState(null) const [isModalOpen, setIsModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const [messageToSign, setMessageToSign] = useState(""); + const [messageToSign, setMessageToSign] = useState("Signing within Turnkey Demo"); const [signature, setSignature] = useState(null); const [suborgId, setSuborgId] = useState("") const [user, setUser] = useState("") - const [verificationResult, setVerificationResult] = useState( + const [messageSigningResult, setMessageSigningResult] = useState( null ); const [anchorEl, setAnchorEl] = useState(null); @@ -135,9 +135,8 @@ useEffect(() => { const handleModalClose = () => { setIsModalOpen(false); // Close the modal - setMessageToSign(""); setSignature(null); - setVerificationResult(null); + setMessageSigningResult(null); }; @@ -161,12 +160,10 @@ useEffect(() => { const handleSign = async () => { try { const addressType = selectedAccount?.startsWith("0x") ? "ETH" : "SOL"; - const message = messageToSign ? messageToSign : "Signing within Turnkey Demo" - const hashedMessage = - addressType === "ETH" - ? keccak256(toUtf8Bytes(message)) // Ethereum requires keccak256 hash - : Buffer.from(message, "utf8").toString("hex"); // Solana doesn't require hashing - + const hashedMessage = addressType === "ETH" + ? keccak256(toUtf8Bytes(messageToSign)) // Ethereum requires keccak256 hash + : Buffer.from(messageToSign, "utf8").toString("hex"); // Solana doesn't require hashing + const resp = await iframeClient?.signRawPayload({ organizationId: suborgId!, signWith: selectedAccount!, @@ -177,7 +174,7 @@ useEffect(() => { ? "HASH_FUNCTION_NO_OP" : "HASH_FUNCTION_NOT_APPLICABLE", }); - + setMessageSigningResult("✔ Success! Message signed") setSignature({ r: resp?.r, s: resp?.s, v: resp?.v }); } catch (error) { console.error("Error signing message:", error); @@ -186,27 +183,26 @@ useEffect(() => { const handleVerify = () => { if (!signature) return; - const message = messageToSign ? messageToSign : "Signing within Turnkey Demo" const addressType = selectedAccount?.startsWith("0x") ? "ETH" : "SOL"; const verificationPassed = addressType === "ETH" ? verifyEthSignature( - message, + messageToSign, signature.r, signature.s, signature.v, selectedAccount! ) : verifySolSignatureWithAddress( - message, + messageToSign, signature.r, signature.s, selectedAccount! ); - setVerificationResult( + setMessageSigningResult( verificationPassed - ? `Signed with: ${`${selectedAccount!.slice( + ? `Verified! The address used to sign the message matches your wallet address: ${`${selectedAccount!.slice( 0, 5 )}...${selectedAccount!.slice(-5)}`}` @@ -216,6 +212,8 @@ useEffect(() => { return (
+ +
Login methods @@ -359,63 +357,64 @@ useEffect(() => {
{accounts.map((account: any, index: number) => ( -
- {account.addressFormat === "ADDRESS_FORMAT_ETHEREUM" && ( - - window.open( - `https://etherscan.io/address/${account.address}`, - "_blank" - ) - } - /> - )} - {account.addressFormat === "ADDRESS_FORMAT_SOLANA" && ( - - window.open( - `https://solscan.io/account/${account.address}`, - "_blank" - ) - } - /> - )} - {`${account.address.slice( - 0, - 5 - )}...${account.address.slice(-5)}`} - - } - label="" - className="radioButton" - /> -
+
setSelectedAccount(account.address)} // Ensures the radio button is selected + style={{ + cursor: "pointer", + display: "flex", + alignItems: "center", + }} +> +
+ window.open( + account.addressFormat === "ADDRESS_FORMAT_ETHEREUM" + ? `https://etherscan.io/address/${account.address}` + : `https://solscan.io/account/${account.address}`, + "_blank" + ) + } + style={{ + display: "flex", + alignItems: "center", + gap: "8px", + }} + > + {account.addressFormat === "ADDRESS_FORMAT_ETHEREUM" && ( +
+ )} + {account.addressFormat === "ADDRESS_FORMAT_SOLANA" && ( +
+ )} + {`${account.address.slice( + 0, + 5 + )}...${account.address.slice(-5)}`} + +
+ + } + label="" + className="radioButton" + style={{ pointerEvents: "none" }} // Ensures radio works via the parent click + /> +
+ + + ))}
- Are you sure you would like to delete your account? + Confirm account deletion { marginTop: "8px", }} > - If there are any funds on your wallet, please ensure you have exported your seed phrase before deleting your account. This action cannot be undone. + This action can not be undone.
{ }} onClick={handleDeleteAccount} > - Delete + Continue
@@ -545,7 +544,7 @@ useEffect(() => { ×
- Sign a Message + Sign a message { setMessageToSign(e.target.value)} - placeholder="Signing within Turnkey Demo" + value={signature ? JSON.stringify(signature) : messageToSign} + rows = {5} + multiline + onChange={(e) => signature ? setSignature(JSON.parse(e.target.value)) : setMessageToSign(e.target.value)} sx={{ bgcolor: "#ffffff", "& .MuiOutlinedInput-root": { - height: "80px", - alignItems: "flex-start", + height: "auto", + alignItems: "flex-start", "& fieldset": { - borderColor: "#D0D5DD", + borderColor: "#D0D5DD", }, "&:hover fieldset": { - borderColor: "#8A929E", + borderColor: "#8A929E", }, "&.Mui-focused fieldset": { borderColor: "#D0D5DD", - border: "1px solid" + border: "1px solid", }, }, + "& .MuiInputBase-input": { + whiteSpace: "pre-wrap", + wordWrap: "break-word", + }, }} /> - - {signature && ( - <> - {`Signature: ${JSON.stringify(signature)}`} + {signature ? - - )} - {verificationResult && ( + : + } + {messageSigningResult && ( - {verificationResult} + {messageSigningResult} )} diff --git a/packages/sdk-react/src/components/export/Export.module.css b/packages/sdk-react/src/components/export/Export.module.css index ebb7bb322..e2578ad57 100644 --- a/packages/sdk-react/src/components/export/Export.module.css +++ b/packages/sdk-react/src/components/export/Export.module.css @@ -30,6 +30,11 @@ margin-bottom: 16px; } + .doneButtonContainer { + margin-top: 12px + } + + .exportButton { display: inline-flex; align-items: center; diff --git a/packages/sdk-react/src/components/export/Export.tsx b/packages/sdk-react/src/components/export/Export.tsx index 02cb972f8..9f2f7dc16 100644 --- a/packages/sdk-react/src/components/export/Export.tsx +++ b/packages/sdk-react/src/components/export/Export.tsx @@ -54,6 +54,7 @@ const Export: React.FC = ({ backgroundColor: "#ffffff", overflowWrap: "break-word", wordWrap: "break-word", + overflow: "hidden", resize: "none", }; iframeStamper.applySettings({ styles }); @@ -196,6 +197,17 @@ Your seed phrase is the key to your wallet. Save it in a secure location. padding: "16px", }} /> + {isIframeVisible && ( +
+ +
+ )} +
(window.location.href = "https://www.turnkey.com/")} className={styles.poweredBy} diff --git a/packages/sdk-react/src/components/import/Import.tsx b/packages/sdk-react/src/components/import/Import.tsx index 064fa365e..4f5ae9e2c 100644 --- a/packages/sdk-react/src/components/import/Import.tsx +++ b/packages/sdk-react/src/components/import/Import.tsx @@ -49,6 +49,7 @@ const Import: React.FC = ({ borderStyle: "none", backgroundColor: "#ffffff", overflowWrap: "break-word", + overflow: "hidden", wordWrap: "break-word", resize: "none", }; @@ -170,9 +171,7 @@ const Import: React.FC = ({ mb: 2, }} > - Import an existing wallet with your secret recovery phrase. Only - you should know your secret recovery phrase. A secret recovery - phrase can be 12, 15, 18, 21, or 24 words. + Enter your seed phrase. Seed phrases are typically 12 - 24 words @@ -192,7 +191,7 @@ const Import: React.FC = ({ setWalletName(e.target.value)} fullWidth From 28b7ceca84b8e24e8c90bf0832d6faab568f4a5a Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Wed, 27 Nov 2024 11:51:43 -0500 Subject: [PATCH 44/73] spacing changes and cleanup --- .../src/app/dashboard/dashboard.css | 13 ++++++------- .../react-components/src/app/dashboard/page.tsx | 2 +- examples/react-components/src/app/index.css | 2 +- packages/sdk-react/src/components/export/Export.tsx | 2 +- packages/sdk-react/src/components/import/Import.tsx | 2 +- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/examples/react-components/src/app/dashboard/dashboard.css b/examples/react-components/src/app/dashboard/dashboard.css index d0d542388..c717fbe8c 100644 --- a/examples/react-components/src/app/dashboard/dashboard.css +++ b/examples/react-components/src/app/dashboard/dashboard.css @@ -49,7 +49,7 @@ } .socialsTitle { - font-size: 20px; + font-size: 1rem; font-weight: 700; line-height: 26px; letter-spacing: -0.01em; @@ -59,7 +59,7 @@ .configTitle { - font-size: 32px; + font-size: 1.5rem; font-weight: 700; line-height: 36px; letter-spacing: -0.01em; @@ -67,7 +67,6 @@ margin-bottom: 16px; margin-top: 16px; } - .modalTitle { font-size: 18px; line-height: 24px; @@ -116,7 +115,7 @@ } .loginMethodDetails { - font-size: 14px; + font-size: 1rem; line-height: 20px; color: #868c95; margin-left: 32px; @@ -207,11 +206,11 @@ } .signMessage { - margin-bottom: 24px; - margin-top: 24px; + margin-bottom: 12px; + /* margin-top: 24px; */ } .exportImportGroup { - margin-top: 24px; + margin-top: 12px; display: flex; gap: 12px; } diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index 325e49d59..34e2f57d8 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -333,7 +333,7 @@ useEffect(() => { }} onClick={handleDropdownClick} > - + {wallets.find((wallet) => wallet.walletId === selectedWallet)?.walletName || "Select Wallet"} diff --git a/examples/react-components/src/app/index.css b/examples/react-components/src/app/index.css index 0f12c7594..b55b13ecf 100644 --- a/examples/react-components/src/app/index.css +++ b/examples/react-components/src/app/index.css @@ -51,7 +51,7 @@ justify-content: space-between; } .configTitle { - font-size: 32px; + font-size: 1.5rem; font-weight: 700; line-height: 36px; letter-spacing: -0.01em; diff --git a/packages/sdk-react/src/components/export/Export.tsx b/packages/sdk-react/src/components/export/Export.tsx index 9f2f7dc16..9c5126ffc 100644 --- a/packages/sdk-react/src/components/export/Export.tsx +++ b/packages/sdk-react/src/components/export/Export.tsx @@ -108,8 +108,8 @@ const Export: React.FC = ({ return ( <> diff --git a/packages/sdk-react/src/components/import/Import.tsx b/packages/sdk-react/src/components/import/Import.tsx index 4f5ae9e2c..7cb69fa31 100644 --- a/packages/sdk-react/src/components/import/Import.tsx +++ b/packages/sdk-react/src/components/import/Import.tsx @@ -125,8 +125,8 @@ const Import: React.FC = ({ return ( <> From f8f7775b9b8074ba2d240e042cdeb6d66d5d25a4 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Mon, 2 Dec 2024 15:14:39 -0500 Subject: [PATCH 45/73] add social linking, passkey linking, todo phone and email verified linking --- examples/react-components/package.json | 1 + .../src/app/{ => components}/Switch.tsx | 0 .../src/app/dashboard/page.tsx | 113 ++++++++++- examples/react-components/src/app/page.tsx | 2 +- .../src/app/utils/facebookUtils.ts | 38 ++++ .../react-components/src/app/utils/oidc.ts | 187 ++++++++++++++++++ pnpm-lock.yaml | 3 + 7 files changed, 338 insertions(+), 6 deletions(-) rename examples/react-components/src/app/{ => components}/Switch.tsx (100%) create mode 100644 examples/react-components/src/app/utils/facebookUtils.ts create mode 100644 examples/react-components/src/app/utils/oidc.ts diff --git a/examples/react-components/package.json b/examples/react-components/package.json index 51fae1bed..93d45c011 100644 --- a/examples/react-components/package.json +++ b/examples/react-components/package.json @@ -15,6 +15,7 @@ "@hello-pangea/dnd": "^17.0.0", "@mui/icons-material": "^6.1.5", "@mui/material": "^6.1.5", + "@noble/hashes": "1.4.0", "@solana/web3.js": "^1.95.4", "@turnkey/sdk-browser": "^1.10.0", "@turnkey/sdk-react": "workspace:*", diff --git a/examples/react-components/src/app/Switch.tsx b/examples/react-components/src/app/components/Switch.tsx similarity index 100% rename from examples/react-components/src/app/Switch.tsx rename to examples/react-components/src/app/components/Switch.tsx diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index 34e2f57d8..aeaf61aed 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -1,6 +1,6 @@ "use client" -import { Export, Import, useTurnkey } from "@turnkey/sdk-react"; +import { Export, Import, useTurnkey, getSuborgs } from "@turnkey/sdk-react"; import { useEffect, useState } from "react"; import "./dashboard.css"; import { @@ -27,10 +27,11 @@ import AddCircleIcon from '@mui/icons-material/AddCircle'; import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import LaunchIcon from '@mui/icons-material/Launch'; +import { appleOidcToken, facebookOidcToken, googleOidcToken } from "../utils/oidc"; export default function Dashboard() { const router = useRouter(); - const { turnkey, getActiveClient, authIframeClient } = useTurnkey(); + const { turnkey, getActiveClient, authIframeClient, passkeyClient } = useTurnkey(); const [loading, setLoading] = useState(true); const [iframeClient, setIframeClient] = useState(); const [accounts, setAccounts] = useState([]); @@ -48,6 +49,87 @@ export default function Dashboard() { ); const [anchorEl, setAnchorEl] = useState(null); + const handleDeleteOauth = async (oauthType: string) => { + let providerId + switch (oauthType) { + case "Apple": + providerId = user?.oauthProviders?.find((provider: { issuer: string }) => provider.issuer.toLowerCase().includes("apple"))?.providerId || null; + break; + + case "Facebook": + providerId = user?.oauthProviders?.find((provider: { issuer: string }) => provider.issuer.toLowerCase().includes("facebook"))?.providerId || null; + break; + + case "Google": + providerId = user?.oauthProviders?.find((provider: { issuer: string }) => provider.issuer.toLowerCase().includes("google"))?.providerId || null; + break; + + default: + console.error(`Unknown OAuth type: ${oauthType}`); + } + + if (providerId){ + await authIframeClient?.deleteOauthProviders({organizationId: suborgId, userId: user.userId, providerIds: [providerId]}) + window.location.reload(); + } + } + + const handleAddOauth = async (oauthType: string) => { + let oidcToken + switch (oauthType) { + case "Apple": + oidcToken = await appleOidcToken({iframePublicKey: authIframeClient?.iframePublicKey!, clientId: process.env.NEXT_PUBLIC_APPLE_CLIENT_ID!, redirectURI: `${process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI!}dashboard`}) + break; + + case "Facebook": + oidcToken = await facebookOidcToken({iframePublicKey: authIframeClient?.iframePublicKey!, clientId: process.env.NEXT_PUBLIC_FACEBOOK_CLIENT_ID!, redirectURI: `${process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI!}dashboard`}) + break; + + case "Google": + oidcToken = await googleOidcToken({iframePublicKey: authIframeClient?.iframePublicKey!, clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!, redirectURI: `${process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI!}dashboard`}) + break; + + default: + console.error(`Unknown OAuth type: ${oauthType}`); + } + if (oidcToken){ + const suborgs = await getSuborgs({filterType:"OIDC_TOKEN", filterValue:oidcToken.idToken}) + if (suborgs!.organizationIds.length > 0){ + alert("Social login is already connected to another account") + return + } + await authIframeClient?.createOauthProviders({organizationId: suborgId, userId: user.userId, oauthProviders: [{providerName: `TurnkeyDemoApp - ${Date.now()}`, oidcToken: oidcToken.idToken}]}) + window.location.reload(); + } + } + + const handleAddPasskey = async () => { + const siteInfo = `${new URL(window.location.href).hostname} - ${new Date().toLocaleString(undefined, { + year: "numeric", + month: "long", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + })}`; + const { encodedChallenge, attestation } = + (await passkeyClient?.createUserPasskey({ + publicKey: { user: { name: siteInfo, displayName: siteInfo } }, + })) || {}; + + if (encodedChallenge && attestation) { + authIframeClient?.createAuthenticators({organizationId: suborgId, userId: user.userId, authenticators: [{authenticatorName: `Passkey - ${Date.now()}`, challenge:encodedChallenge, attestation }]}) + window.location.reload(); + } + } + + const handleDeletePasskey = async () => { + const authenticators = await authIframeClient?.getAuthenticators({organizationId: suborgId, userId: user.userId}) + const authenticatorId = authenticators?.authenticators[0].authenticatorId + await authIframeClient?.deleteAuthenticators({organizationId: suborgId, userId: user.userId, authenticatorIds:[authenticatorId!]}) + window.location.reload(); + } + const handleDropdownClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; @@ -263,11 +345,15 @@ useEffect(() => { )}
{user && user.authenticators && user.authenticators.length > 0 ? ( +
handleDeletePasskey()}> +
) : ( +
handleAddPasskey()}> +
)}
@@ -278,15 +364,24 @@ useEffect(() => {
Google + {user && user.oauthProviders && user.oauthProviders.some( + (provider: { issuer: string; }) => provider.issuer.toLowerCase().includes("google")) + && ( + {} + )}
{user && user.oauthProviders && user.oauthProviders.some( - (provider: { providerName: string; }) => provider.providerName === "Google" + (provider: { issuer: string; }) => provider.issuer.toLowerCase().includes("google") ) ? ( +
handleDeleteOauth("Google")}> +
) : ( +
handleAddOauth("Google")} > +
)}
@@ -295,13 +390,17 @@ useEffect(() => { Apple
{user && user.oauthProviders && user.oauthProviders.some( - (provider: { providerName: string; }) => provider.providerName === "Apple" + (provider: { issuer: string; }) => provider.issuer.toLowerCase().includes("apple") ) ? ( +
handleDeleteOauth("Apple")}> +
) : ( +
handleAddOauth("Apple")}> +
)}
@@ -310,13 +409,17 @@ useEffect(() => { Facebook
{user && user.oauthProviders && user.oauthProviders.some( - (provider: { providerName: string; }) => provider.providerName === "Facebook" + (provider: { issuer: string; }) => provider.issuer.toLowerCase().includes("facebook") ) ? ( +
handleDeleteOauth("Facebook")}> +
) : ( +
handleAddOauth("Facebook")}> +
)}
diff --git a/examples/react-components/src/app/page.tsx b/examples/react-components/src/app/page.tsx index 32fcf4cc2..a9b1d390f 100644 --- a/examples/react-components/src/app/page.tsx +++ b/examples/react-components/src/app/page.tsx @@ -5,7 +5,7 @@ import { useState } from "react"; import { useTurnkey, Auth } from "@turnkey/sdk-react"; import { Typography } from "@mui/material"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; -import CustomSwitch from "./Switch"; +import CustomSwitch from "./components/Switch"; import { DragDropContext, Droppable, Draggable, DropResult } from "@hello-pangea/dnd"; import "./index.css"; import { useRouter } from "next/navigation"; diff --git a/examples/react-components/src/app/utils/facebookUtils.ts b/examples/react-components/src/app/utils/facebookUtils.ts new file mode 100644 index 000000000..cc833dbb5 --- /dev/null +++ b/examples/react-components/src/app/utils/facebookUtils.ts @@ -0,0 +1,38 @@ +"use server"; +import crypto from "crypto"; + +export async function generateChallengePair() { + const verifier = crypto.randomBytes(32).toString("base64url"); + const codeChallenge = crypto + .createHash("sha256") + .update(verifier) + .digest("base64url"); + return { verifier, codeChallenge }; +} + +export async function exchangeCodeForToken( + clientId: any, + redirectURI: any, + authCode: any, + verifier: any +) { + const response = await fetch( + `https://graph.facebook.com/v11.0/oauth/access_token`, + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + client_id: clientId, + redirect_uri: redirectURI, + code: authCode, + code_verifier: verifier, + }), + } + ); + + const tokenData = await response.json(); + if (!response.ok) { + throw new Error("Token exchange failed: " + JSON.stringify(tokenData)); + } + return tokenData; +} diff --git a/examples/react-components/src/app/utils/oidc.ts b/examples/react-components/src/app/utils/oidc.ts new file mode 100644 index 000000000..4bc7d4b65 --- /dev/null +++ b/examples/react-components/src/app/utils/oidc.ts @@ -0,0 +1,187 @@ +"use client"; + +import { sha256 } from "@noble/hashes/sha2"; +import { bytesToHex } from "@noble/hashes/utils"; +import { exchangeCodeForToken, generateChallengePair } from "./facebookUtils"; + +interface OidcTokenParams { + iframePublicKey: string; + clientId: string; + redirectURI: string; +} + +export const appleOidcToken = async ({ iframePublicKey, clientId, redirectURI }: OidcTokenParams): Promise => { + const nonce = bytesToHex(sha256(iframePublicKey)); + const appleAuthUrl = new URL("https://appleid.apple.com/auth/authorize"); + appleAuthUrl.searchParams.set("client_id", clientId); + appleAuthUrl.searchParams.set("redirect_uri", redirectURI); + appleAuthUrl.searchParams.set("response_type", "code id_token"); + appleAuthUrl.searchParams.set("response_mode", "fragment"); + appleAuthUrl.searchParams.set("nonce", nonce); + + const width = 500; + const height = 600; + const left = window.screenX + (window.innerWidth - width) / 2; + const top = window.screenY + (window.innerHeight - height) / 2; + + return new Promise((resolve, reject) => { + const authWindow = window.open( + appleAuthUrl.toString(), + "_blank", + `width=${width},height=${height},top=${top},left=${left},scrollbars=yes,resizable=yes` + ); + + if (!authWindow) { + reject(new Error("Failed to open Apple login window.")); + return; + } + + const interval = setInterval(() => { + try { + const url = authWindow?.location.href || ""; + if (url.startsWith(window.location.origin)) { + const hashParams = new URLSearchParams(url.split("#")[1]); + const idToken = hashParams.get("id_token"); + if (idToken) { + authWindow?.close(); + clearInterval(interval); + resolve({ idToken }); + } + } + } catch (error) { + // Ignore cross-origin errors until redirected + } + + if (authWindow?.closed) { + clearInterval(interval); + reject(new Error("Apple login window was closed.")); + } + }, 500); + }); +}; + +export const googleOidcToken = async ({ iframePublicKey, clientId, redirectURI }: OidcTokenParams): Promise => { + const nonce = bytesToHex(sha256(iframePublicKey)); + const googleAuthUrl = new URL("https://accounts.google.com/o/oauth2/v2/auth"); + googleAuthUrl.searchParams.set("client_id", clientId); + googleAuthUrl.searchParams.set("redirect_uri", redirectURI); + googleAuthUrl.searchParams.set("response_type", "id_token"); + googleAuthUrl.searchParams.set("scope", "openid email profile"); + googleAuthUrl.searchParams.set("nonce", nonce); + googleAuthUrl.searchParams.set("prompt", "select_account"); + + const width = 500; + const height = 600; + const left = window.screenX + (window.innerWidth - width) / 2; + const top = window.screenY + (window.innerHeight - height) / 2; + + return new Promise((resolve, reject) => { + const authWindow = window.open( + googleAuthUrl.toString(), + "_blank", + `width=${width},height=${height},top=${top},left=${left},scrollbars=yes,resizable=yes` + ); + + if (!authWindow) { + reject(new Error("Failed to open Google login window.")); + return; + } + + const interval = setInterval(() => { + try { + const url = authWindow?.location.href || ""; + if (url.startsWith(window.location.origin)) { + const hashParams = new URLSearchParams(url.split("#")[1]); + const idToken = hashParams.get("id_token"); + if (idToken) { + authWindow?.close(); + clearInterval(interval); + resolve({ idToken }); + } + } + } catch (error) { + // Ignore cross-origin errors until redirected + } + + if (authWindow?.closed) { + clearInterval(interval); + reject(new Error("Google login window was closed.")); + } + }, 500); + }); +}; + + +export const facebookOidcToken = async ({ iframePublicKey, clientId, redirectURI }: OidcTokenParams): Promise => { + const { verifier, codeChallenge } = await generateChallengePair(); + sessionStorage.setItem("facebook_verifier", verifier); + + const params = new URLSearchParams({ + client_id: clientId, + redirect_uri: redirectURI, + state: verifier, + code_challenge: codeChallenge, + code_challenge_method: "S256", + nonce: bytesToHex(sha256(iframePublicKey)), + scope: "openid", + response_type: "code", + }); + + const facebookOAuthURL = `https://www.facebook.com/v11.0/dialog/oauth?${params.toString()}`; + const width = 500; + const height = 600; + const left = window.screenX + (window.innerWidth - width) / 2; + const top = window.screenY + (window.innerHeight - height) / 2; + + return new Promise((resolve, reject) => { + const popup = window.open( + facebookOAuthURL, + "_blank", + `width=${width},height=${height},top=${top},left=${left},scrollbars=yes,resizable=yes` + ); + + if (!popup) { + reject(new Error("Failed to open login popup")); + return; + } + + const interval = setInterval(async () => { + try { + if (popup.closed) { + clearInterval(interval); + reject(new Error("Popup closed by user before completing authentication")); + return; + } + + const popupUrl = new URL(popup.location.href); + const authCode = popupUrl.searchParams.get("code"); + + if (authCode) { + popup.close(); + clearInterval(interval); + const verifier = sessionStorage.getItem("facebook_verifier"); + if (!verifier) { + reject(new Error("No verifier found in sessionStorage")); + return; + } + + try { + const tokenData = await exchangeCodeForToken( + clientId, + redirectURI, + authCode, + verifier + ); + sessionStorage.removeItem("facebook_verifier"); + resolve({idToken: tokenData.id_token}); + } catch (error) { + reject(new Error(`Error during token exchange: ${error}`)); + } + } + } catch (error) { + // Ignore cross-origin errors until the popup redirects to the same origin + } + }, 250); + }); +}; + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 874e6531d..224069ba7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -487,6 +487,9 @@ importers: '@mui/material': specifier: ^6.1.5 version: 6.1.5(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0) + '@noble/hashes': + specifier: 1.4.0 + version: 1.4.0 '@solana/web3.js': specifier: ^1.95.4 version: 1.95.4(encoding@0.1.13) From 5029aa0a3cc96f6e33948b6a4e13750d34a4c8fa Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Mon, 2 Dec 2024 16:22:27 -0500 Subject: [PATCH 46/73] separate otp verification component --- .../src/components/auth/Auth.module.css | 20 -- .../sdk-react/src/components/auth/Auth.tsx | 105 +------ .../sdk-react/src/components/auth/Otp.css | 297 ++++++++++++++++++ .../auth/OtpVerification.module.css | 297 ++++++++++++++++++ .../src/components/auth/OtpVerification.tsx | 110 +++++++ .../sdk-react/src/components/auth/index.ts | 1 + .../sdk-react/src/components/auth/utils.ts | 6 + packages/sdk-react/src/index.ts | 1 + 8 files changed, 729 insertions(+), 108 deletions(-) create mode 100644 packages/sdk-react/src/components/auth/Otp.css create mode 100644 packages/sdk-react/src/components/auth/OtpVerification.module.css create mode 100644 packages/sdk-react/src/components/auth/OtpVerification.tsx create mode 100644 packages/sdk-react/src/components/auth/utils.ts diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index fa6e95740..9408c5f9a 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -139,26 +139,6 @@ background: var(--Greyscale-100, #ebedf2); z-index: 0; } -.verification { - font-size: 18px; - font-weight: 400; - line-height: 16px; - letter-spacing: -0.01em; - text-align: center; - color: var(--Greyscale-500, #868c95); - margin-top: 16px; -} - -.verificationBold { - color: var(--Greyscale-900, #2b2f33); - font-weight: 700; - margin-top: 8px; -} -.verificationIcon { - display: flex; - justify-content: center; - margin-bottom: 8px; -} .tos { font-size: 12px; font-weight: 400; diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 8e0b8e6ab..1895d875b 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -1,28 +1,24 @@ import styles from "./Auth.module.css"; -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useState } from "react"; import { useTurnkey } from "../../hooks/useTurnkey"; import { initOtpAuth, - otpAuth, getSuborgs, createSuborg, oauth, } from "../../actions/"; import { MuiPhone } from "./PhoneInput"; -import OtpInput from "./otp"; import GoogleAuthButton from "./Google"; import AppleAuthButton from "./Apple"; import FacebookAuthButton from "./Facebook"; import { CircularProgress, TextField } from "@mui/material"; -import { parsePhoneNumberFromString } from "libphonenumber-js"; import turnkeyIcon from "assets/turnkey.svg"; import googleIcon from "assets/google.svg"; import facebookIcon from "assets/facebook.svg"; import appleIcon from "assets/apple.svg"; -import emailIcon from "assets/email.svg"; -import smsIcon from "assets/sms.svg"; import passkeyIcon from "assets/passkey.svg"; import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; +import OtpVerification from "./OtpVerification"; interface AuthProps { onHandleAuthSuccess: () => Promise; @@ -40,41 +36,27 @@ interface AuthProps { const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrder }) => { const { passkeyClient, authIframeClient } = useTurnkey(); const [error, setError] = useState(null); - const [otpError, setOtpError] = useState(null); const [email, setEmail] = useState(""); const [phone, setPhone] = useState(""); const [otpId, setOtpId] = useState(null); const [step, setStep] = useState("auth"); const [oauthLoading, setOauthLoading] = useState(""); const [suborgId, setSuborgId] = useState(""); - const [resendText, setResendText] = useState("Resend code"); const [passkeySignupScreen, setPasskeySignupScreen] = useState(false); const [passkeyCreationScreen, setPasskeyCreationScreen] = useState(false); const [passkeySignupError, setPasskeySignupError] = useState(""); const [loading, setLoading] = useState(true); const [passkeyCreated, setPasskeyCreated] = useState(false); - const otpInputRef = useRef(null); const handleResendCode = async () => { - setOtpError(null); if (step === "otpEmail") { await handleOtpLogin("EMAIL", email, "OTP_TYPE_EMAIL"); } else if (step === "otpPhone") { await handleOtpLogin("PHONE_NUMBER", phone, "OTP_TYPE_SMS"); } - setResendText("Code sent ✓"); - - setTimeout(() => { - setResendText("Resend code"); - }, 15000); - }; + } - const formatPhoneNumber = (phone: string) => { - const phoneNumber = parsePhoneNumberFromString(phone); - return phoneNumber ? phoneNumber.formatInternational() : phone; - }; - useEffect(() => { if (error) { alert(error); @@ -196,19 +178,6 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde setStep(type === "EMAIL" ? "otpEmail" : "otpPhone"); }; - const handleValidateOtp = async (otp: string) => { - setOtpError(null); - const authResponse = await otpAuth({ - suborgID: suborgId, - otpId: otpId!, - otpCode: otp, - targetPublicKey: authIframeClient!.iframePublicKey!, - }); - authResponse?.credentialBundle - ? await handleAuthSuccess(authResponse.credentialBundle) - : setOtpError("Invalid code. Please try again"); - otpInputRef.current.resetOtp(); - }; const handleOAuthLogin = async (credential: string, providerName: string) => { setOauthLoading(providerName); @@ -482,39 +451,20 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde )} ))} - - {otpId && ( -
-
-
- {step === "otpEmail" ? ( - - ) : ( - - )} -
- - - Enter the 6-digit code we {step === "otpEmail" ? "emailed" : "texted"} to{" "} -
- {step === "otpEmail" - ? email - : formatPhoneNumber(phone)} -
-
- -
-
- {otpError ? otpError : " "} -
-
- )} - - {!otpId ? ( +{otpId && ( + +)} + + + {!otpId && (
By continuing, you agree to our{" "} @@ -538,27 +488,6 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde {"."}
- ) : ( -
- - - {resendText} - - -
)}
Promise; + onResendCode: (type: "EMAIL" | "PHONE_NUMBER", value: string) => Promise; +} + +const OtpVerification: React.FC = ({ + type, + contact, + suborgId, + otpId, + authIframeClient, + onValidateSuccess, + onResendCode, +}) => { + const [otpError, setOtpError] = useState(null); + const [resendText, setResendText] = useState("Resend code"); + const otpInputRef = useRef(null); + + + const handleValidateOtp = async (otp: string) => { + setOtpError(null); + + try { + const authResponse = await otpAuth({ + suborgID: suborgId, + otpId, + otpCode: otp, + targetPublicKey: authIframeClient!.iframePublicKey!, + }); + + if (authResponse?.credentialBundle) { + await onValidateSuccess(authResponse.credentialBundle); + } else { + setOtpError("Invalid code. Please try again"); + } + otpInputRef.current.resetOtp(); + } catch (error) { + setOtpError("An error occurred. Please try again."); + } + }; + + const handleResendCode = async () => { + setOtpError(null); + try { + await onResendCode( + type === "otpEmail" ? "EMAIL" : "PHONE_NUMBER", + contact + ); + setResendText("Code sent ✓"); + + setTimeout(() => { + setResendText("Resend code"); + }, 15000); + } catch { + setOtpError("Failed to resend the code. Please try again."); + } + }; + + return ( +
+
+ Icon +
+ + + Enter the 6-digit code we {type === "otpEmail" ? "emailed" : "texted"} to{" "} + + {type === "otpEmail" ? contact : formatPhoneNumber(contact)} + + + + + +
{otpError ? otpError : " "}
+ +
+ + {resendText} + +
+
+ ); +}; + +export default OtpVerification; diff --git a/packages/sdk-react/src/components/auth/index.ts b/packages/sdk-react/src/components/auth/index.ts index 4adba6e6c..aa33c5f82 100644 --- a/packages/sdk-react/src/components/auth/index.ts +++ b/packages/sdk-react/src/components/auth/index.ts @@ -1 +1,2 @@ export { default as Auth } from "./Auth"; +export { default as OtpVerification} from "./OtpVerification" \ No newline at end of file diff --git a/packages/sdk-react/src/components/auth/utils.ts b/packages/sdk-react/src/components/auth/utils.ts new file mode 100644 index 000000000..6832d206b --- /dev/null +++ b/packages/sdk-react/src/components/auth/utils.ts @@ -0,0 +1,6 @@ +import parsePhoneNumberFromString from "libphonenumber-js"; + +export const formatPhoneNumber = (phone: string) => { + const phoneNumber = parsePhoneNumberFromString(phone); + return phoneNumber ? phoneNumber.formatInternational() : phone; + }; diff --git a/packages/sdk-react/src/index.ts b/packages/sdk-react/src/index.ts index f1f4e0dae..529015981 100644 --- a/packages/sdk-react/src/index.ts +++ b/packages/sdk-react/src/index.ts @@ -1,4 +1,5 @@ import "./components/auth/Auth.module.css"; +import "./components/auth/OtpVerification.module.css"; import "./components/auth/PhoneInput.css"; import "./components/export/Export.module.css" import "./components/import/Import.module.css" From 60424078dd04d4fecdd05c6c6b35c6bc5271b275 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Mon, 2 Dec 2024 17:24:28 -0500 Subject: [PATCH 47/73] scrappy account linking --- examples/react-components/package.json | 1 + .../src/app/components/PhoneInput.tsx | 148 +++++++++++ .../src/app/dashboard/dashboard.css | 28 +- .../src/app/dashboard/page.tsx | 248 +++++++++++++++++- .../auth/OtpVerification.module.css | 4 + pnpm-lock.yaml | 3 + 6 files changed, 413 insertions(+), 19 deletions(-) create mode 100644 examples/react-components/src/app/components/PhoneInput.tsx diff --git a/examples/react-components/package.json b/examples/react-components/package.json index 93d45c011..50523621c 100644 --- a/examples/react-components/package.json +++ b/examples/react-components/package.json @@ -35,6 +35,7 @@ "npm": "^9.7.2", "react": "18.2.0", "react-dom": "18.2.0", + "react-international-phone": "^4.3.0", "tweetnacl": "^1.0.3", "typescript": "5.1.3" } diff --git a/examples/react-components/src/app/components/PhoneInput.tsx b/examples/react-components/src/app/components/PhoneInput.tsx new file mode 100644 index 000000000..5568f83e9 --- /dev/null +++ b/examples/react-components/src/app/components/PhoneInput.tsx @@ -0,0 +1,148 @@ +import "react-international-phone/style.css" +import { + BaseTextFieldProps, + InputAdornment, + MenuItem, + Select, + TextField, + Typography, +} from "@mui/material"; +import { + CountryIso2, + defaultCountries, + parseCountry, + usePhoneInput, +} from "react-international-phone"; +import { FlagImage as OriginalFlagImage } from "react-international-phone"; + +const FlagImage = OriginalFlagImage as React.ElementType; // fix for typecheck issue + +const countries = defaultCountries.filter((country) => { + const { iso2 } = parseCountry(country); + return ["us", "ca"].includes(iso2); +}); + +export interface MUIPhoneProps extends BaseTextFieldProps { + value: string; + onChange: (phone: string) => void; +} + +export const MuiPhone: React.FC = ({ + value, + onChange, + ...restProps +}) => { + const { inputValue, handlePhoneValueChange, inputRef, country, setCountry } = + usePhoneInput({ + defaultCountry: "us", + disableDialCodeAndPrefix: true, + value, + countries: countries, + onChange: (data) => { + onChange(data.phone); + }, + }); + + return ( + + + + + ), + }} + {...restProps} + /> + ); +}; diff --git a/examples/react-components/src/app/dashboard/dashboard.css b/examples/react-components/src/app/dashboard/dashboard.css index c717fbe8c..fc1f95aa7 100644 --- a/examples/react-components/src/app/dashboard/dashboard.css +++ b/examples/react-components/src/app/dashboard/dashboard.css @@ -87,39 +87,34 @@ flex-direction: column; gap: 12px; } - + .loginMethodRow { background-color: #ffffff; border-radius: 8px; - align-items: center; display: flex; + align-items: center; justify-content: space-between; padding: 12px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + position: relative; } - + .labelContainer { display: flex; align-items: center; gap: 8px; - } - - .loginMethodRow { - background-color: #ffffff; - border-radius: 8px; - align-items: center; - display: flex; - justify-content: space-between; - padding: 12px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + flex: 0; } .loginMethodDetails { font-size: 1rem; line-height: 20px; color: #868c95; - margin-left: 32px; + position: absolute; + left: 33%; + text-align: left; } + .accountRow { background-color: #ffffff; @@ -205,6 +200,11 @@ background-color: #ffffff; } + .continue { + margin-top: 12px; + /* margin-top: 24px; */ + } + .signMessage { margin-bottom: 12px; /* margin-top: 24px; */ diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index aeaf61aed..530aaa6cf 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -1,6 +1,6 @@ "use client" -import { Export, Import, useTurnkey, getSuborgs } from "@turnkey/sdk-react"; +import { Export, Import, useTurnkey, getSuborgs, OtpVerification } from "@turnkey/sdk-react"; import { useEffect, useState } from "react"; import "./dashboard.css"; import { @@ -28,6 +28,7 @@ import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import LaunchIcon from '@mui/icons-material/Launch'; import { appleOidcToken, facebookOidcToken, googleOidcToken } from "../utils/oidc"; +import { MuiPhone } from "../components/PhoneInput"; export default function Dashboard() { const router = useRouter(); @@ -44,11 +45,75 @@ export default function Dashboard() { const [signature, setSignature] = useState(null); const [suborgId, setSuborgId] = useState("") const [user, setUser] = useState("") + const [otpId, setOtpId] = useState("") const [messageSigningResult, setMessageSigningResult] = useState( null ); const [anchorEl, setAnchorEl] = useState(null); + const [isEmailModalOpen, setIsEmailModalOpen] = useState(false); + const [isPhoneModalOpen, setIsPhoneModalOpen] = useState(false); + const [isOtpModalOpen, setIsOtpModalOpen] = useState(false); +const [emailInput, setEmailInput] = useState(""); +const [phoneInput, setPhoneInput] = useState(""); + + +const handleResendEmail = async () => { + const initAuthResponse = await authIframeClient?.initOtpAuth({organizationId: suborgId, otpType: "OTP_TYPE_EMAIL", contact: emailInput}) + setOtpId(initAuthResponse?.otpId!) +} +const handleResendSms = async () => { + const initAuthResponse = await authIframeClient?.initOtpAuth({organizationId: suborgId, otpType: "OTP_TYPE_SMS", contact: phoneInput}) + setOtpId(initAuthResponse?.otpId!) +} + + +const handleOtpSuccess = async (credentialBundle: any) => { + window.location.reload(); +} +const handleOpenEmailModal = () => { + setIsEmailModalOpen(true); +}; +const handleOpenPhoneModal = () => { + setIsPhoneModalOpen(true); +}; +const handleEmailSubmit = async () => { + if (!emailInput) { + alert("Please enter a valid email address."); + return; + } + const suborgs = await getSuborgs({filterType:"EMAIL", filterValue:emailInput}) //TODO change to get verified suborgs + if (suborgs!.organizationIds.length > 0){ + alert("Email is already connected to another account") + return + } + await authIframeClient?.updateUser({organizationId: suborgId, userId: user.userId, userEmail: emailInput, userTagIds:[]}) + const initAuthResponse = await authIframeClient?.initOtpAuth({organizationId: suborgId, otpType: "OTP_TYPE_EMAIL", contact: emailInput}) + setOtpId(initAuthResponse?.otpId!) + setIsEmailModalOpen(false); + setIsOtpModalOpen(true) + +}; + + +const handlePhoneSubmit = async () => { + if (!phoneInput) { + alert("Please enter a valid phone number."); + return; + } + const suborgs = await getSuborgs({filterType:"PHONE_NUMBER", filterValue:phoneInput}) //TODO change to get verified suborgs + if (suborgs!.organizationIds.length > 0){ + alert("Phone Number is already connected to another account") + return + } + await authIframeClient?.updateUser({organizationId: suborgId, userId: user.userId, userPhoneNumber: phoneInput, userTagIds:[]}) + const initAuthResponse = await authIframeClient?.initOtpAuth({organizationId: suborgId, otpType: "OTP_TYPE_SMS", contact: phoneInput}) + setOtpId(initAuthResponse?.otpId!) + setIsEmailModalOpen(false); + setIsOtpModalOpen(true) + +}; + const handleDeleteOauth = async (oauthType: string) => { let providerId switch (oauthType) { @@ -118,7 +183,7 @@ export default function Dashboard() { })) || {}; if (encodedChallenge && attestation) { - authIframeClient?.createAuthenticators({organizationId: suborgId, userId: user.userId, authenticators: [{authenticatorName: `Passkey - ${Date.now()}`, challenge:encodedChallenge, attestation }]}) + await authIframeClient?.createAuthenticators({organizationId: suborgId, userId: user.userId, authenticators: [{authenticatorName: `Passkey - ${Date.now()}`, challenge:encodedChallenge, attestation }]}) window.location.reload(); } } @@ -165,6 +230,7 @@ useEffect(() => { const userResponse = await iframeClient!.getUser({organizationId: suborgId!, userId: whoami?.userId!}) setUser(userResponse.user) + console.log(userResponse.user) const walletsResponse = await iframeClient!.getWallets({ organizationId: suborgId!, }); @@ -310,11 +376,15 @@ useEffect(() => { )}
{user && user.userEmail ? ( +
+
) : ( +
+
)}
@@ -328,11 +398,16 @@ useEffect(() => { )}
{user && user.userPhoneNumber ? ( - +
+ +
) : ( +
+ +
)}
@@ -718,7 +793,170 @@ useEffect(() => { )} +{ +isEmailModalOpen && + setIsEmailModalOpen(false)}> + +
setIsEmailModalOpen(false)} + style={{ + position: "absolute", + top: "16px", + right: "16px", + background: "none", + border: "none", + fontSize: "20px", + fontWeight: "bold", + cursor: "pointer", + color: "#6C727E", + }} + > + × +
+ + Enter new email + + setEmailInput(e.target.value)} + placeholder="example@example.com" + sx={{ + bgcolor: "#ffffff", + "& .MuiOutlinedInput-root": { + "& fieldset": { + borderColor: "#D0D5DD", + }, + "&:hover fieldset": { + borderColor: "#8A929E", + }, + "&.Mui-focused fieldset": { + borderColor: "#D0D5DD", + border: "1px solid", + }, + }, + "& .MuiInputBase-input": { + whiteSpace: "pre-wrap", + wordWrap: "break-word", + }, + }} + /> + +
+
+} + +{ +isPhoneModalOpen && + setIsPhoneModalOpen(false)}> + +
setIsPhoneModalOpen(false)} + style={{ + position: "absolute", + top: "16px", + right: "16px", + background: "none", + border: "none", + fontSize: "20px", + fontWeight: "bold", + cursor: "pointer", + color: "#6C727E", + }} + > + × +
+ + Enter new phone number + + setPhoneInput(e)} + /> + +
+
+} +{ +isOtpModalOpen && + setIsOtpModalOpen(false)}> + +
setIsOtpModalOpen(false)} + style={{ + position: "absolute", + top: "16px", + right: "16px", + background: "none", + border: "none", + fontSize: "20px", + fontWeight: "bold", + cursor: "pointer", + color: "#6C727E", + }} + > + × +
+ +
+
+ +}
); } diff --git a/packages/sdk-react/src/components/auth/OtpVerification.module.css b/packages/sdk-react/src/components/auth/OtpVerification.module.css index aa8fd06fd..47dde0352 100644 --- a/packages/sdk-react/src/components/auth/OtpVerification.module.css +++ b/packages/sdk-react/src/components/auth/OtpVerification.module.css @@ -140,6 +140,7 @@ z-index: 0; } .verification { + font-family: Inter; font-size: 16px; font-weight: 400; line-height: 16px; @@ -150,6 +151,7 @@ } .verificationBold { + font-family: Inter; color: var(--Greyscale-900, #2b2f33); font-weight: 700; margin-top: 8px; @@ -170,6 +172,7 @@ } .resendCode { + font-family: Inter; font-size: 12px; line-height: 16px; letter-spacing: -0.01em; @@ -178,6 +181,7 @@ } .resendCodeBold { + font-family: Inter; color: var(--Greyscale-900, #2b2f33); font-weight: 700; cursor: pointer; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 224069ba7..594972b5e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -547,6 +547,9 @@ importers: react-dom: specifier: 18.2.0 version: 18.2.0(react@18.2.0) + react-international-phone: + specifier: ^4.3.0 + version: 4.3.0(react@18.2.0) tweetnacl: specifier: ^1.0.3 version: 1.0.3 From 24f1df7d9cec891f01c94293c1bac575d276a36e Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Tue, 3 Dec 2024 17:27:29 -0500 Subject: [PATCH 48/73] add navbar, tonnes of styling updates --- .../src/app/components/Navbar.css | 15 ++ .../src/app/components/Navbar.tsx | 36 ++++ .../src/app/dashboard/dashboard.css | 17 +- .../src/app/dashboard/page.tsx | 198 +++++++++++------- examples/react-components/src/app/index.css | 6 +- examples/react-components/src/app/page.tsx | 32 ++- .../src/components/auth/Auth.module.css | 2 +- .../sdk-react/src/components/auth/Auth.tsx | 8 +- .../sdk-react/src/components/auth/Otp.css | 2 +- .../auth/OtpVerification.module.css | 2 +- .../src/components/auth/OtpVerification.tsx | 2 +- .../src/components/auth/Socials.module.css | 4 +- .../src/components/export/Export.module.css | 2 +- .../src/components/export/Export.tsx | 60 ++---- .../src/components/import/Import.module.css | 2 +- .../src/components/import/Import.tsx | 4 +- 16 files changed, 249 insertions(+), 143 deletions(-) create mode 100644 examples/react-components/src/app/components/Navbar.css create mode 100644 examples/react-components/src/app/components/Navbar.tsx diff --git a/examples/react-components/src/app/components/Navbar.css b/examples/react-components/src/app/components/Navbar.css new file mode 100644 index 000000000..53a120dd1 --- /dev/null +++ b/examples/react-components/src/app/components/Navbar.css @@ -0,0 +1,15 @@ +.navbar { + position: fixed; + top: 0; + left: 0; + width: 100%; + z-index: 1000; + background-color: black; + display: flex; + align-items: center; + height: 3rem; + padding: 1rem; + } + .navbarLogo { + padding-left: 24px; +} \ No newline at end of file diff --git a/examples/react-components/src/app/components/Navbar.tsx b/examples/react-components/src/app/components/Navbar.tsx new file mode 100644 index 000000000..b45d7b21e --- /dev/null +++ b/examples/react-components/src/app/components/Navbar.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import "./Navbar.css"; + +const Navbar = () => { + return ( + + ); +}; + +export default Navbar; diff --git a/examples/react-components/src/app/dashboard/dashboard.css b/examples/react-components/src/app/dashboard/dashboard.css index fc1f95aa7..27a5aec2f 100644 --- a/examples/react-components/src/app/dashboard/dashboard.css +++ b/examples/react-components/src/app/dashboard/dashboard.css @@ -21,7 +21,8 @@ display: flex; justify-content: center; align-items: center; - padding: 6rem; + padding: 8rem; + padding-bottom: 0rem; gap: 60px; position: relative; background-image: url("../../../public/grid.svg"); @@ -39,7 +40,7 @@ padding: 8px 24px; border-radius: 8px; width: 542px; - height: 642px; + height: 584px; border: 1px solid var(--Greyscale-100, #ebedf2); font-family: Arial, sans-serif; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); @@ -441,3 +442,15 @@ button:hover { opacity: 0; transition: opacity 0.3s ease; } +.passkeyContainer { + font-family: "Inter"; + font-style: normal; + font-weight: 400; +} +.passkeyIconContainer { + display: flex; + justify-content: center; + align-items: center; + gap: 8px; + margin: 16px 0; +} diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index 530aaa6cf..0735350c9 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -29,6 +29,8 @@ import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import LaunchIcon from '@mui/icons-material/Launch'; import { appleOidcToken, facebookOidcToken, googleOidcToken } from "../utils/oidc"; import { MuiPhone } from "../components/PhoneInput"; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import Navbar from "../components/Navbar"; export default function Dashboard() { const router = useRouter(); @@ -41,7 +43,8 @@ export default function Dashboard() { const [selectedWallet, setSelectedWallet] = useState(null) const [isModalOpen, setIsModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const [messageToSign, setMessageToSign] = useState("Signing within Turnkey Demo"); + const [isPasskeyModalOpen, setIsPasskeyModalOpen] = useState(false); + const [messageToSign, setMessageToSign] = useState("Signing within Turnkey Demo."); const [signature, setSignature] = useState(null); const [suborgId, setSuborgId] = useState("") const [user, setUser] = useState("") @@ -188,12 +191,6 @@ const handlePhoneSubmit = async () => { } } - const handleDeletePasskey = async () => { - const authenticators = await authIframeClient?.getAuthenticators({organizationId: suborgId, userId: user.userId}) - const authenticatorId = authenticators?.authenticators[0].authenticatorId - await authIframeClient?.deleteAuthenticators({organizationId: suborgId, userId: user.userId, authenticatorIds:[authenticatorId!]}) - window.location.reload(); - } const handleDropdownClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); @@ -204,7 +201,7 @@ const handlePhoneSubmit = async () => { }; const handleDeleteAccount: any = async () => { - authIframeClient?.deleteSubOrganization({organizationId: suborgId, deleteWithoutExport: true}) + await authIframeClient?.deleteSubOrganization({organizationId: suborgId, deleteWithoutExport: true}) await handleLogout() } @@ -322,7 +319,7 @@ useEffect(() => { ? "HASH_FUNCTION_NO_OP" : "HASH_FUNCTION_NOT_APPLICABLE", }); - setMessageSigningResult("✔ Success! Message signed") + setMessageSigningResult("Success! Message signed") setSignature({ r: resp?.r, s: resp?.s, v: resp?.v }); } catch (error) { console.error("Error signing message:", error); @@ -350,16 +347,14 @@ useEffect(() => { setMessageSigningResult( verificationPassed - ? `Verified! The address used to sign the message matches your wallet address: ${`${selectedAccount!.slice( - 0, - 5 - )}...${selectedAccount!.slice(-5)}`}` - : "Verification failed" + ? "Verified! The address used to sign the message matches your wallet address." + : "Verification failed." ); }; return (
+
@@ -376,11 +371,9 @@ useEffect(() => { )}
{user && user.userEmail ? ( -
- -
) : (
@@ -398,11 +391,9 @@ useEffect(() => { )}
{user && user.userPhoneNumber ? ( -
- -
+ ) : (
@@ -415,18 +406,13 @@ useEffect(() => {
Passkey - {user && user.authenticators && user.authenticators.length > 0 && ( - {user.authenticators[0].credentialId} - )}
{user && user.authenticators && user.authenticators.length > 0 ? ( -
handleDeletePasskey()}> - -
+ ) : ( -
handleAddPasskey()}> +
setIsPasskeyModalOpen(true)}>
)} @@ -448,11 +434,9 @@ useEffect(() => { {user && user.oauthProviders && user.oauthProviders.some( (provider: { issuer: string; }) => provider.issuer.toLowerCase().includes("google") ) ? ( -
handleDeleteOauth("Google")}> - -
+ ) : (
handleAddOauth("Google")} > @@ -467,11 +451,9 @@ useEffect(() => { {user && user.oauthProviders && user.oauthProviders.some( (provider: { issuer: string; }) => provider.issuer.toLowerCase().includes("apple") ) ? ( -
handleDeleteOauth("Apple")}> - -
+ ) : (
handleAddOauth("Apple")}> @@ -486,11 +468,9 @@ useEffect(() => { {user && user.oauthProviders && user.oauthProviders.some( (provider: { issuer: string; }) => provider.issuer.toLowerCase().includes("facebook") ) ? ( -
handleDeleteOauth("Facebook")}> - -
+ ) : (
handleAddOauth("Facebook")}> @@ -522,6 +502,11 @@ useEffect(() => { anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleDropdownClose} + sx={{ + '& .MuiPaper-root': { + width: '112px', + }, + }} > {wallets.map((wallet) => ( { color: "#6C727E", }} > - This helps prove you signed a message using your address + This helps prove you signed a message using your address. { }, }} /> - {signature ? - - : - } - {messageSigningResult && ( + {messageSigningResult && ( { {messageSigningResult} )} + {signature ? + messageSigningResult?.startsWith("Verif") ? + + : + + : + } { @@ -827,14 +823,14 @@ isEmailModalOpen && ×
- Enter new email + Connect email setEmailInput(e.target.value)} - placeholder="example@example.com" + placeholder="Enter your email" sx={{ bgcolor: "#ffffff", "& .MuiOutlinedInput-root": { @@ -896,9 +892,10 @@ isPhoneModalOpen && ×
- Enter new phone number + Connect phone } + +{ +isPasskeyModalOpen && + setIsPasskeyModalOpen(false)}> + +
setIsPasskeyModalOpen(false)} + style={{ + position: "absolute", + top: "16px", + right: "16px", + background: "none", + border: "none", + fontSize: "20px", + fontWeight: "bold", + cursor: "pointer", + color: "#6C727E", + }} + > + × +
+
+
+ +
+
+

Create a passkey

+
+
+
+Passkeys allow for easy biometric access to your wallet and can be synced across devices. +
+ +
+
+
+
+ +} +
); } + + + + diff --git a/examples/react-components/src/app/index.css b/examples/react-components/src/app/index.css index b55b13ecf..93115f7e1 100644 --- a/examples/react-components/src/app/index.css +++ b/examples/react-components/src/app/index.css @@ -21,7 +21,8 @@ display: flex; justify-content: center; align-items: center; - padding: 6rem; + padding: 8rem; + padding-bottom: 0rem; gap: 60px; position: relative; background-image: url("../../public/grid.svg"); @@ -42,7 +43,7 @@ padding: 32px; border-radius: 8px; width: 542px; - height: 642px; + height: 584px; border: 1px solid var(--Greyscale-100, #ebedf2); font-family: Arial, sans-serif; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); @@ -50,6 +51,7 @@ flex-direction: column; justify-content: space-between; } + .configTitle { font-size: 1.5rem; font-weight: 700; diff --git a/examples/react-components/src/app/page.tsx b/examples/react-components/src/app/page.tsx index a9b1d390f..97fc3862f 100644 --- a/examples/react-components/src/app/page.tsx +++ b/examples/react-components/src/app/page.tsx @@ -9,6 +9,7 @@ import CustomSwitch from "./components/Switch"; import { DragDropContext, Droppable, Draggable, DropResult } from "@hello-pangea/dnd"; import "./index.css"; import { useRouter } from "next/navigation"; +import Navbar from "./components/Navbar"; // Define types for config and socials interface SocialConfig { @@ -92,7 +93,7 @@ export default function AuthPage() { facebookEnabled: config.socials.facebook, }; navigator.clipboard.writeText(JSON.stringify(authConfig, null, 2)); - alert("Auth config copied to clipboard!"); + alert("Copied to clipboard!"); }; const authConfig = { @@ -117,6 +118,7 @@ export default function AuthPage() { return (
+
Authentication config @@ -128,7 +130,7 @@ export default function AuthPage() {
{configOrder.map((key, index) => ( key === "socials" ? ( - + {(provided) => (
) : ( - + {(provided) => ( -
-
- Drag handle - {key.charAt(0).toUpperCase() + key.slice(1)} -
- toggleConfig(key as keyof Config)} - /> -
+
+
+ Drag handle + {key.charAt(0).toUpperCase() + key.slice(1)} +
+ toggleConfig(key as keyof Config)} + /> +
)}
) diff --git a/packages/sdk-react/src/components/auth/Auth.module.css b/packages/sdk-react/src/components/auth/Auth.module.css index 9408c5f9a..41ae31857 100644 --- a/packages/sdk-react/src/components/auth/Auth.module.css +++ b/packages/sdk-react/src/components/auth/Auth.module.css @@ -100,7 +100,7 @@ } .authButton:hover { - background-color: #F4E8FF; + background-color: #F5F5F5; } .authButton:disabled { diff --git a/packages/sdk-react/src/components/auth/Auth.tsx b/packages/sdk-react/src/components/auth/Auth.tsx index 1895d875b..f864fa878 100644 --- a/packages/sdk-react/src/components/auth/Auth.tsx +++ b/packages/sdk-react/src/components/auth/Auth.tsx @@ -154,7 +154,7 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde } } catch { - setPasskeySignupError("Passkey request timed out or rejected by user. Please try again") + setPasskeySignupError("The operation either timed out or was not allowed. Please try again") } }; @@ -365,11 +365,11 @@ const Auth: React.FC = ({ onHandleAuthSuccess, authConfig, configOrde
-

Create a Passkey

+

Create a passkey

- Make your logins secure and easy. Set up with fingerprint, face, or screen lock. + Passkeys allow for easy biometric access to your wallet and can be synced across devices.
-

{passkeySignupError ? "Something went wrong" : passkeyCreated ? "Logging in with passkey" : "Creating passkey"}

+

{passkeySignupError ? "Authentication error" : passkeyCreated ? "Logging in with passkey" : "Creating passkey"}

diff --git a/packages/sdk-react/src/components/auth/Otp.css b/packages/sdk-react/src/components/auth/Otp.css index fa6e95740..bd74f4b89 100644 --- a/packages/sdk-react/src/components/auth/Otp.css +++ b/packages/sdk-react/src/components/auth/Otp.css @@ -100,7 +100,7 @@ } .authButton:hover { - background-color: #F4E8FF; + background-color: #F5F5F5; } .authButton:disabled { diff --git a/packages/sdk-react/src/components/auth/OtpVerification.module.css b/packages/sdk-react/src/components/auth/OtpVerification.module.css index 47dde0352..b918109f0 100644 --- a/packages/sdk-react/src/components/auth/OtpVerification.module.css +++ b/packages/sdk-react/src/components/auth/OtpVerification.module.css @@ -100,7 +100,7 @@ } .authButton:hover { - background-color: #F4E8FF; + background-color: #F5F5F5; } .authButton:disabled { diff --git a/packages/sdk-react/src/components/auth/OtpVerification.tsx b/packages/sdk-react/src/components/auth/OtpVerification.tsx index 708a09a24..77216989a 100644 --- a/packages/sdk-react/src/components/auth/OtpVerification.tsx +++ b/packages/sdk-react/src/components/auth/OtpVerification.tsx @@ -76,7 +76,7 @@ const OtpVerification: React.FC = ({
- Enter the 6-digit code we {type === "otpEmail" ? "emailed" : "texted"} to{" "} + Enter the 6-digit code we {type === "otpEmail" ? "emailed" : "sent"} to{" "} {type === "otpEmail" ? contact : formatPhoneNumber(contact)} diff --git a/packages/sdk-react/src/components/auth/Socials.module.css b/packages/sdk-react/src/components/auth/Socials.module.css index 5ce427c6f..caceaac98 100644 --- a/packages/sdk-react/src/components/auth/Socials.module.css +++ b/packages/sdk-react/src/components/auth/Socials.module.css @@ -33,11 +33,11 @@ } .socialButton:hover { - background-color: #F4E8FF; + background-color: #F5F5F5; } .iconButton:hover { - background-color: #F4E8FF; + background-color: #F5F5F5; } .iconSmall { diff --git a/packages/sdk-react/src/components/export/Export.module.css b/packages/sdk-react/src/components/export/Export.module.css index e2578ad57..eb8bf79f1 100644 --- a/packages/sdk-react/src/components/export/Export.module.css +++ b/packages/sdk-react/src/components/export/Export.module.css @@ -52,7 +52,7 @@ } .exportButton:hover { - background-color: #F4E8FF; + background-color: #F5F5F5; } .exportButton:disabled { diff --git a/packages/sdk-react/src/components/export/Export.tsx b/packages/sdk-react/src/components/export/Export.tsx index 9c5126ffc..7969ebd15 100644 --- a/packages/sdk-react/src/components/export/Export.tsx +++ b/packages/sdk-react/src/components/export/Export.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { Modal, Box, Typography } from "@mui/material"; import { useTurnkey } from "../../hooks/useTurnkey"; -import { IframeStamper } from "@turnkey/sdk-browser"; +import type { TurnkeyIframeClient } from "@turnkey/sdk-browser"; import styles from "./Export.module.css"; import unlockIcon from "assets/unlock.svg"; import eyeIcon from "assets/eye.svg"; @@ -15,8 +15,8 @@ type ExportProps = { const Export: React.FC = ({ walletId, }) => { - const { authIframeClient } = useTurnkey(); - const [exportIframeStamper, setExportIframeStamper] = useState(null); + const { authIframeClient, turnkey } = useTurnkey(); + const [exportIframeClient, setExportIframeClient] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const [isIframeVisible, setIsIframeVisible] = useState(false); const TurnkeyExportIframeContainerId = "turnkey-export-iframe-container-id"; @@ -37,29 +37,14 @@ const Export: React.FC = ({ if (!existingIframe) { try { - const iframeStamper = new IframeStamper({ - iframeContainer, - iframeUrl: "https://export.preprod.turnkey.engineering", - iframeElementId: TurnkeyIframeElementId, - }); - - await iframeStamper.init(); - const styles = { - padding: "20px", - fontFamily: "monospace", - color: "#333", - height: "240px", - width: "240px", - borderStyle: "none", - backgroundColor: "#ffffff", - overflowWrap: "break-word", - wordWrap: "break-word", - overflow: "hidden", - resize: "none", - }; - iframeStamper.applySettings({ styles }); - - setExportIframeStamper(iframeStamper); + const newExportIframeClient = await turnkey?.iframeClient({ + iframeContainer: document.getElementById( + TurnkeyExportIframeContainerId + ), + iframeUrl: + "https://export.preprod.turnkey.engineering", + }) + setExportIframeClient(newExportIframeClient!) console.log("IframeStamper initialized successfully."); } catch (error) { console.error("Error initializing IframeStamper:", error); @@ -70,9 +55,8 @@ const Export: React.FC = ({ const handleCloseModal = () => { // Clear the iframe stamper - if (exportIframeStamper) { - exportIframeStamper.clear(); - setExportIframeStamper(null); + if (exportIframeClient) { + setExportIframeClient(null); const existingIframe = document.getElementById(TurnkeyIframeElementId); if (existingIframe) { @@ -95,10 +79,10 @@ const Export: React.FC = ({ const exportResponse = await authIframeClient?.exportWallet({ organizationId: whoami.organizationId, walletId: walletId!, - targetPublicKey: exportIframeStamper!.iframePublicKey!, + targetPublicKey: exportIframeClient!.iframePublicKey!, }); if (exportResponse?.exportBundle) { - await exportIframeStamper?.injectWalletExportBundle( + await exportIframeClient?.injectWalletExportBundle( exportResponse.exportBundle, whoami.organizationId ); @@ -160,7 +144,7 @@ const Export: React.FC = ({
- Make sure nobody can see your screen when viewing your seed phrase + Make sure nobody can see your screen when viewing your seed phrase.
: @@ -190,11 +174,13 @@ Your seed phrase is the key to your wallet. Save it in a secure location. style={{ display: isIframeVisible ? "block" : "none", backgroundColor: "#ffffff", - boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06)", - borderStyle: "none", - overflow: "hidden", - borderRadius: "16px", - padding: "16px", + width: "100%", + boxSizing: "border-box", + padding: "20px", + borderStyle: "solid", + borderWidth: "1px", + borderRadius: "8px", + borderColor: "rgba(216, 219, 227, 1)", }} /> {isIframeVisible && ( diff --git a/packages/sdk-react/src/components/import/Import.module.css b/packages/sdk-react/src/components/import/Import.module.css index a8400348a..99b1b5e7d 100644 --- a/packages/sdk-react/src/components/import/Import.module.css +++ b/packages/sdk-react/src/components/import/Import.module.css @@ -47,7 +47,7 @@ } .importButton:hover { - background-color: #F4E8FF; + background-color: #F5F5F5; } .importButton:disabled { diff --git a/packages/sdk-react/src/components/import/Import.tsx b/packages/sdk-react/src/components/import/Import.tsx index 7cb69fa31..7fa0383af 100644 --- a/packages/sdk-react/src/components/import/Import.tsx +++ b/packages/sdk-react/src/components/import/Import.tsx @@ -162,7 +162,7 @@ const Import: React.FC = ({
- Import Wallet + Import wallet = ({ mb: 2, }} > - Enter your seed phrase. Seed phrases are typically 12 - 24 words + Enter your seed phrase. Seed phrases are typically 12-24 words. From a6324402d97697b19fac9a02764d806505207da9 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Tue, 3 Dec 2024 18:34:17 -0500 Subject: [PATCH 49/73] properly styled iframe export and imports --- .../src/components/export/Export.module.css | 6 +- .../src/components/export/Export.tsx | 3 +- .../src/components/import/Import.module.css | 4 ++ .../src/components/import/Import.tsx | 63 +++++++------------ 4 files changed, 34 insertions(+), 42 deletions(-) diff --git a/packages/sdk-react/src/components/export/Export.module.css b/packages/sdk-react/src/components/export/Export.module.css index eb8bf79f1..82d36db06 100644 --- a/packages/sdk-react/src/components/export/Export.module.css +++ b/packages/sdk-react/src/components/export/Export.module.css @@ -100,4 +100,8 @@ .poweredBy span { position: relative; - } \ No newline at end of file + } + iframe { + box-sizing: border-box; + border: 0 solid #000; +} \ No newline at end of file diff --git a/packages/sdk-react/src/components/export/Export.tsx b/packages/sdk-react/src/components/export/Export.tsx index 7969ebd15..469bff1a3 100644 --- a/packages/sdk-react/src/components/export/Export.tsx +++ b/packages/sdk-react/src/components/export/Export.tsx @@ -42,10 +42,9 @@ const Export: React.FC = ({ TurnkeyExportIframeContainerId ), iframeUrl: - "https://export.preprod.turnkey.engineering", + "https://export.turnkey.com", }) setExportIframeClient(newExportIframeClient!) - console.log("IframeStamper initialized successfully."); } catch (error) { console.error("Error initializing IframeStamper:", error); } diff --git a/packages/sdk-react/src/components/import/Import.module.css b/packages/sdk-react/src/components/import/Import.module.css index 99b1b5e7d..6133b88a5 100644 --- a/packages/sdk-react/src/components/import/Import.module.css +++ b/packages/sdk-react/src/components/import/Import.module.css @@ -75,3 +75,7 @@ .poweredBy span { position: relative; } + iframe { + box-sizing: border-box; + border: 0 solid #000; +} \ No newline at end of file diff --git a/packages/sdk-react/src/components/import/Import.tsx b/packages/sdk-react/src/components/import/Import.tsx index 7fa0383af..a5cfca4c4 100644 --- a/packages/sdk-react/src/components/import/Import.tsx +++ b/packages/sdk-react/src/components/import/Import.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { Modal, Box, Typography, TextField } from "@mui/material"; import { useTurnkey } from "../../hooks/useTurnkey"; -import { DEFAULT_ETHEREUM_ACCOUNTS, DEFAULT_SOLANA_ACCOUNTS, IframeStamper } from "@turnkey/sdk-browser"; +import { DEFAULT_ETHEREUM_ACCOUNTS, DEFAULT_SOLANA_ACCOUNTS, TurnkeyIframeClient } from "@turnkey/sdk-browser"; import styles from "./Import.module.css"; import turnkeyIcon from "assets/turnkey.svg"; import importIcon from "assets/import.svg" @@ -12,8 +12,8 @@ type ImportProps = { const Import: React.FC = ({ onSuccess = () => undefined, }) => { - const { authIframeClient } = useTurnkey(); - const [importIframeStamper, setImportIframeStamper] = useState(null); + const { authIframeClient, turnkey } = useTurnkey(); + const [importIframeClient, setImportIframeClient] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const [walletName, setWalletName] = useState(""); const TurnkeyImportIframeContainerId = "turnkey-import-iframe-container-id"; @@ -33,30 +33,15 @@ const Import: React.FC = ({ if (!existingIframe) { try { - const iframeStamper = new IframeStamper({ - iframeContainer, - iframeUrl: "https://import.preprod.turnkey.engineering", - iframeElementId: TurnkeyIframeElementId, - }); - - await iframeStamper.init(); - const styles = { - padding: "20px", - fontFamily: "monospace", - color: "#333", - height: "240px", - width: "240px", - borderStyle: "none", - backgroundColor: "#ffffff", - overflowWrap: "break-word", - overflow: "hidden", - wordWrap: "break-word", - resize: "none", - }; - iframeStamper.applySettings({ styles }); - - setImportIframeStamper(iframeStamper); - console.log("IframeStamper initialized successfully."); + + const newImportIframeClient = await turnkey?.iframeClient({ + iframeContainer: document.getElementById( + TurnkeyImportIframeContainerId + ), + iframeUrl: + "https://import.turnkey.com", + }) + setImportIframeClient(newImportIframeClient!) } catch (error) { console.error("Error initializing IframeStamper:", error); } @@ -65,9 +50,8 @@ const Import: React.FC = ({ }; const handleCloseModal = () => { - if (importIframeStamper) { - importIframeStamper.clear(); - setImportIframeStamper(null); + if (importIframeClient) { + setImportIframeClient(null); const existingIframe = document.getElementById(TurnkeyIframeElementId); if (existingIframe) { @@ -80,7 +64,7 @@ const Import: React.FC = ({ const handleImport = async () => { const whoami = await authIframeClient!.getWhoami(); - if (!importIframeStamper) { + if (!importIframeClient) { console.error("IframeStamper is not initialized."); return; } @@ -88,7 +72,7 @@ const Import: React.FC = ({ organizationId: whoami.organizationId, userId: whoami.userId, }); - const injected = await importIframeStamper!.injectImportBundle( + const injected = await importIframeClient!.injectImportBundle( initResult.importBundle, whoami.organizationId, whoami.userId @@ -97,7 +81,7 @@ const Import: React.FC = ({ console.error("error injecting import bundle") return; } - const encryptedBundle = await importIframeStamper.extractWalletEncryptedBundle(); + const encryptedBundle = await importIframeClient.extractWalletEncryptedBundle(); if (!encryptedBundle || encryptedBundle.trim() === "") { console.error("failed to retrieve encrypted bundle.") return; @@ -180,12 +164,13 @@ const Import: React.FC = ({ style={{ display: "block", backgroundColor: "#ffffff", - boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.06)", - borderStyle: "none", - overflow: "hidden", - borderRadius: "16px", - padding: "16px", - marginBottom: "16px" + width: "100%", + boxSizing: "border-box", + padding: "20px", + borderStyle: "solid", + borderWidth: "1px", + borderRadius: "8px", + borderColor: "rgba(216, 219, 227, 1)", }} /> From 3d1140331831538634e5bc29825dcdce06002536 Mon Sep 17 00:00:00 2001 From: Mohammad Cheikh Date: Tue, 3 Dec 2024 19:53:15 -0500 Subject: [PATCH 50/73] fix import scroll on iframe --- packages/sdk-react/src/components/export/Export.tsx | 2 +- .../src/components/import/Import.module.css | 2 ++ packages/sdk-react/src/components/import/Import.tsx | 12 ++++++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/sdk-react/src/components/export/Export.tsx b/packages/sdk-react/src/components/export/Export.tsx index 469bff1a3..b6c33a328 100644 --- a/packages/sdk-react/src/components/export/Export.tsx +++ b/packages/sdk-react/src/components/export/Export.tsx @@ -20,7 +20,7 @@ const Export: React.FC = ({ const [isModalOpen, setIsModalOpen] = useState(false); const [isIframeVisible, setIsIframeVisible] = useState(false); const TurnkeyExportIframeContainerId = "turnkey-export-iframe-container-id"; - const TurnkeyIframeElementId = "turnkey-export-iframe-element-id"; + const TurnkeyIframeElementId = "turnkey-default-iframe-element-id"; const handleOpenModal = async () => { setIsModalOpen(true); diff --git a/packages/sdk-react/src/components/import/Import.module.css b/packages/sdk-react/src/components/import/Import.module.css index 6133b88a5..0404bc91c 100644 --- a/packages/sdk-react/src/components/import/Import.module.css +++ b/packages/sdk-react/src/components/import/Import.module.css @@ -78,4 +78,6 @@ iframe { box-sizing: border-box; border: 0 solid #000; + width: 100%; + height: 100%; } \ No newline at end of file diff --git a/packages/sdk-react/src/components/import/Import.tsx b/packages/sdk-react/src/components/import/Import.tsx index a5cfca4c4..659234697 100644 --- a/packages/sdk-react/src/components/import/Import.tsx +++ b/packages/sdk-react/src/components/import/Import.tsx @@ -17,7 +17,7 @@ const Import: React.FC = ({ const [isModalOpen, setIsModalOpen] = useState(false); const [walletName, setWalletName] = useState(""); const TurnkeyImportIframeContainerId = "turnkey-import-iframe-container-id"; - const TurnkeyIframeElementId = "turnkey-import-iframe-element-id"; + const TurnkeyIframeElementId = "turnkey-default-iframe-element-id"; const handleOpenModal = async () => { setIsModalOpen(true); @@ -124,7 +124,7 @@ const Import: React.FC = ({ boxShadow: 24, p: 4, borderRadius: 2, - width: "336px" + width: "366px" }} > {/* Close Button */} @@ -162,18 +162,22 @@ const Import: React.FC = ({
- +{/*
+