Repository: Crossmint/crossmint-sdk
Pull request #1111: React web: NCS ui tweaks
Author: login: jmderby
Created: 2025-05-08T21:58:46Z | Merged: 2025-05-08T22:11:25Z

## Description

Tweak React web NCS UI to align with spec

## Test plan

manual tested 

## Package updates

@crossmint/client-sdk-react-ui


Diff:
diff --git a/packages/client/ui/react-ui/src/components/signers/EmailConfirmation.tsx b/packages/client/ui/react-ui/src/components/signers/EmailConfirmation.tsx
new file mode 100644
index 000000000..b44a6981d
--- /dev/null
+++ b/packages/client/ui/react-ui/src/components/signers/EmailConfirmation.tsx
@@ -0,0 +1,93 @@
+import { useState } from "react";
+import type { UIConfig } from "@crossmint/common-sdk-base";
+
+import { Spinner } from "@/components/common/Spinner";
+import { classNames } from "@/utils/classNames";
+
+interface EmailInputProps {
+    email: string;
+    onConfirm: (email: string) => Promise<void>;
+    onCancel?: () => void;
+    appearance?: UIConfig;
+}
+
+export function EmailConfirmation({ email, onConfirm, onCancel, appearance }: EmailInputProps) {
+    const [isLoading, setIsLoading] = useState(false);
+    const [error, setError] = useState("");
+
+    const handleSendCode = async () => {
+        setIsLoading(true);
+        try {
+            await onConfirm(email);
+        } catch (error) {
+            console.error("Error sending authorization code:", error);
+            setError("Failed to send code. Please try again.");
+        } finally {
+            setIsLoading(false);
+        }
+    };
+    const accentColor = appearance?.colors?.accent || "#4CAF50";
+    const textColor = appearance?.colors?.textPrimary || "#000000";
+    const borderColor = appearance?.colors?.border || "#E0E0E0";
+
+    return (
+        <div className="flex flex-col w-full max-w-md mx-auto rounded-xl">
+            <h2 className="mb-2" style={{ color: textColor }}>
+                Send authorization code to
+            </h2>
+
+            <div className="w-full mb-8 p-4 rounded-xl border" style={{ borderColor }}>
+                <div className="flex items-center">
+                    <div className="flex-shrink-0 mr-3">
+                        <svg
+                            xmlns="http://www.w3.org/2000/svg"
+                            width="24"
+                            height="24"
+                            viewBox="0 0 24 24"
+                            fill="none"
+                            stroke="currentColor"
+                            strokeWidth="2"
+                            strokeLinecap="round"
+                            strokeLinejoin="round"
+                            className="lucide lucide-mail-icon lucide-mail"
+                        >
+                            <path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" />
+                            <rect x="2" y="4" width="20" height="16" rx="2" />
+                        </svg>
+                    </div>
+                    <span style={{ color: textColor }}>{email}</span>
+                </div>
+            </div>
+
+            {error && <div className="text-red-500 mb-4 text-sm">{error}</div>}
+
+            <div className="flex gap-3">
+                <button
+                    onClick={onCancel}
+                    className="flex-1 py-4 px-6 border rounded-full text-center font-medium"
+                    style={{ borderColor }}
+                >
+                    Cancel
+                </button>
+
+                <button
+                    onClick={handleSendCode}
+                    disabled={isLoading}
+                    className={classNames(
+                        "flex-1 py-4 px-6 rounded-full text-white text-center font-medium",
+                        isLoading ? "opacity-70" : ""
+                    )}
+                    style={{ backgroundColor: accentColor }}
+                >
+                    {isLoading ? (
+                        <div className="flex justify-center items-center">
+                            <Spinner style={{ color: "white" }} />
+                        </div>
+                    ) : (
+                        "Send code"
+                    )}
+                </button>
+            </div>
+        </div>
+    );
+}
diff --git a/packages/client/ui/react-ui/src/components/signers/EmailInput.tsx b/packages/client/ui/react-ui/src/components/signers/EmailInput.tsx
deleted file mode 100644
index c7310b126..000000000
--- a/packages/client/ui/react-ui/src/components/signers/EmailInput.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import type React from "react";
-import { useState } from "react";
-import Color from "color";
-import type { UIConfig } from "@crossmint/common-sdk-base";
-import { isEmailValid } from "@crossmint/common-sdk-auth";
-
-import { Spinner } from "@/components/common/Spinner";
-import { classNames } from "@/utils/classNames";
-import { AlertIcon } from "@/icons/alert";
-
-interface EmailInputProps {
-    email: string;
-    setEmail: (email: string) => void;
-    onSubmitEmail: (email: string) => Promise<void>;
-    appearance?: UIConfig;
-}
-
-export function EmailInput({ email, setEmail, onSubmitEmail, appearance }: EmailInputProps) {
-    const [emailError, setEmailError] = useState("");
-    const [isLoading, setIsLoading] = useState(false);
-
-    const handleOnSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
-        e.preventDefault();
-        if (!isEmailValid(email)) {
-            return;
-        }
-
-        setIsLoading(true);
-        try {
-            await onSubmitEmail(email);
-        } catch (error) {
-            console.error("Error submitting email:", error);
-            setEmailError("Failed to submit email. Please try again.");
-        } finally {
-            setIsLoading(false);
-        }
-    };
-
-    return (
-        <>
-            <div className="flex flex-col items-start justify-start w-full rounded-lg">
-                <div className="w-full">
-                    <label
-                        className="text-cm-text-primary block mb-[5px]"
-                        style={{ color: appearance?.colors?.textPrimary }}
-                    >
-                        Email
-                    </label>
-                    <form
-                        role="form"
-                        className="relative"
-                        onSubmit={handleOnSubmit}
-                        noValidate // we want to handle validation ourselves
-                    >
-                        <label htmlFor="emailInput" className="sr-only">
-                            Email
-                        </label>
-                        <input
-                            className={classNames(
-                                "flex-grow text-cm-text-primary text-left pl-[16px] h-[58px] w-full border border-cm-border rounded-xl bg-cm-background-primary placeholder:text-md",
-                                "transition-none duration-200 ease-in-out",
-                                "focus:outline-none focus-ring-custom", // Add focus ring
-                                "placeholder:[color:var(--placeholder-color)]",
-                                emailError ? "border-red-500" : ""
-                            )}
-                            style={{
-                                color: appearance?.colors?.textPrimary,
-                                borderRadius: appearance?.borderRadius,
-                                borderColor: emailError ? appearance?.colors?.danger : appearance?.colors?.border,
-                                backgroundColor: appearance?.colors?.inputBackground,
-                                // @ts-expect-error Add custom placeholder & ring color to tailwind
-                                "--placeholder-color": appearance?.colors?.textSecondary ?? "#67797F",
-                                "--focus-ring-color": new Color(appearance?.colors?.accent ?? "#04AA6D")
-                                    .alpha(0.18)
-                                    .toString(),
-                            }}
-                            type="email"
-                            placeholder="your@email.com"
-                            value={email}
-                            onChange={(e) => {
-                                setEmail(e.target.value);
-                                setEmailError("");
-                            }}
-                            readOnly={isLoading}
-                            aria-describedby="emailError"
-                        />
-                        <div className="absolute inset-y-0 right-0 flex items-center pr-4">
-                            {emailError && <AlertIcon customColor={appearance?.colors?.danger} />}
-                            {isLoading ? (
-                                <Spinner
-                                    style={{
-                                        color: appearance?.colors?.textSecondary,
-                                        fill: appearance?.colors?.textPrimary,
-                                    }}
-                                />
-                            ) : (
-                                <button
-                                    type="submit"
-                                    className={classNames(
-                                        "font-medium text-cm-accent text-nowrap bg-cm-background-primary",
-                                        !isEmailValid(email) ? "!cursor-not-allowed opacity-60" : "cursor-pointer"
-                                    )}
-                                    style={{
-                                        color: appearance?.colors?.accent,
-                                        backgroundColor: appearance?.colors?.inputBackground,
-                                    }}
-                                    disabled={!isEmailValid(email) || isLoading}
-                                >
-                                    Submit
-                                </button>
-                            )}
-                        </div>
-                    </form>
-                    {emailError && <p className="text-xs text-red-500 mb-2 pt-2">{emailError}</p>}
-                </div>
-            </div>
-        </>
-    );
-}
diff --git a/packages/client/ui/react-ui/src/components/signers/EmailOTPInput.tsx b/packages/client/ui/react-ui/src/components/signers/EmailOTPInput.tsx
index 3afecd9f6..e11845186 100644
--- a/packages/client/ui/react-ui/src/components/signers/EmailOTPInput.tsx
+++ b/packages/client/ui/react-ui/src/components/signers/EmailOTPInput.tsx
@@ -3,17 +3,15 @@ import { CountdownButton } from "@/components/common/CountdownButton";
 import type { UIConfig } from "@crossmint/common-sdk-base";
 import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/common/InputOTP";
 import { EmailOtpIcon } from "@/icons/emailOTP";
-import { LeftArrowIcon } from "@/icons/leftArrow";
 
 interface EmailOTPInputProps {
-    email?: string;
+    email: string;
     onSubmitOTP: (token: string) => Promise<void>;
     onResendCode?: () => Promise<void>;
     appearance?: UIConfig;
-    handleOnBack: () => void;
 }
 
-export function EmailOTPInput({ email, onSubmitOTP, handleOnBack, onResendCode, appearance }: EmailOTPInputProps) {
+export function EmailOTPInput({ email, onSubmitOTP, onResendCode, appearance }: EmailOTPInputProps) {
     const [token, setToken] = useState("");
     const [hasError, setHasError] = useState(false);
     const [loading, setLoading] = useState(false);
@@ -35,91 +33,74 @@ export function EmailOTPInput({ email, onSubmitOTP, handleOnBack, onResendCode,
     };
 
     return (
-        <div>
-            <button
-                className={
-                    "absolute left-4 top-4 min-[480px]:!left-6 min-[480px]:!top-6 rounded-full opacity-70 ring-offset-background text-cm-text-primary transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-cm-accent focus:ring-offset-2 disabled:pointer-events-none"
-                }
-                onClick={() => {
-                    setError(null);
-                    handleOnBack();
-                    setToken("");
-                }}
+        <div className="flex flex-col items-center justify-start w-full">
+            <div className="relative left-3">
+                <EmailOtpIcon
+                    customAccentColor={appearance?.colors?.accent}
+                    customButtonBackgroundColor={appearance?.colors?.buttonBackground}
+                    customBackgroundColor={appearance?.colors?.background}
+                />
+            </div>
+            <p
+                className="text-base font-normal mt-4 mb-1 text-center text-cm-text-primary"
+                style={{ color: appearance?.colors?.textPrimary }}
             >
-                <LeftArrowIcon className="w-6 h-6" style={{ color: appearance?.colors?.textPrimary }} />
-            </button>
-            <div className="flex flex-col items-center justify-start w-full">
-                <div className="relative left-3">
-                    <EmailOtpIcon
-                        customAccentColor={appearance?.colors?.accent}
-                        customButtonBackgroundColor={appearance?.colors?.buttonBackground}
-                        customBackgroundColor={appearance?.colors?.background}
-                    />
-                </div>
-                <p
-                    className="text-base font-normal mt-4 mb-1 text-center text-cm-text-primary"
-                    style={{ color: appearance?.colors?.textPrimary }}
-                >
-                    Verify Recovery Key Creation
-                </p>
-                <p
-                    className="text-center text-cm-text-secondary px-4"
-                    style={{ color: appearance?.colors?.textSecondary }}
+                Check your email
+            </p>
+            <p className="text-center text-cm-text-secondary px-4" style={{ color: appearance?.colors?.textSecondary }}>
+                A temporary login code has been sent to {email}
+            </p>
+            <div className="py-8">
+                <InputOTP
+                    maxLength={6}
+                    value={token}
+                    onChange={(val) => {
+                        setToken(val);
+                        setHasError(false);
+                        setError(null);
+                    }}
+                    onComplete={handleOnSubmitOTP}
+                    disabled={loading}
+                    customStyles={{
+                        accent: appearance?.colors?.accent ?? "#04AA6D",
+                        danger: appearance?.colors?.danger ?? "#f44336",
+                        border: appearance?.colors?.border ?? "#E5E7EB",
+                        textPrimary: appearance?.colors?.textPrimary ?? "#909ca3",
+                        buttonBackground: appearance?.colors?.buttonBackground ?? "#eff6ff",
+                        inputBackground: appearance?.colors?.inputBackground ?? "#FFFFFF",
+                        borderRadius: appearance?.borderRadius,
+                    }}
                 >
-                    A verification code has been sent to {email ? email : "your email address"}
-                </p>
-                <div className="py-8">
-                    <InputOTP
-                        maxLength={6}
-                        value={token}
-                        onChange={(val) => {
-                            setToken(val);
-                            setHasError(false);
-                            setError(null);
-                        }}
-                        onComplete={handleOnSubmitOTP}
-                        disabled={loading}
-                        customStyles={{
-                            accent: appearance?.colors?.accent ?? "#04AA6D",
-                            danger: appearance?.colors?.danger ?? "#f44336",
-                            border: appearance?.colors?.border ?? "#E5E7EB",
-                            textPrimary: appearance?.colors?.textPrimary ?? "#909ca3",
-                            buttonBackground: appearance?.colors?.buttonBackground ?? "#eff6ff",
-                            inputBackground: appearance?.colors?.inputBackground ?? "#FFFFFF",
-                            borderRadius: appearance?.borderRadius,
-                        }}
-                    >
-                        <InputOTPGroup>
-                            <InputOTPSlot index={0} hasError={hasError} />
-                            <InputOTPSlot index={1} hasError={hasError} />
-                            <InputOTPSlot index={2} hasError={hasError} />
-                            <InputOTPSlot index={3} hasError={hasError} />
-                            <InputOTPSlot index={4} hasError={hasError} />
-                            <InputOTPSlot index={5} hasError={hasError} />
-                        </InputOTPGroup>
-                    </InputOTP>
-                </div>
-
-                {error && <div className="text-sm text-red-500 mb-4">{error}</div>}
+                    <InputOTPGroup>
+                        <InputOTPSlot index={0} hasError={hasError} />
+                        <InputOTPSlot index={1} hasError={hasError} />
+                        <InputOTPSlot index={2} hasError={hasError} />
+                        <InputOTPSlot index={3} hasError={hasError} />
+                        <InputOTPSlot index={4} hasError={hasError} />
+                        <InputOTPSlot index={5} hasError={hasError} />
+                    </InputOTPGroup>
+                </InputOTP>
+            </div>
 
-                <div className="text-xs leading-tight text-cm-text-secondary text-center">
-                    <span style={{ color: appearance?.colors?.textSecondary }}>
-                        Can't find the email? Check spam folder.
-                        {"\n"}
-                        Some emails may take several minutes to arrive.
-                    </span>
-                </div>
+            {error && <div className="text-sm text-red-500 mb-4">{error}</div>}
 
-                {onResendCode && (
-                    <CountdownButton
-                        initialSeconds={60}
-                        appearance={appearance}
-                        countdownText={(seconds) => `Re-send code in ${seconds}s`}
-                        countdownCompleteText="Re-send code"
-                        handleOnClick={onResendCode}
-                    />
-                )}
+            <div className="text-xs leading-tight text-cm-text-secondary text-center">
+                <span style={{ color: appearance?.colors?.textSecondary }}>
+                    Can't find the email? Check spam folder.
+                    {"\n"}
+                    Some emails may take several minutes to arrive.
+                </span>
             </div>
+
+            {onResendCode && (
+                <CountdownButton
+                    initialSeconds={60}
+                    appearance={appearance}
+                    countdownText={(seconds) => `Re-send code in ${seconds}s`}
+                    countdownCompleteText="Re-send code"
+                    handleOnClick={onResendCode}
+                />
+            )}
         </div>
     );
 }
diff --git a/packages/client/ui/react-ui/src/components/signers/EmailSignersDialog.tsx b/packages/client/ui/react-ui/src/components/signers/EmailSignersDialog.tsx
index c0e55445d..099247910 100644
--- a/packages/client/ui/react-ui/src/components/signers/EmailSignersDialog.tsx
+++ b/packages/client/ui/react-ui/src/components/signers/EmailSignersDialog.tsx
@@ -2,15 +2,13 @@ import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
 import type { UIConfig } from "@crossmint/common-sdk-base";
 import { Dialog, DialogContent, DialogDescription, DialogTitle } from "../common/Dialog";
 import { EmailOTPInput } from "./EmailOTPInput";
-import { EmailInput } from "./EmailInput";
+import { EmailConfirmation } from "./EmailConfirmation";
 
 interface EmailSignersDialogProps {
     email: string;
-    setEmail: (email: string) => void;
     open: boolean;
     setOpen: (open: boolean) => void;
     step: "initial" | "otp";
-    setStep: (step: "initial" | "otp") => void;
     onSubmitOTP: (token: string) => Promise<void>;
     onResendOTPCode: (email: string) => Promise<void>;
     onSubmitEmail: (email: string) => Promise<void>;
@@ -19,11 +17,9 @@ interface EmailSignersDialogProps {
 
 export function EmailSignersDialog({
     email,
-    setEmail,
     open,
     setOpen,
     step,
-    setStep,
     onSubmitOTP,
     onResendOTPCode,
     onSubmitEmail,
@@ -41,7 +37,7 @@ export function EmailSignersDialog({
                 }}
             >
                 <VisuallyHidden asChild>
-                    <DialogTitle>Crossmint Recovery Key</DialogTitle>
+                    <DialogTitle>Confirm it's you</DialogTitle>
                 </VisuallyHidden>
                 <VisuallyHidden asChild>
                     <DialogDescription>Create a recovery key</DialogDescription>
@@ -49,18 +45,19 @@ export function EmailSignersDialog({
 
                 <div className="relative pt-10 pb-[30px] px-6 !min-[480px]:px-10 flex flex-col gap-[10px] antialiased animate-none max-w-[448px]">
                     {step === "initial" ? (
-                        <div>
+                        <div className="flex flex-col gap-4">
                             <h1
                                 className="text-2xl font-bold text-cm-text-primary"
                                 style={{ color: appearance?.colors?.textPrimary }}
                             >
-                                Create Recovery Key
+                                Confirm it's you
                             </h1>
                             <p
                                 className="text-base font-normal mb-3 text-cm-text-secondary"
                                 style={{ color: appearance?.colors?.textSecondary }}
                             >
-                                Enter your email to create a recovery key
+                                You're using this wallet for the first time on this device. Click 'Send code' to get a
+                                one-time verification code.
                             </p>
                         </div>
                     ) : null}
@@ -69,15 +66,14 @@ export function EmailSignersDialog({
                         <EmailOTPInput
                             email={email}
                             onSubmitOTP={onSubmitOTP}
-                            handleOnBack={() => setStep("initial")}
                             onResendCode={() => onResendOTPCode(email)}
                             appearance={appearance}
                         />
                     ) : (
-                        <EmailInput
+                        <EmailConfirmation
                             email={email}
-                            setEmail={setEmail}
-                            onSubmitEmail={onSubmitEmail}
+                            onConfirm={onSubmitEmail}
+                            onCancel={() => setOpen(false)}
                             appearance={appearance}
                         />
                     )}
diff --git a/packages/client/ui/react-ui/src/providers/signers/CrossmintSignerProvider.tsx b/packages/client/ui/react-ui/src/providers/signers/CrossmintSignerProvider.tsx
index b90641e5e..5583c6fb1 100644
--- a/packages/client/ui/react-ui/src/providers/signers/CrossmintSignerProvider.tsx
+++ b/packages/client/ui/react-ui/src/providers/signers/CrossmintSignerProvider.tsx
@@ -113,7 +113,7 @@ export function CrossmintSignerProvider({
                         };
                         successHandlerRef.current = successHandler;
                         errorHandlerRef.current = errorHandler;
-                        handleGetRecoverySigner(args.email).then(successHandler).catch(errorHandler);
+                        handleGetRecoverySigner().then(successHandler).catch(errorHandler);
                     }),
             };
 
@@ -129,7 +129,7 @@ export function CrossmintSignerProvider({
         }
     };
 
-    async function handleGetRecoverySigner(email?: string) {
+    async function handleGetRecoverySigner() {
         if (!iframeWindow.current || jwt == null) {
             throw new Error("[handleGetRecoverySigner] IFrame window not initialized or JWT not set");
         }
@@ -158,14 +158,9 @@ export function CrossmintSignerProvider({
                 successHandlerRef.current = () => resolve();
                 errorHandlerRef.current = (error) => reject(error);
 
-                if (email != null) {
-                    // signer doesn't exist yet, handle email submission
-                    await handleSendEmailOTP(email);
-                } else {
-                    // if no email was provided, initiate the OTP flow from the start
-                    setStep("initial");
-                    setDialogOpen(true);
-                }
+                // Show the initial email confirmation flow dialog
+                setStep("initial");
+                setDialogOpen(true);
             } catch (err) {
                 console.error("Error getting recovery signer", err);
                 reject(err);
@@ -339,11 +334,9 @@ export function CrossmintSignerProvider({
         <CrossmintSignerContext.Provider value={{ experimental_getOrCreateWalletWithRecoveryKey }}>
             <EmailSignersDialog
                 email={email}
-                setEmail={setEmail}
                 open={dialogOpen}
                 setOpen={setDialogOpen}
                 step={step}
-                setStep={setStep}
                 onSubmitOTP={handleOTPSubmit}
                 onResendOTPCode={handleSendEmailOTP}
                 onSubmitEmail={handleSendEmailOTP}


Reviews:
user: login: jmderby
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T21:59:12Z
user: login: jmderby
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T22:01:37Z
user: login: AlbertoElias
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T22:03:47Z

Review comments:
diff_hunk: @@ -0,0 +1,93 @@
+import { useState } from "react";
path: packages/client/ui/react-ui/src/components/signers/EmailConfirmation.tsx
user: login: jmderby
body: mainly renamed file and updated ui
created_at: 2025-05-08T21:59:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -35,91 +33,74 @@ export function EmailOTPInput({ email, onSubmitOTP, handleOnBack, onResendCode,
     };
 
     return (
-        <div>
-            <button
-                className={
-                    "absolute left-4 top-4 min-[480px]:!left-6 min-[480px]:!top-6 rounded-full opacity-70 ring-offset-background text-cm-text-primary transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-cm-accent focus:ring-offset-2 disabled:pointer-events-none"
-                }
-                onClick={() => {
-                    setError(null);
-                    handleOnBack();
-                    setToken("");
-                }}
+        <div className="flex flex-col items-center justify-start w-full">
path: packages/client/ui/react-ui/src/components/signers/EmailOTPInput.tsx
user: login: jmderby
body: removed one parent div but didn't really change anything here
created_at: 2025-05-08T22:01:36Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-05-08T21:58:49Z
body: ###  ⚠️  No Changeset found

Latest commit: c51fe12aa49f3f37ed073629a454fbc97e5ed776

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**



[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/Crossmint/crossmint-sdk/new/jonathan/ncs-ui-tweaks?filename=.changeset/two-students-refuse.md&value=---%0A%22%40fake-scope%2Ffake-pkg%22%3A%20patch%0A---%0A%0AReact%20web%3A%20NCS%20ui%20tweaks%0A)


author_association: NONE
user: login: vercel[bot]
created_at: 2025-05-08T21:58:55Z
body: [vc]: #zA39uZUMGkXG8ULeSa49GXTv9gUjw3M9Au5k9QEPvWg=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJzbWFydC13YWxsZXQtYXV0aC1kZW1vIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vY3Jvc3NtaW50L3NtYXJ0LXdhbGxldC1hdXRoLWRlbW8vQ1JVZWJMUzJxNTg5WXBQTmJMZXpLNkxWS1FvdyIsIm5leHRDb21taXRTdGF0dXMiOiJJR05PUkVEIn1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)


<details><summary>1 Skipped Deployment</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **smart-wallet-auth-demo** | ⬜️ Ignored ([Inspect](https://vercel.com/crossmint/smart-wallet-auth-demo/CRUebLS2q589YpPNbLezK6LVKQow)) |  |  | May 8, 2025 10:01pm |
</details>


author_association: NONE