@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.
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +1 -1
- package/package.json +10 -6
- package/src/components/auth/AuthForm.tsx +50 -0
- package/src/components/auth/AuthFormBackButton.tsx +26 -0
- package/src/components/auth/AuthFormDialog.tsx +33 -0
- package/src/components/auth/EmbeddedAuthForm.tsx +5 -0
- package/src/components/auth/methods/email/EmailAuthFlow.tsx +19 -0
- package/src/components/auth/methods/email/EmailOTPInput.tsx +123 -0
- package/src/components/auth/methods/email/EmailSignIn.tsx +113 -0
- package/src/components/auth/methods/farcaster/FarcasterSignIn.tsx +170 -0
- package/src/components/auth/methods/google/GoogleSignIn.tsx +62 -0
- package/src/components/common/Dialog.tsx +141 -0
- package/src/components/common/Divider.tsx +25 -0
- package/src/components/common/InputOTP.tsx +89 -0
- package/src/components/common/PoweredByCrossmint.tsx +4 -9
- package/src/components/common/Spinner.tsx +22 -0
- package/src/components/dynamic-xyz/DynamicContextProviderWrapper.tsx +12 -2
- package/src/components/embed/v3/EmbeddedCheckoutV3IFrame.tsx +6 -1
- package/src/components/embed/v3/crypto/CryptoWalletConnectionHandler.tsx +11 -3
- package/src/components/index.ts +2 -1
- package/src/hooks/useAuthSignIn.ts +117 -0
- package/src/hooks/useOAuthWindowListener.ts +87 -0
- package/src/icons/alert.tsx +19 -0
- package/src/icons/discord.tsx +18 -0
- package/src/icons/emailOTP.tsx +147 -0
- package/src/icons/farcaster.tsx +26 -0
- package/src/icons/google.tsx +30 -0
- package/src/icons/leftArrow.tsx +20 -0
- package/src/icons/poweredByLeaf.tsx +2 -2
- package/src/providers/CrossmintAuthProvider.tsx +24 -25
- package/src/providers/CrossmintWalletProvider.tsx +3 -3
- package/src/providers/auth/AuthFormProvider.test.tsx +105 -0
- package/src/providers/auth/AuthFormProvider.tsx +116 -0
- package/src/providers/auth/FarcasterProvider.tsx +12 -0
- package/src/twind.config.ts +101 -1
- package/src/types/auth.ts +4 -0
- package/src/components/auth/AuthModal.tsx +0 -207
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function GoogleIcon({ 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="#fbc02d"
|
|
14
|
+
d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12 s5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24s8.955,20,20,20 s20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"
|
|
15
|
+
></path>
|
|
16
|
+
<path
|
|
17
|
+
fill="#e53935"
|
|
18
|
+
d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039 l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"
|
|
19
|
+
></path>
|
|
20
|
+
<path
|
|
21
|
+
fill="#4caf50"
|
|
22
|
+
d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36 c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"
|
|
23
|
+
></path>
|
|
24
|
+
<path
|
|
25
|
+
fill="#1565c0"
|
|
26
|
+
d="M43.611,20.083L43.595,20L42,20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571 c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z"
|
|
27
|
+
></path>
|
|
28
|
+
</svg>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function LeftArrowIcon({ className, style }: { className?: string; style?: React.CSSProperties }) {
|
|
2
|
+
return (
|
|
3
|
+
<svg
|
|
4
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5
|
+
width="24"
|
|
6
|
+
height="24"
|
|
7
|
+
viewBox="0 0 24 24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
strokeWidth="2"
|
|
11
|
+
strokeLinecap="round"
|
|
12
|
+
strokeLinejoin="round"
|
|
13
|
+
className={className}
|
|
14
|
+
style={style}
|
|
15
|
+
>
|
|
16
|
+
<path d="m12 19-7-7 7-7" />
|
|
17
|
+
<path d="M19 12H5" />
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type ReactNode, createContext, useEffect, useState } from "react";
|
|
2
|
-
import { createPortal } from "react-dom";
|
|
3
2
|
|
|
4
3
|
import { CrossmintAuthService } from "@crossmint/client-sdk-auth";
|
|
5
4
|
import type { EVMSmartWalletChain } from "@crossmint/client-sdk-smart-wallet";
|
|
@@ -11,16 +10,17 @@ import {
|
|
|
11
10
|
type SDKExternalUser,
|
|
12
11
|
} from "@crossmint/common-sdk-auth";
|
|
13
12
|
|
|
14
|
-
import
|
|
13
|
+
import AuthFormDialog from "../components/auth/AuthFormDialog";
|
|
15
14
|
import { useCrossmint, useRefreshToken, useWallet } from "../hooks";
|
|
16
15
|
import { CrossmintWalletProvider } from "./CrossmintWalletProvider";
|
|
17
16
|
import { deleteCookie, getCookie, setCookie } from "@/utils/authCookies";
|
|
17
|
+
import { AuthFormProvider } from "./auth/AuthFormProvider";
|
|
18
18
|
|
|
19
19
|
export type CrossmintAuthWalletConfig = {
|
|
20
20
|
defaultChain: EVMSmartWalletChain;
|
|
21
21
|
createOnLogin: "all-users" | "off";
|
|
22
22
|
type: "evm-smart-wallet";
|
|
23
|
-
|
|
23
|
+
showPasskeyHelpers?: boolean;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export type LoginMethod = "email" | "google" | "farcaster";
|
|
@@ -69,7 +69,7 @@ export function CrossmintAuthProvider({
|
|
|
69
69
|
);
|
|
70
70
|
const crossmintAuthService = new CrossmintAuthService(crossmint.apiKey);
|
|
71
71
|
const crossmintBaseUrl = validateApiKeyAndGetCrossmintBaseUrl(crossmint.apiKey);
|
|
72
|
-
const [
|
|
72
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
73
73
|
|
|
74
74
|
const setAuthMaterial = (authMaterial: AuthMaterialWithUser) => {
|
|
75
75
|
setCookie(SESSION_PREFIX, authMaterial.jwt);
|
|
@@ -101,7 +101,7 @@ export function CrossmintAuthProvider({
|
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
setDialogOpen(false);
|
|
105
105
|
}, [crossmint.jwt]);
|
|
106
106
|
|
|
107
107
|
const login = () => {
|
|
@@ -110,14 +110,14 @@ export function CrossmintAuthProvider({
|
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
setDialogOpen(true);
|
|
114
114
|
};
|
|
115
115
|
|
|
116
116
|
const getAuthStatus = (): AuthStatus => {
|
|
117
117
|
if (crossmint.jwt != null) {
|
|
118
118
|
return "logged-in";
|
|
119
119
|
}
|
|
120
|
-
if (
|
|
120
|
+
if (dialogOpen) {
|
|
121
121
|
return "in-progress";
|
|
122
122
|
}
|
|
123
123
|
return "logged-out";
|
|
@@ -153,26 +153,25 @@ export function CrossmintAuthProvider({
|
|
|
153
153
|
>
|
|
154
154
|
<CrossmintWalletProvider
|
|
155
155
|
defaultChain={embeddedWallets.defaultChain}
|
|
156
|
-
|
|
156
|
+
showPasskeyHelpers={embeddedWallets.showPasskeyHelpers}
|
|
157
157
|
appearance={appearance}
|
|
158
158
|
>
|
|
159
|
-
<
|
|
160
|
-
{
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
: null}
|
|
159
|
+
<AuthFormProvider
|
|
160
|
+
initialState={{
|
|
161
|
+
apiKey: crossmint.apiKey,
|
|
162
|
+
baseUrl: crossmintBaseUrl,
|
|
163
|
+
fetchAuthMaterial,
|
|
164
|
+
appearance,
|
|
165
|
+
setDialogOpen,
|
|
166
|
+
loginMethods,
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<WalletManager embeddedWallets={embeddedWallets} accessToken={crossmint.jwt}>
|
|
170
|
+
{children}
|
|
171
|
+
</WalletManager>
|
|
172
|
+
|
|
173
|
+
<AuthFormDialog open={dialogOpen} />
|
|
174
|
+
</AuthFormProvider>
|
|
176
175
|
</CrossmintWalletProvider>
|
|
177
176
|
</AuthContext.Provider>
|
|
178
177
|
);
|
|
@@ -57,12 +57,12 @@ export type WalletConfig = WalletParams & { type: "evm-smart-wallet" };
|
|
|
57
57
|
export function CrossmintWalletProvider({
|
|
58
58
|
children,
|
|
59
59
|
defaultChain,
|
|
60
|
-
|
|
60
|
+
showPasskeyHelpers = true, // enabled by default
|
|
61
61
|
appearance,
|
|
62
62
|
}: {
|
|
63
63
|
children: ReactNode;
|
|
64
64
|
defaultChain: EVMSmartWalletChain;
|
|
65
|
-
|
|
65
|
+
showPasskeyHelpers?: boolean;
|
|
66
66
|
appearance?: UIConfig;
|
|
67
67
|
}) {
|
|
68
68
|
const { crossmint } = useCrossmint("CrossmintWalletProvider must be used within CrossmintProvider");
|
|
@@ -99,7 +99,7 @@ export function CrossmintWalletProvider({
|
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
const enhanceConfigWithPasskeyPrompts = (config: WalletConfig) => {
|
|
102
|
-
if (
|
|
102
|
+
if (showPasskeyHelpers && (config.signer as PasskeySigner).type === "PASSKEY") {
|
|
103
103
|
return {
|
|
104
104
|
...config,
|
|
105
105
|
signer: {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { render, fireEvent, waitFor } from "@testing-library/react";
|
|
2
|
+
import { AuthFormProvider, useAuthForm } from "./AuthFormProvider";
|
|
3
|
+
import { describe, expect, test, vi } from "vitest";
|
|
4
|
+
import type { LoginMethod } from "..";
|
|
5
|
+
|
|
6
|
+
// Mock component to test the AuthFormProvider
|
|
7
|
+
function TestComponent() {
|
|
8
|
+
const { step, apiKey, baseUrl, loginMethods, setStep, setDialogOpen, oauthUrlMap, isLoadingOauthUrlMap } =
|
|
9
|
+
useAuthForm();
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div>
|
|
13
|
+
<div data-testid="step">{step}</div>
|
|
14
|
+
<div data-testid="api-key">{apiKey}</div>
|
|
15
|
+
<div data-testid="base-url">{baseUrl}</div>
|
|
16
|
+
<div data-testid="login-methods">{JSON.stringify(loginMethods)}</div>
|
|
17
|
+
<button onClick={() => setStep("otp")} data-testid="set-step">
|
|
18
|
+
Set Step to OTP
|
|
19
|
+
</button>
|
|
20
|
+
<button onClick={() => setDialogOpen(true)} data-testid="set-dialog-open">
|
|
21
|
+
Open Dialog
|
|
22
|
+
</button>
|
|
23
|
+
<div data-testid="oauth-url">{JSON.stringify(oauthUrlMap)}</div>
|
|
24
|
+
<div data-testid="is-loading-oauth-url">{isLoadingOauthUrlMap.toString()}</div>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("AuthFormProvider", () => {
|
|
30
|
+
const mockFetchAuthMaterial = vi.fn().mockResolvedValue({});
|
|
31
|
+
const mockInitialState = {
|
|
32
|
+
apiKey: "test-api-key",
|
|
33
|
+
baseUrl: "https://api.example.com",
|
|
34
|
+
fetchAuthMaterial: mockFetchAuthMaterial,
|
|
35
|
+
loginMethods: ["email", "google"] as LoginMethod[],
|
|
36
|
+
setDialogOpen: vi.fn(),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.resetAllMocks();
|
|
41
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
42
|
+
ok: true,
|
|
43
|
+
json: () => Promise.resolve({ oauthUrl: "https://oauth.example.com" }),
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("provides initial context values and fetches OAuth URLs", async () => {
|
|
48
|
+
const { getByTestId } = render(
|
|
49
|
+
<AuthFormProvider initialState={mockInitialState}>
|
|
50
|
+
<TestComponent />
|
|
51
|
+
</AuthFormProvider>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(getByTestId("step").textContent).toBe("initial");
|
|
55
|
+
expect(getByTestId("api-key").textContent).toBe("test-api-key");
|
|
56
|
+
expect(getByTestId("base-url").textContent).toBe("https://api.example.com");
|
|
57
|
+
expect(getByTestId("login-methods").textContent).toBe('["email","google"]');
|
|
58
|
+
|
|
59
|
+
await waitFor(() => {
|
|
60
|
+
expect(getByTestId("oauth-url").textContent).toBe('{"google":"https://oauth.example.com"}');
|
|
61
|
+
expect(getByTestId("is-loading-oauth-url").textContent).toBe("false");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("updates step", () => {
|
|
66
|
+
const { getByTestId } = render(
|
|
67
|
+
<AuthFormProvider initialState={mockInitialState}>
|
|
68
|
+
<TestComponent />
|
|
69
|
+
</AuthFormProvider>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
fireEvent.click(getByTestId("set-step"));
|
|
73
|
+
expect(getByTestId("step").textContent).toBe("otp");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("calls setDialogOpen", () => {
|
|
77
|
+
const { getByTestId } = render(
|
|
78
|
+
<AuthFormProvider initialState={mockInitialState}>
|
|
79
|
+
<TestComponent />
|
|
80
|
+
</AuthFormProvider>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
fireEvent.click(getByTestId("set-dialog-open"));
|
|
84
|
+
expect(mockInitialState.setDialogOpen).toHaveBeenCalledWith(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("handles OAuth URL fetch error", async () => {
|
|
88
|
+
global.fetch = vi.fn().mockRejectedValue(new Error("Fetch failed"));
|
|
89
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
90
|
+
|
|
91
|
+
const { getByTestId } = render(
|
|
92
|
+
<AuthFormProvider initialState={mockInitialState}>
|
|
93
|
+
<TestComponent />
|
|
94
|
+
</AuthFormProvider>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
await waitFor(() => {
|
|
98
|
+
expect(getByTestId("oauth-url").textContent).toBe('{"google":""}');
|
|
99
|
+
expect(getByTestId("is-loading-oauth-url").textContent).toBe("false");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
103
|
+
consoleSpy.mockRestore();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
|
|
2
|
+
import type { AuthMaterialWithUser, OAuthProvider } from "@crossmint/common-sdk-auth";
|
|
3
|
+
import type { UIConfig } from "@crossmint/common-sdk-base";
|
|
4
|
+
import type { LoginMethod } from "../CrossmintAuthProvider";
|
|
5
|
+
|
|
6
|
+
type AuthStep = "initial" | "walletMethod" | "otp" | "qrCode";
|
|
7
|
+
type OAuthUrlMap = Record<OAuthProvider, string>;
|
|
8
|
+
const initialOAuthUrlMap: OAuthUrlMap = {
|
|
9
|
+
google: "",
|
|
10
|
+
// Farcaster is not included here as it uses a different authentication method
|
|
11
|
+
};
|
|
12
|
+
interface AuthFormContextType {
|
|
13
|
+
step: AuthStep;
|
|
14
|
+
apiKey: string;
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
fetchAuthMaterial: (refreshToken: string) => Promise<AuthMaterialWithUser>;
|
|
17
|
+
appearance?: UIConfig;
|
|
18
|
+
loginMethods: LoginMethod[];
|
|
19
|
+
oauthUrlMap: OAuthUrlMap;
|
|
20
|
+
isLoadingOauthUrlMap: boolean;
|
|
21
|
+
setStep: (step: AuthStep) => void;
|
|
22
|
+
setDialogOpen: (open: boolean) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type ContextInitialStateProps = {
|
|
26
|
+
apiKey: string;
|
|
27
|
+
baseUrl: string;
|
|
28
|
+
fetchAuthMaterial: (refreshToken: string) => Promise<AuthMaterialWithUser>;
|
|
29
|
+
appearance?: UIConfig;
|
|
30
|
+
loginMethods: LoginMethod[];
|
|
31
|
+
setDialogOpen?: (open: boolean) => void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const AuthFormContext = createContext<AuthFormContextType | undefined>(undefined);
|
|
35
|
+
|
|
36
|
+
export const useAuthForm = () => {
|
|
37
|
+
const context = useContext(AuthFormContext);
|
|
38
|
+
if (!context) {
|
|
39
|
+
throw new Error("useAuthForm must be used within an AuthFormProvider");
|
|
40
|
+
}
|
|
41
|
+
return context;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const AuthFormProvider = ({
|
|
45
|
+
children,
|
|
46
|
+
initialState,
|
|
47
|
+
}: { children: ReactNode; initialState: ContextInitialStateProps }) => {
|
|
48
|
+
const [step, setStep] = useState<AuthStep>("initial");
|
|
49
|
+
const [oauthUrlMap, setOauthUrlMap] = useState<OAuthUrlMap>(initialOAuthUrlMap);
|
|
50
|
+
const [isLoadingOauthUrlMap, setIsLoadingOauthUrlMap] = useState(true);
|
|
51
|
+
|
|
52
|
+
const { loginMethods, apiKey, baseUrl } = initialState;
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const preFetchAndSetOauthUrl = async () => {
|
|
56
|
+
setIsLoadingOauthUrlMap(true);
|
|
57
|
+
try {
|
|
58
|
+
const oauthProviders = loginMethods.filter(
|
|
59
|
+
(method): method is OAuthProvider => method in initialOAuthUrlMap
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const oauthPromiseList = oauthProviders.map(async (provider) => {
|
|
63
|
+
const url = await getOAuthUrl(provider, { apiKey, baseUrl });
|
|
64
|
+
return { [provider]: url };
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const oauthUrlMap = Object.assign({}, ...(await Promise.all(oauthPromiseList)));
|
|
68
|
+
setOauthUrlMap(oauthUrlMap);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error("Error fetching OAuth URLs:", error);
|
|
71
|
+
} finally {
|
|
72
|
+
setIsLoadingOauthUrlMap(false);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
preFetchAndSetOauthUrl();
|
|
76
|
+
}, [loginMethods, apiKey, baseUrl]);
|
|
77
|
+
|
|
78
|
+
const value: AuthFormContextType = {
|
|
79
|
+
step,
|
|
80
|
+
apiKey,
|
|
81
|
+
baseUrl,
|
|
82
|
+
fetchAuthMaterial: initialState.fetchAuthMaterial,
|
|
83
|
+
appearance: initialState.appearance,
|
|
84
|
+
loginMethods,
|
|
85
|
+
oauthUrlMap,
|
|
86
|
+
isLoadingOauthUrlMap,
|
|
87
|
+
setDialogOpen: initialState.setDialogOpen ?? (() => {}),
|
|
88
|
+
setStep,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return <AuthFormContext.Provider value={value}>{children}</AuthFormContext.Provider>;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
async function getOAuthUrl(provider: OAuthProvider, options: { baseUrl: string; apiKey: string }) {
|
|
95
|
+
try {
|
|
96
|
+
const queryParams = new URLSearchParams({ apiKey: options.apiKey });
|
|
97
|
+
const response = await fetch(
|
|
98
|
+
`${options.baseUrl}api/2024-09-26/session/sdk/auth/social/${provider}/start?${queryParams}`,
|
|
99
|
+
{
|
|
100
|
+
headers: {
|
|
101
|
+
"x-api-key": options.apiKey,
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
throw new Error("Failed to get OAuth URL. Please try again or contact support.");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const data = (await response.json()) as { oauthUrl: string };
|
|
111
|
+
return data.oauthUrl;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(`Error fetching OAuth URL for ${provider}:`, error);
|
|
114
|
+
throw new Error(`Failed to get OAuth URL for ${provider}. Please try again or contact support.`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { AuthKitProvider } from "@farcaster/auth-kit";
|
|
3
|
+
|
|
4
|
+
export function FarcasterProvider({ baseUrl, children }: { baseUrl: string; children: ReactNode }) {
|
|
5
|
+
const config = {
|
|
6
|
+
rpcUrl: "https://mainnet.optimism.io",
|
|
7
|
+
domain: new URL(baseUrl).hostname.replace(/^www\./, ""),
|
|
8
|
+
siweUri: `${baseUrl}api/2024-09-26/session/sdk/auth/authenticate`,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
return <AuthKitProvider config={config}>{children}</AuthKitProvider>;
|
|
12
|
+
}
|
package/src/twind.config.ts
CHANGED
|
@@ -2,6 +2,106 @@ import { defineConfig } from "@twind/core";
|
|
|
2
2
|
import presetTailwind from "@twind/preset-tailwind";
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
5
|
-
presets: [presetTailwind(
|
|
5
|
+
presets: [presetTailwind()],
|
|
6
|
+
theme: {
|
|
7
|
+
screens: {
|
|
8
|
+
xs: "480px",
|
|
9
|
+
},
|
|
10
|
+
extend: {
|
|
11
|
+
colors: {
|
|
12
|
+
// Crossmint colors (cm- prefix)
|
|
13
|
+
"cm-text-primary": "#00150D",
|
|
14
|
+
"cm-text-secondary": "#67797F",
|
|
15
|
+
"cm-background-primary": "#FFFFFF",
|
|
16
|
+
"cm-muted-primary": "#F0F2F4",
|
|
17
|
+
"cm-border": "#D9D9D9",
|
|
18
|
+
"cm-ring": "#1A73E8",
|
|
19
|
+
},
|
|
20
|
+
keyframes: {
|
|
21
|
+
"caret-blink": {
|
|
22
|
+
"0%,70%,100%": { opacity: "1" },
|
|
23
|
+
"20%,50%": { opacity: "0" },
|
|
24
|
+
},
|
|
25
|
+
"fade-in": {
|
|
26
|
+
from: { opacity: "0" },
|
|
27
|
+
to: { opacity: "1" },
|
|
28
|
+
},
|
|
29
|
+
"fade-out": {
|
|
30
|
+
from: { opacity: "1" },
|
|
31
|
+
to: { opacity: "0" },
|
|
32
|
+
},
|
|
33
|
+
"slide-in-from-top": {
|
|
34
|
+
"0%": { transform: "translateY(-100%)" },
|
|
35
|
+
"100%": { transform: "translateY(0)" },
|
|
36
|
+
},
|
|
37
|
+
"slide-out-to-top": {
|
|
38
|
+
"0%": { transform: "translateY(0)" },
|
|
39
|
+
"100%": { transform: "translateY(-100%)" },
|
|
40
|
+
},
|
|
41
|
+
"slide-in-from-bottom": {
|
|
42
|
+
"0%": { transform: "translateY(100%)" },
|
|
43
|
+
"100%": { transform: "translateY(0)" },
|
|
44
|
+
},
|
|
45
|
+
"slide-out-to-bottom": {
|
|
46
|
+
"0%": { transform: "translateY(0)" },
|
|
47
|
+
"100%": { transform: "translateY(100%)" },
|
|
48
|
+
},
|
|
49
|
+
"slide-in-from-left": {
|
|
50
|
+
"0%": { transform: "translateX(-100%)" },
|
|
51
|
+
"100%": { transform: "translateX(0)" },
|
|
52
|
+
},
|
|
53
|
+
"slide-out-to-left": {
|
|
54
|
+
"0%": { transform: "translateX(0)" },
|
|
55
|
+
"100%": { transform: "translateX(-100%)" },
|
|
56
|
+
},
|
|
57
|
+
"slide-in-from-right": {
|
|
58
|
+
"0%": { transform: "translateX(100%)" },
|
|
59
|
+
"100%": { transform: "translateX(0)" },
|
|
60
|
+
},
|
|
61
|
+
"slide-out-to-right": {
|
|
62
|
+
"0%": { transform: "translateX(0)" },
|
|
63
|
+
"100%": { transform: "translateX(100%)" },
|
|
64
|
+
},
|
|
65
|
+
"zoom-in-95": {
|
|
66
|
+
"0%": { opacity: "0", transform: "scale(0.95) translate(-50%, -50%)" },
|
|
67
|
+
"100%": { opacity: "1", transform: "scale(1) translate(-50%, -50%)" },
|
|
68
|
+
},
|
|
69
|
+
"zoom-out-95": {
|
|
70
|
+
"0%": { opacity: "1", transform: "scale(1) translate(-50%, -50%)" },
|
|
71
|
+
"100%": { opacity: "0", transform: "scale(0.95) translate(-50%, -50%)" },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
animation: {
|
|
75
|
+
"caret-blink": "caret-blink 1.25s ease-out infinite",
|
|
76
|
+
"fade-in": "fade-in 150ms cubic-bezier(0.16, 1, 0.3, 1)",
|
|
77
|
+
"fade-out": "fade-out 150ms cubic-bezier(0.16, 1, 0.3, 1)",
|
|
78
|
+
"slide-in-from-top": "slide-in-from-top 150ms cubic-bezier(0.16, 1, 0.3, 1)",
|
|
79
|
+
"slide-out-to-top": "slide-out-to-top 150ms cubic-bezier(0.16, 1, 0.3, 1)",
|
|
80
|
+
"slide-in-from-bottom": "slide-in-from-bottom 150ms cubic-bezier(0.16, 1, 0.3, 1)",
|
|
81
|
+
"slide-out-to-bottom": "slide-out-to-bottom 150ms cubic-bezier(0.16, 1, 0.3, 1)",
|
|
82
|
+
"slide-in-from-left": "slide-in-from-left 150ms cubic-bezier(0.16, 1, 0.3, 1)",
|
|
83
|
+
"slide-out-to-left": "slide-out-to-left 150ms cubic-bezier(0.16, 1, 0.3, 1)",
|
|
84
|
+
"slide-in-from-right": "slide-in-from-right 150ms cubic-bezier(0.16, 1, 0.3, 1)",
|
|
85
|
+
"slide-out-to-right": "slide-out-to-right 150ms cubic-bezier(0.16, 1, 0.3, 1)",
|
|
86
|
+
"zoom-in-95": "zoom-in-95 150ms cubic-bezier(0.16, 1, 0.3, 1)",
|
|
87
|
+
"zoom-out-95": "zoom-out-95 150ms cubic-bezier(0.16, 1, 0.3, 1)",
|
|
88
|
+
},
|
|
89
|
+
durations: {
|
|
90
|
+
"300": "300ms",
|
|
91
|
+
"500": "500ms",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
rules: [
|
|
96
|
+
[
|
|
97
|
+
"responsive-border-radius-auth-dialog",
|
|
98
|
+
{
|
|
99
|
+
"@media (max-width: 479px)": {
|
|
100
|
+
"border-bottom-left-radius": "0 !important",
|
|
101
|
+
"border-bottom-right-radius": "0 !important",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
],
|
|
6
106
|
/* config */
|
|
7
107
|
});
|