@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,282 @@
1
+ /**
2
+ * @pixygon/auth - Component Styles
3
+ * CSS-in-JS styles for auth components
4
+ */
5
+
6
+ export const colors = {
7
+ // Brand colors
8
+ primary: '#6366f1', // Indigo
9
+ primaryHover: '#4f46e5',
10
+ primaryLight: '#818cf8',
11
+
12
+ // Neutrals
13
+ background: '#0f0f0f',
14
+ backgroundLight: '#1a1a1a',
15
+ surface: '#262626',
16
+ surfaceHover: '#333333',
17
+ border: '#404040',
18
+ borderFocus: '#6366f1',
19
+
20
+ // Text
21
+ text: '#ffffff',
22
+ textSecondary: '#a3a3a3',
23
+ textMuted: '#737373',
24
+
25
+ // States
26
+ error: '#ef4444',
27
+ errorLight: '#fca5a5',
28
+ success: '#22c55e',
29
+ successLight: '#86efac',
30
+ warning: '#f59e0b',
31
+ };
32
+
33
+ export const styles = {
34
+ // Container
35
+ container: `
36
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
37
+ background: ${colors.background};
38
+ color: ${colors.text};
39
+ padding: 2rem;
40
+ border-radius: 1rem;
41
+ max-width: 400px;
42
+ width: 100%;
43
+ margin: 0 auto;
44
+ `,
45
+
46
+ // Header
47
+ header: `
48
+ text-align: center;
49
+ margin-bottom: 2rem;
50
+ `,
51
+
52
+ logo: `
53
+ width: 64px;
54
+ height: 64px;
55
+ margin: 0 auto 1rem;
56
+ display: block;
57
+ `,
58
+
59
+ title: `
60
+ font-size: 1.5rem;
61
+ font-weight: 600;
62
+ margin: 0 0 0.5rem;
63
+ color: ${colors.text};
64
+ `,
65
+
66
+ subtitle: `
67
+ font-size: 0.875rem;
68
+ color: ${colors.textSecondary};
69
+ margin: 0;
70
+ `,
71
+
72
+ // Form
73
+ form: `
74
+ display: flex;
75
+ flex-direction: column;
76
+ gap: 1rem;
77
+ `,
78
+
79
+ inputGroup: `
80
+ display: flex;
81
+ flex-direction: column;
82
+ gap: 0.375rem;
83
+ `,
84
+
85
+ label: `
86
+ font-size: 0.875rem;
87
+ font-weight: 500;
88
+ color: ${colors.textSecondary};
89
+ `,
90
+
91
+ input: `
92
+ background: ${colors.surface};
93
+ border: 1px solid ${colors.border};
94
+ border-radius: 0.5rem;
95
+ padding: 0.75rem 1rem;
96
+ font-size: 1rem;
97
+ color: ${colors.text};
98
+ outline: none;
99
+ transition: border-color 0.2s, box-shadow 0.2s;
100
+ `,
101
+
102
+ inputFocus: `
103
+ border-color: ${colors.borderFocus};
104
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
105
+ `,
106
+
107
+ inputError: `
108
+ border-color: ${colors.error};
109
+ `,
110
+
111
+ // Buttons
112
+ button: `
113
+ background: ${colors.primary};
114
+ color: white;
115
+ border: none;
116
+ border-radius: 0.5rem;
117
+ padding: 0.875rem 1.5rem;
118
+ font-size: 1rem;
119
+ font-weight: 600;
120
+ cursor: pointer;
121
+ transition: background 0.2s, transform 0.1s;
122
+ display: flex;
123
+ align-items: center;
124
+ justify-content: center;
125
+ gap: 0.5rem;
126
+ `,
127
+
128
+ buttonHover: `
129
+ background: ${colors.primaryHover};
130
+ `,
131
+
132
+ buttonDisabled: `
133
+ opacity: 0.6;
134
+ cursor: not-allowed;
135
+ `,
136
+
137
+ buttonSecondary: `
138
+ background: transparent;
139
+ color: ${colors.textSecondary};
140
+ border: 1px solid ${colors.border};
141
+ `,
142
+
143
+ buttonSecondaryHover: `
144
+ background: ${colors.surface};
145
+ color: ${colors.text};
146
+ `,
147
+
148
+ // Links
149
+ link: `
150
+ color: ${colors.primary};
151
+ text-decoration: none;
152
+ font-size: 0.875rem;
153
+ cursor: pointer;
154
+ transition: color 0.2s;
155
+ `,
156
+
157
+ linkHover: `
158
+ color: ${colors.primaryLight};
159
+ text-decoration: underline;
160
+ `,
161
+
162
+ // Messages
163
+ error: `
164
+ background: rgba(239, 68, 68, 0.1);
165
+ border: 1px solid ${colors.error};
166
+ border-radius: 0.5rem;
167
+ padding: 0.75rem 1rem;
168
+ color: ${colors.errorLight};
169
+ font-size: 0.875rem;
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 0.5rem;
173
+ `,
174
+
175
+ success: `
176
+ background: rgba(34, 197, 94, 0.1);
177
+ border: 1px solid ${colors.success};
178
+ border-radius: 0.5rem;
179
+ padding: 0.75rem 1rem;
180
+ color: ${colors.successLight};
181
+ font-size: 0.875rem;
182
+ display: flex;
183
+ align-items: center;
184
+ gap: 0.5rem;
185
+ `,
186
+
187
+ // Divider
188
+ divider: `
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 1rem;
192
+ color: ${colors.textMuted};
193
+ font-size: 0.75rem;
194
+ margin: 0.5rem 0;
195
+ `,
196
+
197
+ dividerLine: `
198
+ flex: 1;
199
+ height: 1px;
200
+ background: ${colors.border};
201
+ `,
202
+
203
+ // Footer
204
+ footer: `
205
+ text-align: center;
206
+ margin-top: 1.5rem;
207
+ font-size: 0.875rem;
208
+ color: ${colors.textSecondary};
209
+ `,
210
+
211
+ // Loading spinner
212
+ spinner: `
213
+ width: 20px;
214
+ height: 20px;
215
+ border: 2px solid transparent;
216
+ border-top-color: currentColor;
217
+ border-radius: 50%;
218
+ animation: spin 0.8s linear infinite;
219
+ `,
220
+
221
+ // Branding
222
+ brandingContainer: `
223
+ display: flex;
224
+ align-items: center;
225
+ justify-content: center;
226
+ gap: 0.5rem;
227
+ margin-bottom: 1.5rem;
228
+ `,
229
+
230
+ brandingText: `
231
+ font-size: 0.75rem;
232
+ color: ${colors.textMuted};
233
+ `,
234
+
235
+ brandingLogo: `
236
+ height: 16px;
237
+ width: auto;
238
+ `,
239
+
240
+ // Verification code input
241
+ codeInputContainer: `
242
+ display: flex;
243
+ gap: 0.5rem;
244
+ justify-content: center;
245
+ `,
246
+
247
+ codeInput: `
248
+ width: 3rem;
249
+ height: 3.5rem;
250
+ text-align: center;
251
+ font-size: 1.5rem;
252
+ font-weight: 600;
253
+ background: ${colors.surface};
254
+ border: 1px solid ${colors.border};
255
+ border-radius: 0.5rem;
256
+ color: ${colors.text};
257
+ outline: none;
258
+ transition: border-color 0.2s, box-shadow 0.2s;
259
+ `,
260
+
261
+ // Password strength
262
+ passwordStrength: `
263
+ display: flex;
264
+ gap: 0.25rem;
265
+ margin-top: 0.5rem;
266
+ `,
267
+
268
+ passwordStrengthBar: `
269
+ flex: 1;
270
+ height: 4px;
271
+ border-radius: 2px;
272
+ background: ${colors.border};
273
+ transition: background 0.3s;
274
+ `,
275
+ };
276
+
277
+ // CSS keyframes (to be injected)
278
+ export const keyframes = `
279
+ @keyframes spin {
280
+ to { transform: rotate(360deg); }
281
+ }
282
+ `;
@@ -0,0 +1,284 @@
1
+ /**
2
+ * @pixygon/auth - Custom Hooks
3
+ * Convenient hooks for accessing auth state and actions
4
+ */
5
+
6
+ import { useCallback, useMemo, useEffect } from 'react';
7
+ import { useAuthContext } from '../providers/AuthProvider';
8
+ import type { User, UserRole, AuthStatus, AuthError } from '../types';
9
+
10
+ // ============================================================================
11
+ // useAuth - Main auth hook
12
+ // ============================================================================
13
+
14
+ export interface UseAuthReturn {
15
+ // State
16
+ user: User | null;
17
+ status: AuthStatus;
18
+ isLoading: boolean;
19
+ isAuthenticated: boolean;
20
+ isVerified: boolean;
21
+ error: AuthError | null;
22
+
23
+ // Actions
24
+ login: ReturnType<typeof useAuthContext>['login'];
25
+ register: ReturnType<typeof useAuthContext>['register'];
26
+ logout: ReturnType<typeof useAuthContext>['logout'];
27
+ verify: ReturnType<typeof useAuthContext>['verify'];
28
+ resendVerification: ReturnType<typeof useAuthContext>['resendVerification'];
29
+ forgotPassword: ReturnType<typeof useAuthContext>['forgotPassword'];
30
+ recoverPassword: ReturnType<typeof useAuthContext>['recoverPassword'];
31
+
32
+ // Utilities
33
+ hasRole: (role: UserRole | UserRole[]) => boolean;
34
+ }
35
+
36
+ /**
37
+ * Main auth hook providing all auth state and actions
38
+ */
39
+ export function useAuth(): UseAuthReturn {
40
+ const context = useAuthContext();
41
+
42
+ return useMemo(
43
+ () => ({
44
+ // State
45
+ user: context.user,
46
+ status: context.status,
47
+ isLoading: context.isLoading,
48
+ isAuthenticated: context.isAuthenticated,
49
+ isVerified: context.isVerified,
50
+ error: context.error,
51
+
52
+ // Actions
53
+ login: context.login,
54
+ register: context.register,
55
+ logout: context.logout,
56
+ verify: context.verify,
57
+ resendVerification: context.resendVerification,
58
+ forgotPassword: context.forgotPassword,
59
+ recoverPassword: context.recoverPassword,
60
+
61
+ // Utilities
62
+ hasRole: context.hasRole,
63
+ }),
64
+ [context]
65
+ );
66
+ }
67
+
68
+ // ============================================================================
69
+ // useUser - User data hook
70
+ // ============================================================================
71
+
72
+ export interface UseUserReturn {
73
+ user: User | null;
74
+ isAuthenticated: boolean;
75
+ isVerified: boolean;
76
+ role: UserRole | null;
77
+ hasRole: (role: UserRole | UserRole[]) => boolean;
78
+
79
+ // User properties shortcuts
80
+ userId: string | null;
81
+ userName: string | null;
82
+ email: string | null;
83
+ profilePicture: string | null;
84
+ subscriptionTier: User['subscriptionTier'] | null;
85
+
86
+ // Token info
87
+ dailyTokens: number;
88
+ purchasedTokens: number;
89
+ bonusTokens: number;
90
+ subscriptionTokens: number;
91
+ totalTokens: number;
92
+ }
93
+
94
+ /**
95
+ * Hook for accessing user data with convenient shortcuts
96
+ */
97
+ export function useUser(): UseUserReturn {
98
+ const { user, isAuthenticated, isVerified, hasRole } = useAuthContext();
99
+
100
+ return useMemo(() => {
101
+ const dailyTokens = user?.dailyTokens ?? 0;
102
+ const purchasedTokens = user?.purchasedTokens ?? 0;
103
+ const bonusTokens = user?.bonusTokens ?? 0;
104
+ const subscriptionTokens = user?.subscriptionTokens ?? 0;
105
+
106
+ return {
107
+ user,
108
+ isAuthenticated,
109
+ isVerified,
110
+ role: user?.role ?? null,
111
+ hasRole,
112
+
113
+ // Shortcuts
114
+ userId: user?._id ?? null,
115
+ userName: user?.userName ?? null,
116
+ email: user?.email ?? null,
117
+ profilePicture: user?.profilePicture ?? null,
118
+ subscriptionTier: user?.subscriptionTier ?? null,
119
+
120
+ // Tokens
121
+ dailyTokens,
122
+ purchasedTokens,
123
+ bonusTokens,
124
+ subscriptionTokens,
125
+ totalTokens: dailyTokens + purchasedTokens + bonusTokens + subscriptionTokens,
126
+ };
127
+ }, [user, isAuthenticated, isVerified, hasRole]);
128
+ }
129
+
130
+ // ============================================================================
131
+ // useToken - Token management hook
132
+ // ============================================================================
133
+
134
+ export interface UseTokenReturn {
135
+ accessToken: string | null;
136
+ refreshToken: string | null;
137
+ getAccessToken: () => string | null;
138
+ refreshTokens: () => Promise<void>;
139
+ isAuthenticated: boolean;
140
+ }
141
+
142
+ /**
143
+ * Hook for accessing and managing auth tokens
144
+ */
145
+ export function useToken(): UseTokenReturn {
146
+ const { accessToken, refreshToken, getAccessToken, refreshTokens, isAuthenticated } =
147
+ useAuthContext();
148
+
149
+ return useMemo(
150
+ () => ({
151
+ accessToken,
152
+ refreshToken,
153
+ getAccessToken,
154
+ refreshTokens,
155
+ isAuthenticated,
156
+ }),
157
+ [accessToken, refreshToken, getAccessToken, refreshTokens, isAuthenticated]
158
+ );
159
+ }
160
+
161
+ // ============================================================================
162
+ // useAuthStatus - Status-specific hook
163
+ // ============================================================================
164
+
165
+ export interface UseAuthStatusReturn {
166
+ status: AuthStatus;
167
+ isIdle: boolean;
168
+ isLoading: boolean;
169
+ isAuthenticated: boolean;
170
+ isUnauthenticated: boolean;
171
+ isVerifying: boolean;
172
+ }
173
+
174
+ /**
175
+ * Hook for auth status checks
176
+ */
177
+ export function useAuthStatus(): UseAuthStatusReturn {
178
+ const { status, isLoading } = useAuthContext();
179
+
180
+ return useMemo(
181
+ () => ({
182
+ status,
183
+ isIdle: status === 'idle',
184
+ isLoading,
185
+ isAuthenticated: status === 'authenticated',
186
+ isUnauthenticated: status === 'unauthenticated',
187
+ isVerifying: status === 'verifying',
188
+ }),
189
+ [status, isLoading]
190
+ );
191
+ }
192
+
193
+ // ============================================================================
194
+ // useRequireAuth - Protected route hook
195
+ // ============================================================================
196
+
197
+ export interface UseRequireAuthOptions {
198
+ /** Redirect path when not authenticated */
199
+ redirectTo?: string;
200
+ /** Required roles (any of) */
201
+ roles?: UserRole[];
202
+ /** Callback when auth check fails */
203
+ onUnauthorized?: () => void;
204
+ }
205
+
206
+ export interface UseRequireAuthReturn {
207
+ isAuthorized: boolean;
208
+ isLoading: boolean;
209
+ user: User | null;
210
+ }
211
+
212
+ /**
213
+ * Hook for protected routes/components
214
+ * Returns authorization state and user
215
+ */
216
+ export function useRequireAuth(options: UseRequireAuthOptions = {}): UseRequireAuthReturn {
217
+ const { roles, onUnauthorized } = options;
218
+ const { user, isAuthenticated, isLoading, hasRole } = useAuthContext();
219
+
220
+ const isAuthorized = useMemo(() => {
221
+ if (!isAuthenticated) return false;
222
+ if (roles && roles.length > 0 && !hasRole(roles)) return false;
223
+ return true;
224
+ }, [isAuthenticated, roles, hasRole]);
225
+
226
+ // Call onUnauthorized when auth check is complete and user is not authorized
227
+ useEffect(() => {
228
+ if (!isLoading && !isAuthorized && onUnauthorized) {
229
+ onUnauthorized();
230
+ }
231
+ }, [isLoading, isAuthorized, onUnauthorized]);
232
+
233
+ return useMemo(
234
+ () => ({
235
+ isAuthorized,
236
+ isLoading,
237
+ user,
238
+ }),
239
+ [isAuthorized, isLoading, user]
240
+ );
241
+ }
242
+
243
+ // ============================================================================
244
+ // useProfileSync - Profile synchronization hook
245
+ // ============================================================================
246
+
247
+ export { useProfileSync } from './useProfileSync';
248
+ export type { UserProfile, UseProfileSyncReturn } from './useProfileSync';
249
+
250
+ // ============================================================================
251
+ // useAuthError - Error handling hook
252
+ // ============================================================================
253
+
254
+ export interface UseAuthErrorReturn {
255
+ error: AuthError | null;
256
+ hasError: boolean;
257
+ errorMessage: string | null;
258
+ errorCode: AuthError['code'] | null;
259
+ clearError: () => void;
260
+ }
261
+
262
+ /**
263
+ * Hook for auth error handling
264
+ */
265
+ export function useAuthError(): UseAuthErrorReturn {
266
+ const context = useAuthContext();
267
+
268
+ // Note: clearError would need to be added to the context
269
+ // For now, errors clear on next action
270
+
271
+ return useMemo(
272
+ () => ({
273
+ error: context.error,
274
+ hasError: !!context.error,
275
+ errorMessage: context.error?.message ?? null,
276
+ errorCode: context.error?.code ?? null,
277
+ clearError: () => {
278
+ // Errors are cleared on next action
279
+ // Could be enhanced with explicit clear in context
280
+ },
281
+ }),
282
+ [context.error]
283
+ );
284
+ }