@pixygon/auth 1.0.0
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/README.md +267 -0
- package/dist/chunk-E34M2RJD.mjs +1003 -0
- package/dist/components/index.d.mts +14 -0
- package/dist/components/index.d.ts +14 -0
- package/dist/components/index.js +1720 -0
- package/dist/components/index.mjs +1646 -0
- package/dist/index-CIK2MKl9.d.mts +201 -0
- package/dist/index-CIK2MKl9.d.ts +201 -0
- package/dist/index.d.mts +233 -0
- package/dist/index.d.ts +233 -0
- package/dist/index.js +1037 -0
- package/dist/index.mjs +28 -0
- package/package.json +58 -0
- package/src/api/client.ts +358 -0
- package/src/components/ForgotPasswordForm.tsx +410 -0
- package/src/components/LoginForm.tsx +323 -0
- package/src/components/PixygonAuth.tsx +170 -0
- package/src/components/RegisterForm.tsx +463 -0
- package/src/components/VerifyForm.tsx +411 -0
- package/src/components/index.ts +40 -0
- package/src/components/styles.ts +282 -0
- package/src/hooks/index.ts +284 -0
- package/src/hooks/useProfileSync.ts +201 -0
- package/src/index.ts +99 -0
- package/src/providers/AuthProvider.tsx +480 -0
- package/src/types/index.ts +310 -0
- package/src/utils/storage.ts +167 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile Sync Hook
|
|
3
|
+
* Synchronizes user profile data across Pixygon applications
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
7
|
+
import { useAuthContext } from '../providers/AuthProvider';
|
|
8
|
+
import type { User } from '../types';
|
|
9
|
+
|
|
10
|
+
export interface UserProfile extends User {
|
|
11
|
+
displayName?: string;
|
|
12
|
+
avatar?: string;
|
|
13
|
+
bio?: string;
|
|
14
|
+
preferences?: Record<string, unknown>;
|
|
15
|
+
linkedApps?: string[];
|
|
16
|
+
lastSyncedAt?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UseProfileSyncReturn {
|
|
20
|
+
/** Current user profile */
|
|
21
|
+
profile: UserProfile | null;
|
|
22
|
+
/** Whether the profile is loading */
|
|
23
|
+
isLoading: boolean;
|
|
24
|
+
/** Whether the profile is syncing */
|
|
25
|
+
isSyncing: boolean;
|
|
26
|
+
/** Error if any */
|
|
27
|
+
error: Error | null;
|
|
28
|
+
/** Update profile */
|
|
29
|
+
updateProfile: (updates: Partial<UserProfile>) => Promise<void>;
|
|
30
|
+
/** Force sync profile from server */
|
|
31
|
+
syncProfile: () => Promise<void>;
|
|
32
|
+
/** Update preferences */
|
|
33
|
+
updatePreferences: (preferences: Record<string, unknown>) => Promise<void>;
|
|
34
|
+
/** Link another app to profile */
|
|
35
|
+
linkApp: (appId: string) => Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Hook to sync user profile across apps
|
|
40
|
+
*/
|
|
41
|
+
export function useProfileSync(): UseProfileSyncReturn {
|
|
42
|
+
const { user, config, getAccessToken } = useAuthContext();
|
|
43
|
+
const [profile, setProfile] = useState<UserProfile | null>(null);
|
|
44
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
45
|
+
const [isSyncing, setIsSyncing] = useState(false);
|
|
46
|
+
const [error, setError] = useState<Error | null>(null);
|
|
47
|
+
|
|
48
|
+
// Fetch profile from server
|
|
49
|
+
const fetchProfile = useCallback(async () => {
|
|
50
|
+
const token = getAccessToken();
|
|
51
|
+
if (!token || !user) {
|
|
52
|
+
setProfile(null);
|
|
53
|
+
setIsLoading(false);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const response = await fetch(`${config.baseUrl}/users/profile`, {
|
|
59
|
+
headers: {
|
|
60
|
+
'Authorization': `Bearer ${token}`,
|
|
61
|
+
'Content-Type': 'application/json',
|
|
62
|
+
'X-App-Id': config.appId,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
throw new Error('Failed to fetch profile');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const data = await response.json();
|
|
71
|
+
setProfile({
|
|
72
|
+
...user,
|
|
73
|
+
...data.user,
|
|
74
|
+
lastSyncedAt: new Date().toISOString(),
|
|
75
|
+
});
|
|
76
|
+
setError(null);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
setError(err instanceof Error ? err : new Error('Failed to fetch profile'));
|
|
79
|
+
// Use local user data as fallback
|
|
80
|
+
setProfile(user ? { ...user, lastSyncedAt: undefined } : null);
|
|
81
|
+
} finally {
|
|
82
|
+
setIsLoading(false);
|
|
83
|
+
}
|
|
84
|
+
}, [config.baseUrl, config.appId, getAccessToken, user]);
|
|
85
|
+
|
|
86
|
+
// Initial load
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (user) {
|
|
89
|
+
fetchProfile();
|
|
90
|
+
} else {
|
|
91
|
+
setProfile(null);
|
|
92
|
+
setIsLoading(false);
|
|
93
|
+
}
|
|
94
|
+
}, [user, fetchProfile]);
|
|
95
|
+
|
|
96
|
+
// Sync profile from server
|
|
97
|
+
const syncProfile = useCallback(async () => {
|
|
98
|
+
if (!user) return;
|
|
99
|
+
|
|
100
|
+
setIsSyncing(true);
|
|
101
|
+
await fetchProfile();
|
|
102
|
+
setIsSyncing(false);
|
|
103
|
+
}, [user, fetchProfile]);
|
|
104
|
+
|
|
105
|
+
// Update profile
|
|
106
|
+
const updateProfile = useCallback(async (updates: Partial<UserProfile>) => {
|
|
107
|
+
const token = getAccessToken();
|
|
108
|
+
if (!token || !profile) {
|
|
109
|
+
throw new Error('Not authenticated');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setIsSyncing(true);
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetch(`${config.baseUrl}/users/profile`, {
|
|
115
|
+
method: 'PATCH',
|
|
116
|
+
headers: {
|
|
117
|
+
'Authorization': `Bearer ${token}`,
|
|
118
|
+
'Content-Type': 'application/json',
|
|
119
|
+
'X-App-Id': config.appId,
|
|
120
|
+
},
|
|
121
|
+
body: JSON.stringify(updates),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
const errorData = await response.json().catch(() => ({}));
|
|
126
|
+
throw new Error(errorData.message || 'Failed to update profile');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const data = await response.json();
|
|
130
|
+
setProfile({
|
|
131
|
+
...profile,
|
|
132
|
+
...data.user,
|
|
133
|
+
lastSyncedAt: new Date().toISOString(),
|
|
134
|
+
});
|
|
135
|
+
setError(null);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
const error = err instanceof Error ? err : new Error('Failed to update profile');
|
|
138
|
+
setError(error);
|
|
139
|
+
throw error;
|
|
140
|
+
} finally {
|
|
141
|
+
setIsSyncing(false);
|
|
142
|
+
}
|
|
143
|
+
}, [config.baseUrl, config.appId, getAccessToken, profile]);
|
|
144
|
+
|
|
145
|
+
// Update preferences
|
|
146
|
+
const updatePreferences = useCallback(async (preferences: Record<string, unknown>) => {
|
|
147
|
+
await updateProfile({
|
|
148
|
+
preferences: {
|
|
149
|
+
...profile?.preferences,
|
|
150
|
+
...preferences,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}, [updateProfile, profile?.preferences]);
|
|
154
|
+
|
|
155
|
+
// Link another app
|
|
156
|
+
const linkApp = useCallback(async (appId: string) => {
|
|
157
|
+
const token = getAccessToken();
|
|
158
|
+
if (!token) {
|
|
159
|
+
throw new Error('Not authenticated');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
setIsSyncing(true);
|
|
163
|
+
try {
|
|
164
|
+
const response = await fetch(`${config.baseUrl}/users/link-app`, {
|
|
165
|
+
method: 'POST',
|
|
166
|
+
headers: {
|
|
167
|
+
'Authorization': `Bearer ${token}`,
|
|
168
|
+
'Content-Type': 'application/json',
|
|
169
|
+
'X-App-Id': config.appId,
|
|
170
|
+
},
|
|
171
|
+
body: JSON.stringify({ appId }),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
if (!response.ok) {
|
|
175
|
+
const errorData = await response.json().catch(() => ({}));
|
|
176
|
+
throw new Error(errorData.message || 'Failed to link app');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Refresh profile to get updated linked apps
|
|
180
|
+
await fetchProfile();
|
|
181
|
+
setError(null);
|
|
182
|
+
} catch (err) {
|
|
183
|
+
const error = err instanceof Error ? err : new Error('Failed to link app');
|
|
184
|
+
setError(error);
|
|
185
|
+
throw error;
|
|
186
|
+
} finally {
|
|
187
|
+
setIsSyncing(false);
|
|
188
|
+
}
|
|
189
|
+
}, [config.baseUrl, config.appId, getAccessToken, fetchProfile]);
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
profile,
|
|
193
|
+
isLoading,
|
|
194
|
+
isSyncing,
|
|
195
|
+
error,
|
|
196
|
+
updateProfile,
|
|
197
|
+
syncProfile,
|
|
198
|
+
updatePreferences,
|
|
199
|
+
linkApp,
|
|
200
|
+
};
|
|
201
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pixygon/auth
|
|
3
|
+
* Shared authentication package for all Pixygon applications
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* ```tsx
|
|
7
|
+
* import { AuthProvider, useAuth } from '@pixygon/auth';
|
|
8
|
+
*
|
|
9
|
+
* // Wrap your app
|
|
10
|
+
* <AuthProvider config={{ baseUrl: '...', appId: 'my-app' }}>
|
|
11
|
+
* <App />
|
|
12
|
+
* </AuthProvider>
|
|
13
|
+
*
|
|
14
|
+
* // Use in components
|
|
15
|
+
* const { user, login, logout, isAuthenticated } = useAuth();
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Types
|
|
20
|
+
export type {
|
|
21
|
+
// User types
|
|
22
|
+
User,
|
|
23
|
+
UserRole,
|
|
24
|
+
SubscriptionTier,
|
|
25
|
+
|
|
26
|
+
// Token types
|
|
27
|
+
TokenPair,
|
|
28
|
+
TokenPayload,
|
|
29
|
+
|
|
30
|
+
// State types
|
|
31
|
+
AuthState,
|
|
32
|
+
AuthStatus,
|
|
33
|
+
|
|
34
|
+
// Request/Response types
|
|
35
|
+
LoginRequest,
|
|
36
|
+
LoginResponse,
|
|
37
|
+
RegisterRequest,
|
|
38
|
+
RegisterResponse,
|
|
39
|
+
VerifyRequest,
|
|
40
|
+
VerifyResponse,
|
|
41
|
+
ForgotPasswordRequest,
|
|
42
|
+
ForgotPasswordResponse,
|
|
43
|
+
RecoverPasswordRequest,
|
|
44
|
+
RecoverPasswordResponse,
|
|
45
|
+
RefreshTokenRequest,
|
|
46
|
+
RefreshTokenResponse,
|
|
47
|
+
ResendVerificationRequest,
|
|
48
|
+
ResendVerificationResponse,
|
|
49
|
+
|
|
50
|
+
// Error types
|
|
51
|
+
AuthError,
|
|
52
|
+
AuthErrorCode,
|
|
53
|
+
|
|
54
|
+
// Config types
|
|
55
|
+
AuthConfig,
|
|
56
|
+
AuthStorage,
|
|
57
|
+
AuthContextValue,
|
|
58
|
+
|
|
59
|
+
// Component props
|
|
60
|
+
LoginFormProps,
|
|
61
|
+
RegisterFormProps,
|
|
62
|
+
VerifyFormProps,
|
|
63
|
+
ForgotPasswordFormProps,
|
|
64
|
+
PixygonAuthProps,
|
|
65
|
+
} from './types';
|
|
66
|
+
|
|
67
|
+
// Provider
|
|
68
|
+
export { AuthProvider, AuthContext, useAuthContext } from './providers/AuthProvider';
|
|
69
|
+
export type { AuthProviderProps } from './providers/AuthProvider';
|
|
70
|
+
|
|
71
|
+
// Hooks
|
|
72
|
+
export {
|
|
73
|
+
useAuth,
|
|
74
|
+
useUser,
|
|
75
|
+
useToken,
|
|
76
|
+
useAuthStatus,
|
|
77
|
+
useRequireAuth,
|
|
78
|
+
useAuthError,
|
|
79
|
+
useProfileSync,
|
|
80
|
+
} from './hooks';
|
|
81
|
+
export type {
|
|
82
|
+
UseAuthReturn,
|
|
83
|
+
UseUserReturn,
|
|
84
|
+
UseTokenReturn,
|
|
85
|
+
UseAuthStatusReturn,
|
|
86
|
+
UseRequireAuthOptions,
|
|
87
|
+
UseRequireAuthReturn,
|
|
88
|
+
UseAuthErrorReturn,
|
|
89
|
+
UseProfileSyncReturn,
|
|
90
|
+
UserProfile,
|
|
91
|
+
} from './hooks';
|
|
92
|
+
|
|
93
|
+
// Utilities
|
|
94
|
+
export { createTokenStorage } from './utils/storage';
|
|
95
|
+
export type { TokenStorage } from './utils/storage';
|
|
96
|
+
|
|
97
|
+
// API Client
|
|
98
|
+
export { createAuthApiClient } from './api/client';
|
|
99
|
+
export type { AuthApiClient } from './api/client';
|