diff --git a/examples/react-components/src/app/dashboard/page.tsx b/examples/react-components/src/app/dashboard/page.tsx index c9b5717d..846c42eb 100644 --- a/examples/react-components/src/app/dashboard/page.tsx +++ b/examples/react-components/src/app/dashboard/page.tsx @@ -71,6 +71,13 @@ export default function Dashboard() { const [emailInput, setEmailInput] = useState(""); const [phoneInput, setPhoneInput] = useState(""); + const handleExportSuccess = async () => { + toast.success("Wallet successfully exported"); + }; + const handleImportSuccess = async () => { + await getWallets(); + toast.success("Wallet successfully imported"); + }; const handleResendEmail = async () => { const initAuthResponse = await authIframeClient?.initOtpAuth({ organizationId: suborgId, @@ -268,12 +275,11 @@ export default function Dashboard() { try { if (turnkey && authIframeClient) { const session = await turnkey?.getReadWriteSession(); - console.log(session); - if (!session || Date.now() > session!.sessionExpiry) { + if (!session || Date.now() > session!.expiry) { await handleLogout(); } await authIframeClient.injectCredentialBundle( - session.credentialBundle + session!.credentialBundle ); const whoami = await authIframeClient?.getWhoami(); const suborgId = whoami?.organizationId; @@ -285,7 +291,6 @@ export default function Dashboard() { }); setUser(userResponse.user); - console.log(userResponse.user); const walletsResponse = await authIframeClient!.getWallets({ organizationId: suborgId!, }); @@ -654,8 +659,16 @@ export default function Dashboard() {
- - + toast.error(errorMessage)} + > + toast.error(errorMessage)} + onHandleImportSuccess={handleImportSuccess} + />
diff --git a/examples/react-components/src/app/layout.tsx b/examples/react-components/src/app/layout.tsx index f73ac3e7..6ae06089 100644 --- a/examples/react-components/src/app/layout.tsx +++ b/examples/react-components/src/app/layout.tsx @@ -18,6 +18,10 @@ interface RootLayoutProps { function RootLayout({ children }: RootLayoutProps) { return ( + + Turnkey Auth Demo + + {children} diff --git a/packages/sdk-react/src/components/export/Export.module.css b/packages/sdk-react/src/components/export/Export.module.css index 497ee6e3..77c03ab8 100644 --- a/packages/sdk-react/src/components/export/Export.module.css +++ b/packages/sdk-react/src/components/export/Export.module.css @@ -4,6 +4,7 @@ font-weight: 600; letter-spacing: -0.01em; text-align: left; + color: var(--text-primary); } .exportCard { @@ -14,10 +15,10 @@ 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); + background: var(--auth-card-bg); + border: 1px solid var(--auth-card-border); + border-radius: var(--auth-card-radius); + box-shadow: var(--auth-card-shadow); } .exportCard h2 { @@ -28,6 +29,7 @@ text-align: center; font-size: 1.5rem; margin-bottom: 16px; + color: var(--text-primary); } .doneButtonContainer { @@ -40,24 +42,24 @@ justify-content: center; padding: 10px 16px; gap: 8px; - color: var(--Greyscale-900, #2b2f33); + color: var(--button-text); width: 100%; font-size: 1rem; - background: #ffffff; - border: 1px solid var(--Greyscale-400, #a2a7ae); + background: var(--button-bg); + border: 1px solid var(--button-border); border-radius: 8px; cursor: pointer; transition: background-color 0.3s ease; } .exportButton:hover { - background-color: #f5f5f5; + background-color: var(--button-hover-bg); } .exportButton:disabled { - color: var(--Greyscale-700, #a2a7ae); - background: #ffffff; - border-color: var(--Greyscale-100, #f5f7fb); + color: var(--button-disabled-text); + background: var(--button-disabled-bg); + border-color: var(--button-disabled-border); cursor: default; } @@ -71,6 +73,7 @@ width: 95%; margin-right: auto; margin-bottom: 32px; + color: var(--text-primary); } .row { @@ -92,7 +95,7 @@ font-weight: 400; line-height: 16px; letter-spacing: -0.01em; - color: var(--Greyscale-500, #868c95); + color: var(--text-secondary); margin-top: 16px; cursor: pointer; } @@ -100,7 +103,8 @@ .poweredBy span { position: relative; } + iframe { box-sizing: border-box; - border: 0 solid #000; + border: 0 solid var(--border-default); } diff --git a/packages/sdk-react/src/components/export/Export.tsx b/packages/sdk-react/src/components/export/Export.tsx index cff366df..1b7b62aa 100644 --- a/packages/sdk-react/src/components/export/Export.tsx +++ b/packages/sdk-react/src/components/export/Export.tsx @@ -8,11 +8,18 @@ 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; + onHandleExportSuccess: () => Promise; + onError: (errorMessage: string) => void; }; -const Export: React.FC = ({ walletId }) => { +const Export: React.FC = ({ + walletId, + onHandleExportSuccess, + onError, +}) => { const { authIframeClient, turnkey } = useTurnkey(); const [exportIframeClient, setExportIframeClient] = useState(null); @@ -74,17 +81,28 @@ const Export: React.FC = ({ walletId }) => { }; const exportWallet = async () => { - const whoami = await authIframeClient!.getWhoami(); - const exportResponse = await authIframeClient?.exportWallet({ - organizationId: whoami.organizationId, - walletId: walletId!, - targetPublicKey: exportIframeClient!.iframePublicKey!, - }); - if (exportResponse?.exportBundle) { + try { + const whoami = await authIframeClient!.getWhoami(); + + const exportResponse = await authIframeClient?.exportWallet({ + organizationId: whoami.organizationId, + walletId: walletId!, + targetPublicKey: exportIframeClient!.iframePublicKey!, + }); + + if (!exportResponse?.exportBundle) { + throw new Error("Failed to retrieve export bundle"); + } + await exportIframeClient?.injectWalletExportBundle( exportResponse.exportBundle, whoami.organizationId ); + + onHandleExportSuccess(); + } catch (error) { + console.error("Error during wallet export:", error); + onError("Failed to export wallet"); } }; diff --git a/packages/sdk-react/src/components/import/Import.module.css b/packages/sdk-react/src/components/import/Import.module.css index 33fe1923..38b9a406 100644 --- a/packages/sdk-react/src/components/import/Import.module.css +++ b/packages/sdk-react/src/components/import/Import.module.css @@ -4,6 +4,7 @@ font-weight: 600; letter-spacing: -0.01em; text-align: left; + color: var(--text-primary); } .importCard { @@ -14,10 +15,10 @@ 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); + background: var(--auth-card-bg); + border: 1px solid var(--auth-card-border); + border-radius: var(--auth-card-radius); + box-shadow: var(--auth-card-shadow); } .importCard h2 { @@ -28,6 +29,7 @@ text-align: center; font-size: 1.5rem; margin-bottom: 16px; + color: var(--text-primary); } .importButton { @@ -36,24 +38,24 @@ justify-content: center; padding: 10px 16px; gap: 8px; - color: var(--Greyscale-900, #2b2f33); + color: var(--button-text); width: 100%; font-size: 1rem; - background: #ffffff; - border: 1px solid var(--Greyscale-400, #a2a7ae); + background: var(--button-bg); + border: 1px solid var(--button-border); border-radius: 8px; cursor: pointer; transition: background-color 0.3s ease; } .importButton:hover { - background-color: #f5f5f5; + background-color: var(--button-hover-bg); } .importButton:disabled { - color: var(--Greyscale-700, #a2a7ae); - background: #ffffff; - border-color: var(--Greyscale-100, #f5f7fb); + color: var(--button-disabled-text); + background: var(--button-disabled-bg); + border-color: var(--button-disabled-border); cursor: default; } @@ -67,7 +69,7 @@ font-weight: 400; line-height: 16px; letter-spacing: -0.01em; - color: var(--Greyscale-500, #868c95); + color: var(--text-secondary); margin-top: 16px; cursor: pointer; } @@ -75,9 +77,10 @@ .poweredBy span { position: relative; } + iframe { box-sizing: border-box; - border: 0 solid #000; + border: 0 solid var(--border-default); width: 100%; height: 100%; } diff --git a/packages/sdk-react/src/components/import/Import.tsx b/packages/sdk-react/src/components/import/Import.tsx index b171e135..b91ace6a 100644 --- a/packages/sdk-react/src/components/import/Import.tsx +++ b/packages/sdk-react/src/components/import/Import.tsx @@ -10,10 +10,11 @@ import styles from "./Import.module.css"; import turnkeyIcon from "assets/turnkey.svg"; import importIcon from "assets/import.svg"; type ImportProps = { - onSuccess?: () => void; + onError: (errorMessage: string) => void; + onHandleImportSuccess: () => Promise; }; -const Import: React.FC = ({ onSuccess = () => undefined }) => { +const Import: React.FC = ({ onHandleImportSuccess, onError }) => { const { authIframeClient, turnkey } = useTurnkey(); const [importIframeClient, setImportIframeClient] = useState(null); @@ -66,44 +67,51 @@ const Import: React.FC = ({ onSuccess = () => undefined }) => { }; const handleImport = async () => { - const whoami = await authIframeClient!.getWhoami(); - if (!importIframeClient) { - console.error("IframeStamper is not initialized."); - return; - } - const initResult = await authIframeClient!.initImportWallet({ - organizationId: whoami.organizationId, - userId: whoami.userId, - }); - const injected = await importIframeClient!.injectImportBundle( - initResult.importBundle, - whoami.organizationId, - whoami.userId - ); - if (!injected) { - console.error("error injecting import bundle"); - return; - } - const encryptedBundle = - await importIframeClient.extractWalletEncryptedBundle(); - if (!encryptedBundle || encryptedBundle.trim() === "") { - console.error("failed to retrieve encrypted bundle."); - return; - } - const response = await authIframeClient?.importWallet({ - organizationId: whoami.organizationId, - userId: whoami.userId, - walletName: walletName, - encryptedBundle, - accounts: [...DEFAULT_ETHEREUM_ACCOUNTS, ...DEFAULT_SOLANA_ACCOUNTS], - }); + try { + const whoami = await authIframeClient!.getWhoami(); + if (!importIframeClient) { + throw new Error("Import iframe client not initialized"); + } - if (response) { - console.log("Wallet imported successfully!"); - handleCloseModal(); - onSuccess(); - } else { - console.error("Failed to import wallet"); + const initResult = await authIframeClient!.initImportWallet({ + organizationId: whoami.organizationId, + userId: whoami.userId, + }); + + const injected = await importIframeClient!.injectImportBundle( + initResult.importBundle, + whoami.organizationId, + whoami.userId + ); + + if (!injected) { + throw new Error("Failed to inject import bundle"); + } + + const encryptedBundle = + await importIframeClient.extractWalletEncryptedBundle(); + + if (!encryptedBundle || encryptedBundle.trim() === "") { + throw new Error("Encrypted wallet bundle is empty or invalid"); + } + + const response = await authIframeClient?.importWallet({ + organizationId: whoami.organizationId, + userId: whoami.userId, + walletName: walletName, + encryptedBundle, + accounts: [...DEFAULT_ETHEREUM_ACCOUNTS, ...DEFAULT_SOLANA_ACCOUNTS], + }); + + if (response?.walletId) { + handleCloseModal(); + onHandleImportSuccess(); + } else { + throw new Error("Failed to import wallet"); + } + } catch (error) { + console.error("Error during wallet import:", error); + onError("Failed to import wallet"); } }; diff --git a/packages/sdk-react/src/components/theme.css b/packages/sdk-react/src/components/theme.css new file mode 100644 index 00000000..aeae08b1 --- /dev/null +++ b/packages/sdk-react/src/components/theme.css @@ -0,0 +1,73 @@ +/* Define the font-face rules */ +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("./assets/fonts/Inter-Regular.woff2") format("woff2"); +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url("./assets/fonts/Inter-SemiBold.woff2?v=3.19") format("woff2"); +} + +:root { + /* Button Colors */ + --button-bg: #ffffff; /* Default button background */ + --button-text: var(--Greyscale-900, #2b2f33); /* Default button text */ + --button-border: var(--Greyscale-400, #a2a7ae); /* Default button border */ + --button-hover-bg: #f5f5f5; /* Hover button background */ + --button-hover-text: var(--Greyscale-900, #2b2f33); /* Hover button text */ + --button-disabled-bg: #ffffff; /* Disabled button background */ + --button-disabled-text: var( + --Greyscale-700, + #a2a7ae + ); /* Disabled button text */ + --button-disabled-border: var( + --Greyscale-100, + #f5f7fb + ); /* Disabled button border */ + + /* Input Field Colors */ + --input-bg: #ffffff; /* Input field background */ + --input-text: var(--Greyscale-900, #2b2f33); /* Input text color */ + --input-border: var(--Greyscale-400, #d0d5dd); /* Input border color */ + --input-hover-border: var( + --Greyscale-500, + #868c95 + ); /* Input border on hover */ + --input-focus-border: var( + --Greyscale-900, + #2b2f33 + ); /* Input border on focus */ + + /* Text Colors */ + --text-primary: var(--Greyscale-900, #2b2f33); /* Primary text */ + --text-secondary: var(--Greyscale-500, #868c95); /* Secondary text */ + --text-disabled: var(--Greyscale-700, #a2a7ae); /* Disabled text */ + + /* Border Colors */ + --border-default: var(--Greyscale-400, #a2a7ae); /* Default border */ + --border-hover: var(--Greyscale-500, #868c95); /* Hover border */ + --border-disabled: var(--Greyscale-100, #f5f7fb); /* Disabled border */ + + /* Card Colors */ + --auth-card-bg: var( + --Greyscale-20, + #f5f7fb + ); /* Default authCard background */ + --auth-card-border: var( + --Greyscale-100, + #ebedf2 + ); /* Default authCard border */ + --auth-card-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* Default authCard shadow */ + --auth-card-radius: 15px; /* Default authCard border radius */ + + /* Accent Colors */ + --accent-color: var(--Blue-500, #4c48ff); + --error-color: #ff4c4c; +} diff --git a/packages/sdk-react/src/index.ts b/packages/sdk-react/src/index.ts index be9a828b..a9c1b54c 100644 --- a/packages/sdk-react/src/index.ts +++ b/packages/sdk-react/src/index.ts @@ -3,7 +3,7 @@ import "./components/auth/OtpVerification.module.css"; import "./components/auth/PhoneInput.css"; import "./components/export/Export.module.css"; import "./components/import/Import.module.css"; -import "./components/auth/theme.css"; +import "./components/theme.css"; import { TurnkeyContext, TurnkeyProvider } from "./contexts/TurnkeyContext"; import { useTurnkey } from "./hooks/use-turnkey"; export * from "./components";