@crossmint/client-sdk-react-ui 1.8.0 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.d.cts +6 -4
  3. package/dist/index.d.ts +6 -4
  4. package/dist/index.js +1 -1
  5. package/package.json +10 -6
  6. package/src/components/auth/AuthForm.tsx +50 -0
  7. package/src/components/auth/AuthFormBackButton.tsx +26 -0
  8. package/src/components/auth/AuthFormDialog.tsx +33 -0
  9. package/src/components/auth/EmbeddedAuthForm.tsx +5 -0
  10. package/src/components/auth/methods/email/EmailAuthFlow.tsx +19 -0
  11. package/src/components/auth/methods/email/EmailOTPInput.tsx +123 -0
  12. package/src/components/auth/methods/email/EmailSignIn.tsx +113 -0
  13. package/src/components/auth/methods/farcaster/FarcasterSignIn.tsx +170 -0
  14. package/src/components/auth/methods/google/GoogleSignIn.tsx +62 -0
  15. package/src/components/common/Dialog.tsx +141 -0
  16. package/src/components/common/Divider.tsx +25 -0
  17. package/src/components/common/InputOTP.tsx +89 -0
  18. package/src/components/common/PoweredByCrossmint.tsx +4 -9
  19. package/src/components/common/Spinner.tsx +22 -0
  20. package/src/components/dynamic-xyz/DynamicContextProviderWrapper.tsx +12 -2
  21. package/src/components/embed/v3/EmbeddedCheckoutV3IFrame.tsx +6 -1
  22. package/src/components/embed/v3/crypto/CryptoWalletConnectionHandler.tsx +11 -3
  23. package/src/components/index.ts +2 -1
  24. package/src/hooks/useAuthSignIn.ts +117 -0
  25. package/src/hooks/useOAuthWindowListener.ts +87 -0
  26. package/src/icons/alert.tsx +19 -0
  27. package/src/icons/discord.tsx +18 -0
  28. package/src/icons/emailOTP.tsx +147 -0
  29. package/src/icons/farcaster.tsx +26 -0
  30. package/src/icons/google.tsx +30 -0
  31. package/src/icons/leftArrow.tsx +20 -0
  32. package/src/icons/poweredByLeaf.tsx +2 -2
  33. package/src/providers/CrossmintAuthProvider.tsx +24 -25
  34. package/src/providers/CrossmintWalletProvider.tsx +3 -3
  35. package/src/providers/auth/AuthFormProvider.test.tsx +105 -0
  36. package/src/providers/auth/AuthFormProvider.tsx +116 -0
  37. package/src/providers/auth/FarcasterProvider.tsx +12 -0
  38. package/src/twind.config.ts +101 -1
  39. package/src/types/auth.ts +4 -0
  40. package/src/components/auth/AuthModal.tsx +0 -207
@@ -0,0 +1,89 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { OTPInput, OTPInputContext } from "input-otp";
5
+ import { createContext, useContext } from "react";
6
+ import { classNames } from "@/utils/classNames";
7
+
8
+ // Define the type for customStyles
9
+ type CustomStyles = {
10
+ textPrimary: string;
11
+ inputBackground: string;
12
+ buttonBackground: string;
13
+ accent: string;
14
+ danger: string;
15
+ border: string;
16
+ borderRadius?: string;
17
+ };
18
+
19
+ // Create a context for customStyles
20
+ const CustomStylesContext = createContext<CustomStyles | undefined>(undefined);
21
+
22
+ const InputOTP = React.forwardRef<
23
+ React.ElementRef<typeof OTPInput>,
24
+ React.ComponentPropsWithoutRef<typeof OTPInput> & { customStyles?: CustomStyles }
25
+ >(({ className, containerClassName, customStyles, ...props }, ref) => (
26
+ <CustomStylesContext.Provider value={customStyles}>
27
+ <OTPInput
28
+ autoFocus
29
+ ref={ref}
30
+ containerClassName={classNames("flex items-center gap-2 has-[:disabled]:opacity-50", containerClassName)}
31
+ className={classNames("disabled:cursor-not-allowed", className)}
32
+ {...props}
33
+ />
34
+ </CustomStylesContext.Provider>
35
+ ));
36
+ InputOTP.displayName = "InputOTP";
37
+
38
+ const InputOTPGroup = React.forwardRef<React.ElementRef<"div">, React.ComponentPropsWithoutRef<"div">>(
39
+ ({ className, ...props }, ref) => (
40
+ <div ref={ref} className={classNames("flex gap-2 items-center", className)} {...props} />
41
+ )
42
+ );
43
+ InputOTPGroup.displayName = "InputOTPGroup";
44
+
45
+ const InputOTPSlot = React.forwardRef<
46
+ React.ElementRef<"div">,
47
+ React.ComponentPropsWithoutRef<"div"> & {
48
+ index: number;
49
+ hasError?: boolean;
50
+ }
51
+ >(({ index, className, hasError, ...props }, ref) => {
52
+ const inputOTPContext = React.useContext(OTPInputContext);
53
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
54
+ const customStyles = useContext(CustomStylesContext);
55
+
56
+ return (
57
+ <div
58
+ ref={ref}
59
+ className={classNames(
60
+ "relative flex h-14 w-12 items-center justify-center border text-lg transition-all rounded-md text-cm-text-primary",
61
+ isActive && `z-10 ring-2 ring-offset-background`,
62
+ className
63
+ )}
64
+ style={{
65
+ borderRadius: customStyles?.borderRadius,
66
+ borderColor: hasError ? customStyles?.danger : customStyles?.border,
67
+ boxShadow: isActive ? `0 0 0 2px ${customStyles?.accent}` : "none",
68
+ backgroundColor: char ? customStyles?.buttonBackground : customStyles?.inputBackground,
69
+ }}
70
+ {...props}
71
+ >
72
+ {char}
73
+ {hasFakeCaret && (
74
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
75
+ <div
76
+ className="h-4 w-px animate-caret-blink duration-1000"
77
+ style={{
78
+ height: "18px",
79
+ backgroundColor: customStyles?.textPrimary,
80
+ }}
81
+ />
82
+ </div>
83
+ )}
84
+ </div>
85
+ );
86
+ });
87
+ InputOTPSlot.displayName = "InputOTPSlot";
88
+
89
+ export { InputOTP, InputOTPGroup, InputOTPSlot };
@@ -1,16 +1,11 @@
1
1
  import { PoweredByLeaf } from "@/icons/poweredByLeaf";
2
+ import { classNames } from "@/utils/classNames";
2
3
 
3
- export function PoweredByCrossmint({ color }: { color?: string }) {
4
+ export function PoweredByCrossmint({ color, className }: { color?: string; className?: string }) {
4
5
  return (
5
6
  <p
6
- style={{
7
- display: "flex",
8
- fontSize: "0.75rem",
9
- fontWeight: "400",
10
- letterSpacing: "-0.2px",
11
- padding: "0.5rem",
12
- color: color || "#67797F",
13
- }}
7
+ className={classNames("flex text-xs font-normal tracking-tight p-2 items-center", className)}
8
+ style={{ color: color || "#67797F" }}
14
9
  >
15
10
  Powered by
16
11
  <span className="flex self-center pl-1 gap-1 items-center font-semibold">
@@ -0,0 +1,22 @@
1
+ import type React from "react";
2
+ import { classNames } from "@/utils/classNames";
3
+
4
+ export const Spinner = ({ className, style }: { className?: string; style?: React.CSSProperties }) => (
5
+ <svg
6
+ aria-hidden="true"
7
+ className={classNames("w-6 h-6 fill-cm-text-primary text-cm-text-secondary animate-spin", className)}
8
+ style={style}
9
+ viewBox="0 0 100 101"
10
+ fill="none"
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ >
13
+ <path
14
+ d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
15
+ fill="currentColor"
16
+ />
17
+ <path
18
+ d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
19
+ fill="currentFill"
20
+ />
21
+ </svg>
22
+ );
@@ -1,17 +1,27 @@
1
+ import type { APIKeyEnvironmentPrefix } from "@crossmint/common-sdk-base";
1
2
  import { type DynamicContextProps, DynamicContextProvider } from "@dynamic-labs/sdk-react-core";
2
3
  import type { ReactNode } from "react";
3
4
 
4
5
  export interface DynamicContextProviderWrapperProps {
5
6
  children?: ReactNode;
6
7
  settings: Omit<DynamicContextProps["settings"], "initialAuthenticationMode" | "environmentId">;
8
+ apiKeyEnvironment: APIKeyEnvironmentPrefix;
7
9
  }
8
10
 
9
- export default function DynamicContextProviderWrapper({ children, settings }: DynamicContextProviderWrapperProps) {
11
+ export default function DynamicContextProviderWrapper({
12
+ children,
13
+ settings,
14
+ apiKeyEnvironment,
15
+ }: DynamicContextProviderWrapperProps) {
10
16
  return (
11
17
  <DynamicContextProvider
12
18
  settings={{
13
19
  initialAuthenticationMode: "connect-only",
14
- environmentId: "cd53135a-b32b-4704-bfca-324b665e9329", // TODO: Key per env
20
+ environmentId:
21
+ apiKeyEnvironment === "production"
22
+ ? "3fc6c24e-6a8e-45f8-aae1-a87d7a027e12"
23
+ : "cd53135a-b32b-4704-bfca-324b665e9329",
24
+ cssOverrides: `.powered-by-dynamic { display: none !important; }`,
15
25
  ...settings,
16
26
  }}
17
27
  >
@@ -63,7 +63,12 @@ export function EmbeddedCheckoutV3IFrame(props: CrossmintEmbeddedCheckoutV3Props
63
63
  backgroundColor: "transparent",
64
64
  }}
65
65
  />
66
- {props.payment.crypto.enabled ? <CryptoWalletConnectionHandler iframeClient={iframeClient} /> : null}
66
+ {props.payment.crypto.enabled ? (
67
+ <CryptoWalletConnectionHandler
68
+ iframeClient={iframeClient}
69
+ apiKeyEnvironment={apiClient["parsedAPIKey"].environment}
70
+ />
71
+ ) : null}
67
72
  </>
68
73
  );
69
74
  }
@@ -1,17 +1,25 @@
1
1
  import DynamicContextProviderWrapper from "@/components/dynamic-xyz/DynamicContextProviderWrapper";
2
2
  import type { EmbeddedCheckoutV3IFrameEmitter } from "@crossmint/client-sdk-base";
3
- import { type BlockchainIncludingTestnet, chainIdToBlockchain } from "@crossmint/common-sdk-base";
3
+ import {
4
+ type APIKeyEnvironmentPrefix,
5
+ type BlockchainIncludingTestnet,
6
+ chainIdToBlockchain,
7
+ } from "@crossmint/common-sdk-base";
4
8
  import { EthereumWalletConnectors } from "@dynamic-labs/ethereum";
5
9
  import { type HandleConnectedWallet, useDynamicContext } from "@dynamic-labs/sdk-react-core";
6
10
  import { SolanaWalletConnectors } from "@dynamic-labs/solana";
7
11
  import { useEffect } from "react";
8
12
  import { handleSendTransaction } from "./utils/handleSendTransaction";
9
13
 
10
- export function CryptoWalletConnectionHandler(props: { iframeClient: EmbeddedCheckoutV3IFrameEmitter | null }) {
11
- const { iframeClient } = props;
14
+ export function CryptoWalletConnectionHandler(props: {
15
+ iframeClient: EmbeddedCheckoutV3IFrameEmitter | null;
16
+ apiKeyEnvironment: APIKeyEnvironmentPrefix;
17
+ }) {
18
+ const { iframeClient, apiKeyEnvironment } = props;
12
19
 
13
20
  return (
14
21
  <DynamicContextProviderWrapper
22
+ apiKeyEnvironment={apiKeyEnvironment}
15
23
  settings={{
16
24
  walletConnectors: [EthereumWalletConnectors, SolanaWalletConnectors],
17
25
  events: {
@@ -3,5 +3,6 @@ export * from "./CrossmintNFTDetail";
3
3
 
4
4
  export * from "./embed";
5
5
  export * from "./hosted";
6
-
7
6
  export * from "./embed/v3";
7
+
8
+ export { EmbeddedAuthForm } from "./auth/EmbeddedAuthForm";
@@ -0,0 +1,117 @@
1
+ import type { UseSignInData } from "@farcaster/auth-kit";
2
+
3
+ export function useAuthSignIn() {
4
+ return {
5
+ onEmailSignIn,
6
+ onConfirmEmailOtp,
7
+ onFarcasterSignIn,
8
+ };
9
+ }
10
+
11
+ async function onEmailSignIn(email: string, options: { baseUrl: string; apiKey: string }) {
12
+ try {
13
+ const queryParams = new URLSearchParams({ apiKey: options.apiKey });
14
+ const response = await fetch(`${options.baseUrl}api/2024-09-26/session/sdk/auth/otps/send?${queryParams}`, {
15
+ headers: {
16
+ "Content-Type": "application/json",
17
+ "x-api-key": options.apiKey,
18
+ },
19
+ credentials: "same-origin",
20
+ cache: "no-cache",
21
+ mode: "cors",
22
+ method: "POST",
23
+ body: JSON.stringify({ email }),
24
+ });
25
+
26
+ if (!response?.ok) {
27
+ throw new Error("Failed to send email. Please try again or contact support.");
28
+ }
29
+
30
+ return await response.json();
31
+ } catch (err) {
32
+ console.error("Error signing in via email ", err);
33
+ throw new Error("Error signing in via email " + err);
34
+ }
35
+ }
36
+
37
+ async function onConfirmEmailOtp(
38
+ email: string,
39
+ emailId: string,
40
+ token: string,
41
+ options: { baseUrl: string; apiKey: string }
42
+ ) {
43
+ try {
44
+ const queryParams = new URLSearchParams({
45
+ email,
46
+ signinAuthenticationMethod: "email",
47
+ apiKey: options.apiKey,
48
+ token,
49
+ locale: "en",
50
+ state: emailId,
51
+ callbackUrl: `${options.baseUrl}api/2024-09-26/session/sdk/auth/we-dont-actually-use-this-anymore`,
52
+ });
53
+ const response = await fetch(`${options.baseUrl}api/2024-09-26/session/sdk/auth/authenticate?${queryParams}`, {
54
+ headers: {
55
+ "Content-Type": "application/json",
56
+ "x-api-key": options.apiKey,
57
+ },
58
+ credentials: "same-origin",
59
+ cache: "no-cache",
60
+ mode: "cors",
61
+ method: "POST",
62
+ });
63
+
64
+ if (!response?.ok) {
65
+ throw new Error("Failed to confirm email otp. Please try again or contact support.");
66
+ }
67
+
68
+ const data = await response.json();
69
+ const callbackUrl = new URL(data.callbackUrl);
70
+
71
+ // parse the oneTimeSecret from the callbackUrl response
72
+ return callbackUrl.searchParams.get("oneTimeSecret");
73
+ } catch (err) {
74
+ console.error("Error confirming email otp ", err);
75
+ throw new Error("Error confirming email otp " + err);
76
+ }
77
+ }
78
+
79
+ async function onFarcasterSignIn(data: UseSignInData, options: { baseUrl: string; apiKey: string }) {
80
+ try {
81
+ const queryParams = new URLSearchParams({
82
+ signinAuthenticationMethod: "farcaster",
83
+ apiKey: options.apiKey,
84
+ callbackUrl: `${options.baseUrl}sdk/2024-09-26/auth/callback?isPopup=false`,
85
+ });
86
+
87
+ const response = await fetch(`${options.baseUrl}api/2024-09-26/session/sdk/auth/authenticate?${queryParams}`, {
88
+ headers: {
89
+ "Content-Type": "application/json",
90
+ "x-api-key": options.apiKey,
91
+ },
92
+ body: JSON.stringify({
93
+ ...data,
94
+ domain: data.signatureParams.domain,
95
+ redirect: true,
96
+ callbackUrl: `${options.baseUrl}sdk/2024-09-26/auth/callback?isPopup=false`,
97
+ }),
98
+ credentials: "same-origin",
99
+ cache: "no-cache",
100
+ mode: "cors",
101
+ method: "POST",
102
+ });
103
+
104
+ if (!response?.ok) {
105
+ throw new Error("Failed to sign in via farcaster. Please try again or contact support.");
106
+ }
107
+
108
+ const resData = await response.json();
109
+ const callbackUrl = new URL(resData.callbackUrl);
110
+
111
+ // parse the oneTimeSecret from the callbackUrl response
112
+ return callbackUrl.searchParams.get("oneTimeSecret");
113
+ } catch (err) {
114
+ console.error("Error signing in via farcaster ", err);
115
+ throw new Error("Error signing in via farcaster " + err);
116
+ }
117
+ }
@@ -0,0 +1,87 @@
1
+ import type { OAuthProvider } from "@crossmint/common-sdk-auth";
2
+ import { ChildWindow, PopupWindow } from "@crossmint/client-sdk-window";
3
+ import type { AuthMaterialWithUser } from "@crossmint/common-sdk-auth";
4
+ import { useEffect, useRef, useState } from "react";
5
+ import { z } from "zod";
6
+ import { useAuthForm } from "@/providers/auth/AuthFormProvider";
7
+
8
+ export const useOAuthWindowListener = (
9
+ provider: OAuthProvider,
10
+ options: {
11
+ apiKey: string;
12
+ baseUrl: string;
13
+ fetchAuthMaterial: (refreshToken: string) => Promise<AuthMaterialWithUser>;
14
+ }
15
+ ) => {
16
+ const { oauthUrlMap } = useAuthForm();
17
+ const [isLoading, setIsLoading] = useState(false);
18
+ const childRef = useRef<ChildWindow<IncomingEvents, OutgoingEvents> | null>(null);
19
+
20
+ useEffect(() => {
21
+ if (childRef.current == null) {
22
+ childRef.current = new ChildWindow<IncomingEvents, OutgoingEvents>(window.opener || window.parent, "*", {
23
+ incomingEvents,
24
+ outgoingEvents,
25
+ });
26
+ }
27
+
28
+ return () => {
29
+ if (childRef.current != null) {
30
+ childRef.current.off("authMaterialFromPopupCallback");
31
+ }
32
+ };
33
+ }, []);
34
+
35
+ const createPopupAndSetupListeners = async () => {
36
+ if (childRef.current == null) {
37
+ throw new Error("Child window not initialized");
38
+ }
39
+ setIsLoading(true);
40
+ const popup = await PopupWindow.init(oauthUrlMap[provider], {
41
+ awaitToLoad: false,
42
+ crossOrigin: true,
43
+ width: 400,
44
+ height: 700,
45
+ });
46
+
47
+ const handleAuthMaterial = async (data: { oneTimeSecret: string }) => {
48
+ await options.fetchAuthMaterial(data.oneTimeSecret);
49
+ childRef.current?.off("authMaterialFromPopupCallback");
50
+ popup.window.close();
51
+ setIsLoading(false);
52
+ };
53
+
54
+ childRef.current.on("authMaterialFromPopupCallback", handleAuthMaterial);
55
+
56
+ // Add a check for manual window closure
57
+ // Ideally we should find a more explicit way of doing this, but I think this is fine for now.
58
+ const checkWindowClosure = setInterval(() => {
59
+ if (popup.window.closed) {
60
+ clearInterval(checkWindowClosure);
61
+ setIsLoading(false);
62
+ childRef.current?.off("authMaterialFromPopupCallback");
63
+ }
64
+ }, 2500); // Check every 2.5 seconds
65
+ };
66
+
67
+ return {
68
+ createPopupAndSetupListeners,
69
+ isLoading,
70
+ };
71
+ };
72
+
73
+ const incomingEvents = {
74
+ authMaterialFromPopupCallback: z.object({ oneTimeSecret: z.string() }),
75
+ };
76
+
77
+ const outgoingEvents = {
78
+ authMaterialFromAuthFrame: z.object({ oneTimeSecret: z.string() }),
79
+ };
80
+
81
+ type IncomingEvents = {
82
+ authMaterialFromPopupCallback: typeof incomingEvents.authMaterialFromPopupCallback;
83
+ };
84
+
85
+ type OutgoingEvents = {
86
+ authMaterialFromAuthFrame: typeof outgoingEvents.authMaterialFromAuthFrame;
87
+ };
@@ -0,0 +1,19 @@
1
+ export function AlertIcon({ customColor = "#f44336" }: { customColor?: string }) {
2
+ return (
3
+ <svg
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ width="20"
6
+ height="20"
7
+ viewBox="0 0 24 24"
8
+ fill="none"
9
+ stroke={customColor}
10
+ strokeWidth="2"
11
+ strokeLinecap="round"
12
+ strokeLinejoin="round"
13
+ >
14
+ <circle cx="12" cy="12" r="10" />
15
+ <line x1="12" x2="12" y1="8" y2="12" />
16
+ <line x1="12" x2="12.01" y1="16" y2="16" />
17
+ </svg>
18
+ );
19
+ }
@@ -0,0 +1,18 @@
1
+ export function DiscordIcon({ className }: { className?: string }) {
2
+ return (
3
+ <svg
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ x="0px"
6
+ y="0px"
7
+ width="100"
8
+ height="100"
9
+ viewBox="0 0 48 48"
10
+ className={className}
11
+ >
12
+ <path
13
+ fill="#8c9eff"
14
+ d="M40,12c0,0-4.585-3.588-10-4l-0.488,0.976C34.408,10.174,36.654,11.891,39,14c-4.045-2.065-8.039-4-15-4s-10.955,1.935-15,4c2.346-2.109,5.018-4.015,9.488-5.024L18,8c-5.681,0.537-10,4-10,4s-5.121,7.425-6,22c5.162,5.953,13,6,13,6l1.639-2.185C13.857,36.848,10.715,35.121,8,32c3.238,2.45,8.125,5,16,5s12.762-2.55,16-5c-2.715,3.121-5.857,4.848-8.639,5.815L33,40c0,0,7.838-0.047,13-6C45.121,19.425,40,12,40,12z M17.5,30c-1.933,0-3.5-1.791-3.5-4c0-2.209,1.567-4,3.5-4s3.5,1.791,3.5,4C21,28.209,19.433,30,17.5,30z M30.5,30c-1.933,0-3.5-1.791-3.5-4c0-2.209,1.567-4,3.5-4s3.5,1.791,3.5,4C34,28.209,32.433,30,30.5,30z"
15
+ ></path>
16
+ </svg>
17
+ );
18
+ }
@@ -0,0 +1,147 @@
1
+ export function EmailOtpIcon({
2
+ customAccentColor = "#04AA6D",
3
+ customButtonBackgroundColor = "#F2F3F7",
4
+ customBackgroundColor = "#ffffff",
5
+ }: { customAccentColor?: string; customButtonBackgroundColor?: string; customBackgroundColor?: string }) {
6
+ return (
7
+ <svg width="136" height="113" viewBox="0 0 136 113" fill="none" xmlns="http://www.w3.org/2000/svg">
8
+ <mask id="mask0_944_1329" maskUnits="userSpaceOnUse" x="8" y="8" width="97" height="97">
9
+ <path
10
+ d="M104.598 56.2539C104.598 82.7636 83.1073 104.254 56.5977 104.254C30.088 104.254 8.59766 82.7636 8.59766 56.2539C8.59766 29.7442 30.088 8.25391 56.5977 8.25391C83.1073 8.25391 104.598 29.7442 104.598 56.2539Z"
11
+ fill="#D9D9D9"
12
+ />
13
+ </mask>
14
+ <g mask="url(#mask0_944_1329)">
15
+ <circle cx="56.5977" cy="56.2539" r="48" fill={customButtonBackgroundColor} />
16
+ <g filter="url(#filter0_d_944_1329)">
17
+ <mask id="mask1_944_1329" maskUnits="userSpaceOnUse" x="29" y="37" width="55" height="37">
18
+ <rect x="29.2957" y="37.5449" width="54.604" height="35.8658" rx="3.212" fill="#52E0B5" />
19
+ </mask>
20
+ <g mask="url(#mask1_944_1329)">
21
+ <rect
22
+ x="29.2957"
23
+ y="34.8662"
24
+ width="54.604"
25
+ height="38.544"
26
+ rx="3.212"
27
+ fill={customBackgroundColor}
28
+ />
29
+ <mask id="mask2_944_1329" maskUnits="userSpaceOnUse" x="29" y="51" width="55" height="41">
30
+ <path
31
+ d="M31.8833 74.2272C29.1168 72.267 29.1866 68.1383 32.0178 66.2728L53.9469 51.8234C55.5553 50.7636 57.6403 50.7636 59.2487 51.8234L81.1779 66.2728C84.0091 68.1383 84.0789 72.267 81.3124 74.2272L56.5978 91.7381L31.8833 74.2272Z"
32
+ fill="#52E0B5"
33
+ />
34
+ </mask>
35
+ <g mask="url(#mask2_944_1329)">
36
+ <path
37
+ d="M32.0675 73.9008C29.5307 72.1034 29.5947 68.3175 32.1909 66.6068L54.12 52.1574C55.5949 51.1856 57.5068 51.1856 58.9817 52.1574L80.9108 66.6068C83.507 68.3175 83.571 72.1034 81.0341 73.9008L56.5508 91.2479L32.0675 73.9008Z"
38
+ stroke="#D0D5DD"
39
+ strokeWidth="0.8"
40
+ />
41
+ </g>
42
+ <path
43
+ d="M26.9443 36.6072C23.1618 33.6476 22.8212 28.0457 26.2172 24.6497L50.9406 -0.073638C54.0648 -3.19783 59.1301 -3.19783 62.2543 -0.0736384L86.9776 24.6497C90.3737 28.0457 90.033 33.6476 86.2506 36.6072L61.5272 55.9514C58.6312 58.2173 54.5636 58.2173 51.6677 55.9514L26.9443 36.6072Z"
44
+ fill={customBackgroundColor}
45
+ />
46
+ <path
47
+ d="M27.1908 36.2921C23.5974 33.4806 23.2738 28.1588 26.5001 24.9326L51.2234 0.209205C54.1914 -2.75878 59.0035 -2.75878 61.9714 0.209205L86.6948 24.9326C89.921 28.1588 89.5974 33.4806 86.0041 36.2921L61.2807 55.6364C58.5296 57.789 54.6653 57.789 51.9142 55.6364L27.1908 36.2921Z"
48
+ stroke="#67797F"
49
+ strokeOpacity="0.45"
50
+ strokeWidth="0.8"
51
+ />
52
+ </g>
53
+ </g>
54
+ </g>
55
+ <g filter="url(#filter1_d_944_1329)">
56
+ <path
57
+ d="M106.88 68.5039H82.8799C74.5956 68.5039 67.8799 75.2196 67.8799 83.5039C67.8799 91.7882 74.5956 98.5039 82.8799 98.5039H106.88C115.164 98.5039 121.88 91.7882 121.88 83.5039C121.88 75.2196 115.164 68.5039 106.88 68.5039Z"
58
+ fill={customBackgroundColor}
59
+ />
60
+ </g>
61
+ <g clipPath="url(#clip0_944_1329)">
62
+ <mask id="mask3_944_1329" maskUnits="userSpaceOnUse" x="70" y="71" width="49" height="25">
63
+ <path d="M118.88 71.5039H70.8799V95.5039H118.88V71.5039Z" fill="white" />
64
+ </mask>
65
+ <g mask="url(#mask3_944_1329)">
66
+ <path d="M118.88 71.5039H70.8799V95.5039H118.88V71.5039Z" fill="url(#paint0_linear_944_1329)" />
67
+ <path d="M118.88 71.5039H70.8799V95.5039H118.88V71.5039Z" fill={customAccentColor} />
68
+ </g>
69
+ <path
70
+ d="M80.5866 87.7014L80.7337 85.1127L78.5604 86.5381L77.7805 85.183L80.1009 84.0196L77.7805 82.8563L78.5604 81.5012L80.7337 82.9266L80.5866 80.3378H82.1399L81.9993 82.9266L84.1726 81.5012L84.9524 82.8563L82.6257 84.0196L84.9524 85.183L84.1726 86.5381L81.9993 85.1127L82.1399 87.7014H80.5866ZM90.0001 87.7014L90.1471 85.1127L87.9738 86.5381L87.194 85.183L89.5143 84.0196L87.194 82.8563L87.9738 81.5012L90.1471 82.9266L90.0001 80.3378H91.5534L91.4127 82.9266L93.586 81.5012L94.3659 82.8563L92.0391 84.0196L94.3659 85.183L93.586 86.5381L91.4127 85.1127L91.5534 87.7014H90.0001ZM99.4135 87.7014L99.5605 85.1127L97.3872 86.5381L96.6074 85.183L98.9277 84.0196L96.6074 82.8563L97.3872 81.5012L99.5605 82.9266L99.4135 80.3378H100.967L100.826 82.9266L102.999 81.5012L103.779 82.8563L101.453 84.0196L103.779 85.183L102.999 86.5381L100.826 85.1127L100.967 87.7014H99.4135ZM108.827 87.7014L108.974 85.1127L106.801 86.5381L106.021 85.183L108.341 84.0196L106.021 82.8563L106.801 81.5012L108.974 82.9266L108.827 80.3378H110.38L110.24 82.9266L112.413 81.5012L113.193 82.8563L110.866 84.0196L113.193 85.183L112.413 86.5381L110.24 85.1127L110.38 87.7014H108.827Z"
71
+ fill={customBackgroundColor}
72
+ />
73
+ </g>
74
+ <defs>
75
+ <filter
76
+ id="filter0_d_944_1329"
77
+ x="11.6957"
78
+ y="28.7449"
79
+ width="89.804"
80
+ height="71.0657"
81
+ filterUnits="userSpaceOnUse"
82
+ colorInterpolationFilters="sRGB"
83
+ >
84
+ <feFlood floodOpacity="0" result="BackgroundImageFix" />
85
+ <feColorMatrix
86
+ in="SourceAlpha"
87
+ type="matrix"
88
+ values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
89
+ result="hardAlpha"
90
+ />
91
+ <feOffset dy="8.8" />
92
+ <feGaussianBlur stdDeviation="8.8" />
93
+ <feComposite in2="hardAlpha" operator="out" />
94
+ <feColorMatrix
95
+ type="matrix"
96
+ values="0 0 0 0 0.0370486 0 0 0 0 0.087038 0 0 0 0 0.0916667 0 0 0 0.16 0"
97
+ />
98
+ <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_944_1329" />
99
+ <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_944_1329" result="shape" />
100
+ </filter>
101
+ <filter
102
+ id="filter1_d_944_1329"
103
+ x="53.8799"
104
+ y="54.5039"
105
+ width="82"
106
+ height="58"
107
+ filterUnits="userSpaceOnUse"
108
+ colorInterpolationFilters="sRGB"
109
+ >
110
+ <feFlood floodOpacity="0" result="BackgroundImageFix" />
111
+ <feColorMatrix
112
+ in="SourceAlpha"
113
+ type="matrix"
114
+ values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
115
+ result="hardAlpha"
116
+ />
117
+ <feOffset />
118
+ <feGaussianBlur stdDeviation="7" />
119
+ <feComposite in2="hardAlpha" operator="out" />
120
+ <feColorMatrix
121
+ type="matrix"
122
+ values="0 0 0 0 0.0362847 0 0 0 0 0.0869733 0 0 0 0 0.0916667 0 0 0 0.12 0"
123
+ />
124
+ <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_944_1329" />
125
+ <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_944_1329" result="shape" />
126
+ </filter>
127
+ <linearGradient
128
+ id="paint0_linear_944_1329"
129
+ x1="103.915"
130
+ y1="95.5039"
131
+ x2="97.9027"
132
+ y2="68.179"
133
+ gradientUnits="userSpaceOnUse"
134
+ >
135
+ <stop stopColor="#2E996C" />
136
+ <stop offset="1" stopColor="#D0F2E5" />
137
+ </linearGradient>
138
+ <clipPath id="clip0_944_1329">
139
+ <path
140
+ d="M70.8799 83.5039C70.8799 76.8765 76.2525 71.5039 82.8799 71.5039H106.88C113.507 71.5039 118.88 76.8765 118.88 83.5039C118.88 90.1313 113.507 95.5039 106.88 95.5039H82.8799C76.2525 95.5039 70.8799 90.1313 70.8799 83.5039Z"
141
+ fill={customBackgroundColor}
142
+ />
143
+ </clipPath>
144
+ </defs>
145
+ </svg>
146
+ );
147
+ }
@@ -0,0 +1,26 @@
1
+ export const FarcasterIcon = ({ className }: { className?: string }) => {
2
+ return (
3
+ <svg
4
+ width="24"
5
+ height="24"
6
+ viewBox="0 0 1000 1000"
7
+ fill="none"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ className={className}
10
+ >
11
+ <rect width="1000" height="1000" fill="#855DCD" />
12
+ <path
13
+ d="M257.778 155.556H742.222V844.444H671.111V528.889H670.414C662.554 441.677 589.258 373.333 500 373.333C410.742 373.333 337.446 441.677 329.586 528.889H328.889V844.444H257.778V155.556Z"
14
+ fill="white"
15
+ />
16
+ <path
17
+ d="M128.889 253.333L157.778 351.111H182.222V746.667C169.949 746.667 160 756.616 160 768.889V795.556H155.556C143.283 795.556 133.333 805.505 133.333 817.778V844.444H382.222V817.778C382.222 805.505 372.273 795.556 360 795.556H355.556V768.889C355.556 756.616 345.606 746.667 333.333 746.667H306.667V253.333H128.889Z"
18
+ fill="white"
19
+ />
20
+ <path
21
+ d="M675.555 746.667C663.282 746.667 653.333 756.616 653.333 768.889V795.556H648.889C636.616 795.556 626.667 805.505 626.667 817.778V844.444H875.555V817.778C875.555 805.505 865.606 795.556 853.333 795.556H848.889V768.889C848.889 756.616 838.94 746.667 826.667 746.667V351.111H851.111L880 253.333H702.222V746.667H675.555Z"
22
+ fill="white"
23
+ />
24
+ </svg>
25
+ );
26
+ };