@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.
@@ -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';