@cohostvip/cohost-react 0.2.4 → 0.3.4

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.
@@ -0,0 +1,2 @@
1
+ import '@testing-library/jest-dom/vitest';
2
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":"AAAA,OAAO,kCAAkC,CAAC"}
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom/vitest';
@@ -0,0 +1,57 @@
1
+ import * as React from 'react';
2
+ import { AuthClient, type AuthConfig, type AuthState, type AuthUser, type OTPType, type SetAuthenticatedInput } from '@cohostvip/cohost-auth';
3
+ /**
4
+ * Value provided by AuthContext
5
+ */
6
+ export interface AuthContextValue {
7
+ /** Current auth state */
8
+ state: AuthState;
9
+ /** The underlying AuthClient instance */
10
+ client: AuthClient;
11
+ /** Request OTP to be sent to contact (email or phone) */
12
+ requestOTP: (contact: string, type?: OTPType) => Promise<boolean>;
13
+ /** Verify OTP and sign in */
14
+ verifyOTP: (contact: string, code: string) => Promise<AuthUser>;
15
+ /** Sign out the current user */
16
+ signOut: () => Promise<void>;
17
+ /** Get current access token (refreshing if needed) */
18
+ getToken: () => Promise<string | null>;
19
+ /** Manually set authenticated state (for custom auth flows like passkey) */
20
+ setAuthenticated: (input: SetAuthenticatedInput) => void;
21
+ }
22
+ /**
23
+ * Props for AuthProvider
24
+ */
25
+ export type AuthProviderProps = {
26
+ /** Children to render */
27
+ children: React.ReactNode;
28
+ } & ({
29
+ /** Auth configuration (creates a new client) */
30
+ config: AuthConfig;
31
+ /** Pre-existing client (mutually exclusive with config) */
32
+ client?: never;
33
+ } | {
34
+ /** Auth configuration */
35
+ config?: never;
36
+ /** Pre-existing AuthClient instance */
37
+ client: AuthClient;
38
+ });
39
+ export declare const AuthContext: React.Context<AuthContextValue | null>;
40
+ /**
41
+ * Provider component that wraps your app and provides auth context
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * import { AuthProvider } from '@cohostvip/cohost-react';
46
+ *
47
+ * function App() {
48
+ * return (
49
+ * <AuthProvider config={{ apiUrl: '/api' }}>
50
+ * <MyApp />
51
+ * </AuthProvider>
52
+ * );
53
+ * }
54
+ * ```
55
+ */
56
+ export declare const AuthProvider: React.FC<AuthProviderProps>;
57
+ //# sourceMappingURL=AuthContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthContext.d.ts","sourceRoot":"","sources":["../../src/auth/AuthContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EACL,UAAU,EAEV,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,OAAO,EACZ,KAAK,qBAAqB,EAC3B,MAAM,wBAAwB,CAAC;AAEhC;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,yBAAyB;IACzB,KAAK,EAAE,SAAS,CAAC;IACjB,yCAAyC;IACzC,MAAM,EAAE,UAAU,CAAC;IACnB,yDAAyD;IACzD,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,6BAA6B;IAC7B,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChE,gCAAgC;IAChC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,sDAAsD;IACtD,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,4EAA4E;IAC5E,gBAAgB,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAC1D;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,yBAAyB;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,GAAG,CACA;IACE,gDAAgD;IAChD,MAAM,EAAE,UAAU,CAAC;IACnB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,KAAK,CAAC;CAChB,GACD;IACE,yBAAyB;IACzB,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,uCAAuC;IACvC,MAAM,EAAE,UAAU,CAAC;CACpB,CACJ,CAAC;AAEF,eAAO,MAAM,WAAW,wCAA+C,CAAC;AAExE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA6CpD,CAAC"}
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { createAuthClient, } from '@cohostvip/cohost-auth';
4
+ export const AuthContext = createContext(null);
5
+ /**
6
+ * Provider component that wraps your app and provides auth context
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * import { AuthProvider } from '@cohostvip/cohost-react';
11
+ *
12
+ * function App() {
13
+ * return (
14
+ * <AuthProvider config={{ apiUrl: '/api' }}>
15
+ * <MyApp />
16
+ * </AuthProvider>
17
+ * );
18
+ * }
19
+ * ```
20
+ */
21
+ export const AuthProvider = ({ children, config, client: providedClient, }) => {
22
+ // Create or use provided client (stable reference)
23
+ const clientRef = useRef(null);
24
+ if (!clientRef.current) {
25
+ clientRef.current = providedClient ?? createAuthClient(config);
26
+ }
27
+ const client = clientRef.current;
28
+ // Track auth state
29
+ const [state, setState] = useState(() => client.getState());
30
+ // Initialize client and subscribe to state changes
31
+ useEffect(() => {
32
+ // Initialize on mount
33
+ client.initialize();
34
+ // Subscribe to state changes
35
+ const unsubscribe = client.onAuthStateChanged((newState) => {
36
+ setState(newState);
37
+ });
38
+ return () => {
39
+ unsubscribe();
40
+ };
41
+ }, [client]);
42
+ // Memoize context value to prevent unnecessary re-renders
43
+ const value = useMemo(() => ({
44
+ state,
45
+ client,
46
+ requestOTP: (contact, type) => client.requestOTP(contact, type),
47
+ verifyOTP: (contact, code) => client.verifyOTP(contact, code),
48
+ signOut: () => client.signOut(),
49
+ getToken: () => client.getToken(),
50
+ setAuthenticated: (input) => client.setAuthenticated(input),
51
+ }), [state, client]);
52
+ return _jsx(AuthContext.Provider, { value: value, children: children });
53
+ };
@@ -0,0 +1,43 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * Props for AuthGuard component
4
+ */
5
+ export interface AuthGuardProps {
6
+ /** Content to render when authenticated */
7
+ children: React.ReactNode;
8
+ /** Content to render while loading auth state (default: null) */
9
+ loadingFallback?: React.ReactNode;
10
+ /** Content to render when not authenticated (default: null) */
11
+ unauthenticatedFallback?: React.ReactNode;
12
+ /** Callback when user is not authenticated (e.g., for redirects) */
13
+ onUnauthenticated?: () => void;
14
+ }
15
+ /**
16
+ * Component that only renders children when authenticated
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * // Basic usage
21
+ * <AuthGuard>
22
+ * <ProtectedContent />
23
+ * </AuthGuard>
24
+ *
25
+ * // With fallbacks
26
+ * <AuthGuard
27
+ * loadingFallback={<Spinner />}
28
+ * unauthenticatedFallback={<LoginPrompt />}
29
+ * >
30
+ * <Dashboard />
31
+ * </AuthGuard>
32
+ *
33
+ * // With redirect callback
34
+ * <AuthGuard
35
+ * onUnauthenticated={() => router.push('/login')}
36
+ * loadingFallback={<Spinner />}
37
+ * >
38
+ * <AdminPanel />
39
+ * </AuthGuard>
40
+ * ```
41
+ */
42
+ export declare const AuthGuard: React.FC<AuthGuardProps>;
43
+ //# sourceMappingURL=AuthGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthGuard.d.ts","sourceRoot":"","sources":["../../src/auth/AuthGuard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,2CAA2C;IAC3C,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,iEAAiE;IACjE,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAClC,+DAA+D;IAC/D,uBAAuB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1C,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAwB9C,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useAuth } from './hooks';
3
+ /**
4
+ * Component that only renders children when authenticated
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * // Basic usage
9
+ * <AuthGuard>
10
+ * <ProtectedContent />
11
+ * </AuthGuard>
12
+ *
13
+ * // With fallbacks
14
+ * <AuthGuard
15
+ * loadingFallback={<Spinner />}
16
+ * unauthenticatedFallback={<LoginPrompt />}
17
+ * >
18
+ * <Dashboard />
19
+ * </AuthGuard>
20
+ *
21
+ * // With redirect callback
22
+ * <AuthGuard
23
+ * onUnauthenticated={() => router.push('/login')}
24
+ * loadingFallback={<Spinner />}
25
+ * >
26
+ * <AdminPanel />
27
+ * </AuthGuard>
28
+ * ```
29
+ */
30
+ export const AuthGuard = ({ children, loadingFallback = null, unauthenticatedFallback = null, onUnauthenticated, }) => {
31
+ const { state } = useAuth();
32
+ // Show loading fallback while auth state is being determined
33
+ if (state.isLoading) {
34
+ return _jsx(_Fragment, { children: loadingFallback });
35
+ }
36
+ // Handle unauthenticated state
37
+ if (!state.isAuthenticated) {
38
+ // Call redirect callback if provided
39
+ if (onUnauthenticated) {
40
+ onUnauthenticated();
41
+ }
42
+ return _jsx(_Fragment, { children: unauthenticatedFallback });
43
+ }
44
+ // User is authenticated, render children
45
+ return _jsx(_Fragment, { children: children });
46
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=auth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.test.d.ts","sourceRoot":"","sources":["../../../src/auth/__tests__/auth.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,241 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
3
+ import { render, screen, waitFor } from '@testing-library/react';
4
+ import * as React from 'react';
5
+ import { AuthProvider, useAuth, useUser, useAuthClient, AuthGuard } from '../index';
6
+ // Mock fetch globally
7
+ const mockFetch = vi.fn();
8
+ global.fetch = mockFetch;
9
+ const mockOkResponse = (data) => ({
10
+ ok: true,
11
+ status: 200,
12
+ headers: new Headers({ 'content-type': 'application/json' }),
13
+ json: async () => ({ status: 'ok', data }),
14
+ });
15
+ const defaultConfig = {
16
+ apiUrl: 'https://api.example.com',
17
+ storage: 'memory',
18
+ autoRefresh: false,
19
+ };
20
+ describe('AuthProvider', () => {
21
+ beforeEach(() => {
22
+ mockFetch.mockReset();
23
+ });
24
+ it('should render children', () => {
25
+ render(_jsx(AuthProvider, { config: defaultConfig, children: _jsx("div", { "data-testid": "child", children: "Hello" }) }));
26
+ expect(screen.getByTestId('child')).toBeInTheDocument();
27
+ });
28
+ it('should provide auth context to children', () => {
29
+ function TestComponent() {
30
+ const auth = useAuth();
31
+ return _jsx("div", { "data-testid": "has-auth", children: auth ? 'yes' : 'no' });
32
+ }
33
+ render(_jsx(AuthProvider, { config: defaultConfig, children: _jsx(TestComponent, {}) }));
34
+ expect(screen.getByTestId('has-auth')).toHaveTextContent('yes');
35
+ });
36
+ it('should initialize and eventually set not authenticated', async () => {
37
+ function TestComponent() {
38
+ const { state } = useAuth();
39
+ return (_jsxs("div", { children: [_jsx("span", { "data-testid": "loading", children: state.isLoading ? 'yes' : 'no' }), _jsx("span", { "data-testid": "authenticated", children: state.isAuthenticated ? 'yes' : 'no' })] }));
40
+ }
41
+ render(_jsx(AuthProvider, { config: defaultConfig, children: _jsx(TestComponent, {}) }));
42
+ // Wait for initialization to complete
43
+ await waitFor(() => {
44
+ expect(screen.getByTestId('loading')).toHaveTextContent('no');
45
+ });
46
+ expect(screen.getByTestId('authenticated')).toHaveTextContent('no');
47
+ });
48
+ });
49
+ describe('useAuth', () => {
50
+ beforeEach(() => {
51
+ mockFetch.mockReset();
52
+ });
53
+ it('should throw if used outside AuthProvider', () => {
54
+ // Suppress console.error for this test
55
+ const spy = vi.spyOn(console, 'error').mockImplementation(() => { });
56
+ function TestComponent() {
57
+ useAuth();
58
+ return null;
59
+ }
60
+ expect(() => render(_jsx(TestComponent, {}))).toThrow('useAuth must be used within an AuthProvider');
61
+ spy.mockRestore();
62
+ });
63
+ it('should provide auth methods', () => {
64
+ function TestComponent() {
65
+ const { requestOTP, verifyOTP, signOut, getToken } = useAuth();
66
+ return (_jsx("div", { "data-testid": "has-methods", children: typeof requestOTP === 'function' &&
67
+ typeof verifyOTP === 'function' &&
68
+ typeof signOut === 'function' &&
69
+ typeof getToken === 'function'
70
+ ? 'yes'
71
+ : 'no' }));
72
+ }
73
+ render(_jsx(AuthProvider, { config: defaultConfig, children: _jsx(TestComponent, {}) }));
74
+ expect(screen.getByTestId('has-methods')).toHaveTextContent('yes');
75
+ });
76
+ it('should call requestOTP successfully', async () => {
77
+ mockFetch.mockResolvedValue(mockOkResponse({ sent: true }));
78
+ let requestResult = null;
79
+ function TestComponent() {
80
+ const { requestOTP } = useAuth();
81
+ React.useEffect(() => {
82
+ requestOTP('test@example.com').then((result) => {
83
+ requestResult = result;
84
+ });
85
+ }, [requestOTP]);
86
+ return null;
87
+ }
88
+ render(_jsx(AuthProvider, { config: defaultConfig, children: _jsx(TestComponent, {}) }));
89
+ await waitFor(() => {
90
+ expect(requestResult).toBe(true);
91
+ });
92
+ expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/otp/request', expect.objectContaining({
93
+ method: 'POST',
94
+ body: expect.stringContaining('test@example.com'),
95
+ }));
96
+ });
97
+ it('should authenticate user with verifyOTP', async () => {
98
+ const authResult = {
99
+ user: {
100
+ uid: '123',
101
+ email: 'test@example.com',
102
+ emailVerified: true,
103
+ provider: 'otp',
104
+ providerId: 'cohost',
105
+ },
106
+ customToken: 'jwt-token',
107
+ isNewUser: false,
108
+ };
109
+ mockFetch.mockResolvedValue(mockOkResponse(authResult));
110
+ function TestComponent() {
111
+ const { state, verifyOTP } = useAuth();
112
+ const [verified, setVerified] = React.useState(false);
113
+ React.useEffect(() => {
114
+ if (!verified) {
115
+ setVerified(true);
116
+ verifyOTP('test@example.com', '123456');
117
+ }
118
+ }, [verified, verifyOTP]);
119
+ return (_jsxs("div", { children: [_jsx("span", { "data-testid": "authenticated", children: state.isAuthenticated ? 'yes' : 'no' }), _jsx("span", { "data-testid": "email", children: state.user?.email || 'none' })] }));
120
+ }
121
+ render(_jsx(AuthProvider, { config: defaultConfig, children: _jsx(TestComponent, {}) }));
122
+ await waitFor(() => {
123
+ expect(screen.getByTestId('authenticated')).toHaveTextContent('yes');
124
+ });
125
+ expect(screen.getByTestId('email')).toHaveTextContent('test@example.com');
126
+ });
127
+ });
128
+ describe('useUser', () => {
129
+ beforeEach(() => {
130
+ mockFetch.mockReset();
131
+ });
132
+ it('should return null when not authenticated', async () => {
133
+ function TestComponent() {
134
+ const user = useUser();
135
+ const { state } = useAuth();
136
+ return (_jsxs("div", { children: [_jsx("span", { "data-testid": "loading", children: state.isLoading ? 'yes' : 'no' }), _jsx("span", { "data-testid": "user", children: user ? user.email : 'null' })] }));
137
+ }
138
+ render(_jsx(AuthProvider, { config: defaultConfig, children: _jsx(TestComponent, {}) }));
139
+ await waitFor(() => {
140
+ expect(screen.getByTestId('loading')).toHaveTextContent('no');
141
+ });
142
+ expect(screen.getByTestId('user')).toHaveTextContent('null');
143
+ });
144
+ it('should return user when authenticated', async () => {
145
+ const authResult = {
146
+ user: {
147
+ uid: '123',
148
+ email: 'test@example.com',
149
+ emailVerified: true,
150
+ provider: 'otp',
151
+ providerId: 'cohost',
152
+ },
153
+ customToken: 'jwt-token',
154
+ isNewUser: false,
155
+ };
156
+ mockFetch.mockResolvedValue(mockOkResponse(authResult));
157
+ function TestComponent() {
158
+ const { verifyOTP } = useAuth();
159
+ const user = useUser();
160
+ const [verified, setVerified] = React.useState(false);
161
+ React.useEffect(() => {
162
+ if (!verified) {
163
+ setVerified(true);
164
+ verifyOTP('test@example.com', '123456');
165
+ }
166
+ }, [verified, verifyOTP]);
167
+ return _jsx("div", { "data-testid": "user", children: user ? user.email : 'null' });
168
+ }
169
+ render(_jsx(AuthProvider, { config: defaultConfig, children: _jsx(TestComponent, {}) }));
170
+ await waitFor(() => {
171
+ expect(screen.getByTestId('user')).toHaveTextContent('test@example.com');
172
+ });
173
+ });
174
+ });
175
+ describe('useAuthClient', () => {
176
+ it('should return the AuthClient instance', () => {
177
+ function TestComponent() {
178
+ const client = useAuthClient();
179
+ return (_jsx("div", { "data-testid": "has-client", children: client && typeof client.initialize === 'function' ? 'yes' : 'no' }));
180
+ }
181
+ render(_jsx(AuthProvider, { config: defaultConfig, children: _jsx(TestComponent, {}) }));
182
+ expect(screen.getByTestId('has-client')).toHaveTextContent('yes');
183
+ });
184
+ });
185
+ describe('AuthGuard', () => {
186
+ beforeEach(() => {
187
+ mockFetch.mockReset();
188
+ });
189
+ it('should show unauthenticated fallback when not authenticated', async () => {
190
+ function TestComponent() {
191
+ return (_jsx(AuthGuard, { loadingFallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), unauthenticatedFallback: _jsx("div", { "data-testid": "login", children: "Please log in" }), children: _jsx("div", { "data-testid": "protected", children: "Protected Content" }) }));
192
+ }
193
+ render(_jsx(AuthProvider, { config: defaultConfig, children: _jsx(TestComponent, {}) }));
194
+ // Wait for initialization to complete
195
+ await waitFor(() => {
196
+ expect(screen.queryByTestId('loading')).not.toBeInTheDocument();
197
+ });
198
+ expect(screen.getByTestId('login')).toBeInTheDocument();
199
+ expect(screen.queryByTestId('protected')).not.toBeInTheDocument();
200
+ });
201
+ it('should call onUnauthenticated callback when not authenticated', async () => {
202
+ const onUnauthenticated = vi.fn();
203
+ function TestComponent() {
204
+ return (_jsx(AuthGuard, { onUnauthenticated: onUnauthenticated, children: _jsx("div", { "data-testid": "protected", children: "Protected Content" }) }));
205
+ }
206
+ render(_jsx(AuthProvider, { config: defaultConfig, children: _jsx(TestComponent, {}) }));
207
+ await waitFor(() => {
208
+ expect(onUnauthenticated).toHaveBeenCalled();
209
+ });
210
+ });
211
+ it('should show children when authenticated', async () => {
212
+ const authResult = {
213
+ user: {
214
+ uid: '123',
215
+ email: 'test@example.com',
216
+ emailVerified: true,
217
+ provider: 'otp',
218
+ providerId: 'cohost',
219
+ },
220
+ customToken: 'jwt-token',
221
+ isNewUser: false,
222
+ };
223
+ mockFetch.mockResolvedValue(mockOkResponse(authResult));
224
+ function Wrapper() {
225
+ const { verifyOTP, state } = useAuth();
226
+ const [verified, setVerified] = React.useState(false);
227
+ React.useEffect(() => {
228
+ if (!verified && !state.isAuthenticated) {
229
+ setVerified(true);
230
+ verifyOTP('test@example.com', '123456');
231
+ }
232
+ }, [verified, verifyOTP, state.isAuthenticated]);
233
+ return (_jsx(AuthGuard, { loadingFallback: _jsx("div", { "data-testid": "loading", children: "Loading..." }), unauthenticatedFallback: _jsx("div", { "data-testid": "login", children: "Please log in" }), children: _jsx("div", { "data-testid": "protected", children: "Protected Content" }) }));
234
+ }
235
+ render(_jsx(AuthProvider, { config: defaultConfig, children: _jsx(Wrapper, {}) }));
236
+ await waitFor(() => {
237
+ expect(screen.getByTestId('protected')).toBeInTheDocument();
238
+ });
239
+ expect(screen.queryByTestId('login')).not.toBeInTheDocument();
240
+ });
241
+ });
@@ -0,0 +1,60 @@
1
+ import type { AuthClient, AuthUser } from '@cohostvip/cohost-auth';
2
+ import { type AuthContextValue } from './AuthContext';
3
+ /**
4
+ * Hook to access auth state and methods
5
+ *
6
+ * @throws Error if used outside of AuthProvider
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * function LoginButton() {
11
+ * const { state, requestOTP, verifyOTP, signOut } = useAuth();
12
+ *
13
+ * if (state.isLoading) return <Loading />;
14
+ * if (state.isAuthenticated) {
15
+ * return <button onClick={signOut}>Sign Out</button>;
16
+ * }
17
+ * return <button onClick={() => requestOTP('user@example.com')}>Sign In</button>;
18
+ * }
19
+ * ```
20
+ */
21
+ export declare function useAuth(): AuthContextValue;
22
+ /**
23
+ * Hook to access the current user
24
+ * Returns null if not authenticated
25
+ *
26
+ * @throws Error if used outside of AuthProvider
27
+ *
28
+ * @example
29
+ * ```tsx
30
+ * function Profile() {
31
+ * const user = useUser();
32
+ *
33
+ * if (!user) return <div>Not logged in</div>;
34
+ * return <div>Welcome, {user.displayName || user.email}</div>;
35
+ * }
36
+ * ```
37
+ */
38
+ export declare function useUser(): AuthUser | null;
39
+ /**
40
+ * Hook to access the raw AuthClient instance
41
+ * Useful for advanced use cases or accessing methods not exposed by useAuth
42
+ *
43
+ * @throws Error if used outside of AuthProvider
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * function TokenDisplay() {
48
+ * const client = useAuthClient();
49
+ * const [token, setToken] = useState<string | null>(null);
50
+ *
51
+ * useEffect(() => {
52
+ * client.getToken().then(setToken);
53
+ * }, [client]);
54
+ *
55
+ * return <div>Token: {token}</div>;
56
+ * }
57
+ * ```
58
+ */
59
+ export declare function useAuthClient(): AuthClient;
60
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/auth/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEnE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,IAAI,gBAAgB,CAM1C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,OAAO,IAAI,QAAQ,GAAG,IAAI,CAGzC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,IAAI,UAAU,CAG1C"}
@@ -0,0 +1,71 @@
1
+ import { useContext } from 'react';
2
+ import { AuthContext } from './AuthContext';
3
+ /**
4
+ * Hook to access auth state and methods
5
+ *
6
+ * @throws Error if used outside of AuthProvider
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * function LoginButton() {
11
+ * const { state, requestOTP, verifyOTP, signOut } = useAuth();
12
+ *
13
+ * if (state.isLoading) return <Loading />;
14
+ * if (state.isAuthenticated) {
15
+ * return <button onClick={signOut}>Sign Out</button>;
16
+ * }
17
+ * return <button onClick={() => requestOTP('user@example.com')}>Sign In</button>;
18
+ * }
19
+ * ```
20
+ */
21
+ export function useAuth() {
22
+ const ctx = useContext(AuthContext);
23
+ if (!ctx) {
24
+ throw new Error('useAuth must be used within an AuthProvider');
25
+ }
26
+ return ctx;
27
+ }
28
+ /**
29
+ * Hook to access the current user
30
+ * Returns null if not authenticated
31
+ *
32
+ * @throws Error if used outside of AuthProvider
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * function Profile() {
37
+ * const user = useUser();
38
+ *
39
+ * if (!user) return <div>Not logged in</div>;
40
+ * return <div>Welcome, {user.displayName || user.email}</div>;
41
+ * }
42
+ * ```
43
+ */
44
+ export function useUser() {
45
+ const { state } = useAuth();
46
+ return state.user;
47
+ }
48
+ /**
49
+ * Hook to access the raw AuthClient instance
50
+ * Useful for advanced use cases or accessing methods not exposed by useAuth
51
+ *
52
+ * @throws Error if used outside of AuthProvider
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * function TokenDisplay() {
57
+ * const client = useAuthClient();
58
+ * const [token, setToken] = useState<string | null>(null);
59
+ *
60
+ * useEffect(() => {
61
+ * client.getToken().then(setToken);
62
+ * }, [client]);
63
+ *
64
+ * return <div>Token: {token}</div>;
65
+ * }
66
+ * ```
67
+ */
68
+ export function useAuthClient() {
69
+ const { client } = useAuth();
70
+ return client;
71
+ }
@@ -0,0 +1,4 @@
1
+ export { AuthProvider, type AuthProviderProps, type AuthContextValue } from './AuthContext';
2
+ export { useAuth, useUser, useAuthClient } from './hooks';
3
+ export { AuthGuard, type AuthGuardProps } from './AuthGuard';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAG5F,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG1D,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,6 @@
1
+ // Auth context and provider
2
+ export { AuthProvider } from './AuthContext';
3
+ // Hooks
4
+ export { useAuth, useUser, useAuthClient } from './hooks';
5
+ // Components
6
+ export { AuthGuard } from './AuthGuard';
@@ -5,6 +5,8 @@ export type PaymentElementProviderProps = {
5
5
  };
6
6
  export type PaymentElementContextType = {
7
7
  tokenizeCard: (cardInfo: CreditCardInformation) => Promise<any>;
8
+ paymentIntent: any | null;
9
+ isLoading: boolean;
8
10
  };
9
11
  export declare const PaymentElementProvider: React.FC<PaymentElementProviderProps>;
10
12
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"PaymentElementContext.d.ts","sourceRoot":"","sources":["../../src/context/PaymentElementContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyD,MAAM,OAAO,CAAC;AAE9E,OAAO,EAAE,qBAAqB,EAAa,MAAM,yBAAyB,CAAC;AAG3E,MAAM,MAAM,2BAA2B,GAAG;IACtC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACpC,YAAY,EAAE,CAAC,QAAQ,EAAE,qBAAqB,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CACnE,CAAC;AAUF,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CAkDxE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAAO,yBAIpC,CAAC"}
1
+ {"version":3,"file":"PaymentElementContext.d.ts","sourceRoot":"","sources":["../../src/context/PaymentElementContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAiE,MAAM,OAAO,CAAC;AAGtF,OAAO,EAAE,qBAAqB,EAAa,MAAM,yBAAyB,CAAC;AAG3E,MAAM,MAAM,2BAA2B,GAAG;IACtC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACpC,YAAY,EAAE,CAAC,QAAQ,EAAE,qBAAqB,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IAChE,aAAa,EAAE,GAAG,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;CACtB,CAAC;AAUF,eAAO,MAAM,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC,2BAA2B,CA2ExE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAAO,yBAIpC,CAAC"}
@@ -1,20 +1,23 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useEffect, useState } from 'react';
2
+ import { createContext, useContext, useEffect, useState, useRef } from 'react';
3
3
  import { useCohostCheckout } from './CohostCheckoutContext';
4
+ import { useCohostClient } from './CohostContext';
4
5
  import { authnetTokenizer } from '../lib/tokenizers/authnet';
5
6
  const PaymentElementContext = createContext(null);
6
7
  const tokenizers = {
7
8
  'authnet': authnetTokenizer
8
9
  };
9
10
  export const PaymentElementProvider = ({ children }) => {
10
- const { cartSession } = useCohostCheckout();
11
+ const { cartSession, cartSessionId } = useCohostCheckout();
12
+ const { client } = useCohostClient();
11
13
  const [paymentIntent, setPaymentIntent] = useState(null);
12
14
  const [tokenizer, setTokenizer] = useState(null);
15
+ const [isLoading, setIsLoading] = useState(false);
16
+ const fetchAttemptedRef = useRef(false);
13
17
  const tokenizeCard = async (cardInfo) => {
14
18
  if (!tokenizer) {
15
19
  throw new Error("Tokenizer not found");
16
20
  }
17
- const paymentIntent = cartSession?.meta?.paymentIntent;
18
21
  if (!paymentIntent) {
19
22
  throw new Error("Payment intent not found");
20
23
  }
@@ -32,10 +35,35 @@ export const PaymentElementProvider = ({ children }) => {
32
35
  }
33
36
  }, [paymentIntent]);
34
37
  useEffect(() => {
35
- setPaymentIntent(cartSession?.meta?.paymentIntent || null);
36
- }, [cartSession]);
38
+ if (!cartSession) {
39
+ return;
40
+ }
41
+ // If cart session has payment intent, use it
42
+ if (cartSession.meta?.paymentIntent) {
43
+ setPaymentIntent(cartSession.meta.paymentIntent);
44
+ return;
45
+ }
46
+ // Only fetch once per mount
47
+ if (fetchAttemptedRef.current) {
48
+ return;
49
+ }
50
+ fetchAttemptedRef.current = true;
51
+ setIsLoading(true);
52
+ client.cart.getPaymentIntent(cartSessionId)
53
+ .then((result) => {
54
+ setPaymentIntent(result);
55
+ })
56
+ .catch((error) => {
57
+ console.error("Error fetching payment intent:", error);
58
+ })
59
+ .finally(() => {
60
+ setIsLoading(false);
61
+ });
62
+ }, [cartSession, cartSessionId, client]);
37
63
  return (_jsx(PaymentElementContext.Provider, { value: {
38
- tokenizeCard
64
+ tokenizeCard,
65
+ paymentIntent,
66
+ isLoading,
39
67
  }, children: children }));
40
68
  };
41
69
  /**
package/dist/index.d.ts CHANGED
@@ -6,4 +6,5 @@ export { CohostStartCheckoutProvider } from './provider/CohostStartCheckoutProvi
6
6
  export { CreditCardInformation } from './lib/tokenizers/types';
7
7
  export { useCohost } from './hooks/useCohost';
8
8
  export { formatCurrency } from './lib/utils';
9
+ export { AuthProvider, type AuthProviderProps, type AuthContextValue, useAuth, useUser, useAuthClient, AuthGuard, type AuthGuardProps, } from './auth';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACnH,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACpG,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAC5F,OAAO,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACnH,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACpG,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAC5F,OAAO,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,OAAO,EACP,OAAO,EACP,aAAa,EACb,SAAS,EACT,KAAK,cAAc,GACpB,MAAM,QAAQ,CAAC"}
package/dist/index.js CHANGED
@@ -5,3 +5,5 @@ export { PaymentElementProvider, usePaymentElement } from './context/PaymentElem
5
5
  export { CohostStartCheckoutProvider } from './provider/CohostStartCheckoutProvider';
6
6
  export { useCohost } from './hooks/useCohost';
7
7
  export { formatCurrency } from './lib/utils';
8
+ // Auth bindings
9
+ export { AuthProvider, useAuth, useUser, useAuthClient, AuthGuard, } from './auth';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cohostvip/cohost-react",
3
- "version": "0.2.4",
3
+ "version": "0.3.4",
4
4
  "description": "React bindings for the Cohost API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -27,7 +27,8 @@
27
27
  "react-dom": "^18.0.0 || ^19.0.0"
28
28
  },
29
29
  "dependencies": {
30
- "@cohostvip/cohost-node": "^0.2.4"
30
+ "@cohostvip/cohost-node": "workspace:^",
31
+ "@cohostvip/cohost-auth": "workspace:*"
31
32
  },
32
33
  "devDependencies": {
33
34
  "@testing-library/jest-dom": "6.6.3",