@umituz/react-native-auth 2.5.2 → 2.5.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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-auth",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.4",
|
|
4
4
|
"description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design with dependency injection, configurable validation, and comprehensive error handling.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
package/src/index.ts
CHANGED
|
@@ -99,6 +99,9 @@ export type {
|
|
|
99
99
|
export { useAuth } from './presentation/hooks/useAuth';
|
|
100
100
|
export type { UseAuthResult } from './presentation/hooks/useAuth';
|
|
101
101
|
|
|
102
|
+
export { useUserProfile } from './presentation/hooks/useUserProfile';
|
|
103
|
+
export type { UserProfileData, UseUserProfileParams } from './presentation/hooks/useUserProfile';
|
|
104
|
+
|
|
102
105
|
// =============================================================================
|
|
103
106
|
// PRESENTATION LAYER - Screens & Navigation
|
|
104
107
|
// =============================================================================
|
|
@@ -128,6 +131,8 @@ export { PasswordMatchIndicator } from './presentation/components/PasswordMatchI
|
|
|
128
131
|
export type { PasswordMatchIndicatorProps } from './presentation/components/PasswordMatchIndicator';
|
|
129
132
|
export { AuthBottomSheet } from './presentation/components/AuthBottomSheet';
|
|
130
133
|
export type { AuthBottomSheetProps } from './presentation/components/AuthBottomSheet';
|
|
134
|
+
export { ProfileSection } from './presentation/components/ProfileSection';
|
|
135
|
+
export type { ProfileSectionConfig, ProfileSectionProps } from './presentation/components/ProfileSection';
|
|
131
136
|
|
|
132
137
|
// =============================================================================
|
|
133
138
|
// PRESENTATION LAYER - Stores
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile Section Component
|
|
3
|
+
* Generic user profile section for settings screens
|
|
4
|
+
* Shows user info, sign in CTA for anonymous, or account navigation for signed in users
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
|
|
9
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
10
|
+
|
|
11
|
+
export interface ProfileSectionConfig {
|
|
12
|
+
displayName: string;
|
|
13
|
+
userId?: string;
|
|
14
|
+
isAnonymous: boolean;
|
|
15
|
+
avatarUrl?: string;
|
|
16
|
+
accountSettingsRoute?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ProfileSectionProps {
|
|
20
|
+
profile: ProfileSectionConfig;
|
|
21
|
+
onPress?: () => void;
|
|
22
|
+
onSignIn?: () => void;
|
|
23
|
+
signInText?: string;
|
|
24
|
+
anonymousText?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const ProfileSection: React.FC<ProfileSectionProps> = ({
|
|
28
|
+
profile,
|
|
29
|
+
onPress,
|
|
30
|
+
onSignIn,
|
|
31
|
+
signInText = "Sign In",
|
|
32
|
+
anonymousText = "Anonymous User",
|
|
33
|
+
}) => {
|
|
34
|
+
const tokens = useAppDesignTokens();
|
|
35
|
+
|
|
36
|
+
const handlePress = () => {
|
|
37
|
+
if (profile.isAnonymous && onSignIn) {
|
|
38
|
+
onSignIn();
|
|
39
|
+
} else if (onPress) {
|
|
40
|
+
onPress();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const getInitials = (name: string): string => {
|
|
45
|
+
return name
|
|
46
|
+
.split(" ")
|
|
47
|
+
.map((n) => n[0])
|
|
48
|
+
.join("")
|
|
49
|
+
.toUpperCase()
|
|
50
|
+
.slice(0, 2);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<TouchableOpacity
|
|
55
|
+
style={[styles.container, { backgroundColor: tokens.colors.surface }]}
|
|
56
|
+
onPress={handlePress}
|
|
57
|
+
activeOpacity={0.7}
|
|
58
|
+
disabled={!onPress && !onSignIn}
|
|
59
|
+
>
|
|
60
|
+
<View style={styles.content}>
|
|
61
|
+
{/* Avatar */}
|
|
62
|
+
<View
|
|
63
|
+
style={[
|
|
64
|
+
styles.avatar,
|
|
65
|
+
{ backgroundColor: profile.isAnonymous ? tokens.colors.warning : tokens.colors.primary },
|
|
66
|
+
]}
|
|
67
|
+
>
|
|
68
|
+
{profile.avatarUrl ? (
|
|
69
|
+
<Text style={styles.avatarText}>
|
|
70
|
+
{getInitials(profile.displayName)}
|
|
71
|
+
</Text>
|
|
72
|
+
) : (
|
|
73
|
+
<Text
|
|
74
|
+
style={[styles.avatarText, { color: tokens.colors.onPrimary }]}
|
|
75
|
+
>
|
|
76
|
+
{getInitials(profile.displayName)}
|
|
77
|
+
</Text>
|
|
78
|
+
)}
|
|
79
|
+
</View>
|
|
80
|
+
|
|
81
|
+
{/* User Info */}
|
|
82
|
+
<View style={styles.info}>
|
|
83
|
+
<Text
|
|
84
|
+
style={[styles.displayName, { color: tokens.colors.textPrimary }]}
|
|
85
|
+
numberOfLines={1}
|
|
86
|
+
>
|
|
87
|
+
{profile.displayName}
|
|
88
|
+
</Text>
|
|
89
|
+
{profile.userId && (
|
|
90
|
+
<Text
|
|
91
|
+
style={[styles.userId, { color: tokens.colors.textSecondary }]}
|
|
92
|
+
numberOfLines={1}
|
|
93
|
+
>
|
|
94
|
+
{profile.userId}
|
|
95
|
+
</Text>
|
|
96
|
+
)}
|
|
97
|
+
</View>
|
|
98
|
+
|
|
99
|
+
{/* Action Icon */}
|
|
100
|
+
{(onPress || onSignIn) && (
|
|
101
|
+
<Text style={[styles.chevron, { color: tokens.colors.textTertiary }]}>
|
|
102
|
+
›
|
|
103
|
+
</Text>)}
|
|
104
|
+
</View>
|
|
105
|
+
|
|
106
|
+
{/* Sign In CTA for Anonymous Users */}
|
|
107
|
+
{profile.isAnonymous && onSignIn && (
|
|
108
|
+
<View
|
|
109
|
+
style={[
|
|
110
|
+
styles.ctaContainer,
|
|
111
|
+
{ borderTopColor: tokens.colors.border },
|
|
112
|
+
]}
|
|
113
|
+
>
|
|
114
|
+
<TouchableOpacity
|
|
115
|
+
style={[
|
|
116
|
+
styles.ctaButton,
|
|
117
|
+
{ backgroundColor: tokens.colors.primary },
|
|
118
|
+
]}
|
|
119
|
+
onPress={onSignIn}
|
|
120
|
+
activeOpacity={0.8}
|
|
121
|
+
>
|
|
122
|
+
<Text
|
|
123
|
+
style={[
|
|
124
|
+
styles.ctaText,
|
|
125
|
+
{ color: tokens.colors.onPrimary },
|
|
126
|
+
]}
|
|
127
|
+
>
|
|
128
|
+
{signInText}
|
|
129
|
+
</Text>
|
|
130
|
+
</TouchableOpacity>
|
|
131
|
+
</View>
|
|
132
|
+
)}
|
|
133
|
+
</TouchableOpacity>
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const styles = StyleSheet.create({
|
|
138
|
+
container: {
|
|
139
|
+
borderRadius: 12,
|
|
140
|
+
padding: 16,
|
|
141
|
+
marginBottom: 16,
|
|
142
|
+
},
|
|
143
|
+
content: {
|
|
144
|
+
flexDirection: "row",
|
|
145
|
+
alignItems: "center",
|
|
146
|
+
},
|
|
147
|
+
avatar: {
|
|
148
|
+
width: 56,
|
|
149
|
+
height: 56,
|
|
150
|
+
borderRadius: 28,
|
|
151
|
+
justifyContent: "center",
|
|
152
|
+
alignItems: "center",
|
|
153
|
+
marginRight: 12,
|
|
154
|
+
},
|
|
155
|
+
avatarText: {
|
|
156
|
+
fontSize: 20,
|
|
157
|
+
fontWeight: "600",
|
|
158
|
+
},
|
|
159
|
+
info: {
|
|
160
|
+
flex: 1,
|
|
161
|
+
},
|
|
162
|
+
displayName: {
|
|
163
|
+
fontSize: 18,
|
|
164
|
+
fontWeight: "600",
|
|
165
|
+
marginBottom: 2,
|
|
166
|
+
},
|
|
167
|
+
userId: {
|
|
168
|
+
fontSize: 13,
|
|
169
|
+
},
|
|
170
|
+
chevron: {
|
|
171
|
+
fontSize: 24,
|
|
172
|
+
fontWeight: "400",
|
|
173
|
+
},
|
|
174
|
+
ctaContainer: {
|
|
175
|
+
marginTop: 12,
|
|
176
|
+
paddingTop: 12,
|
|
177
|
+
borderTopWidth: 1,
|
|
178
|
+
},
|
|
179
|
+
ctaButton: {
|
|
180
|
+
paddingVertical: 12,
|
|
181
|
+
borderRadius: 8,
|
|
182
|
+
alignItems: "center",
|
|
183
|
+
},
|
|
184
|
+
ctaText: {
|
|
185
|
+
fontSize: 15,
|
|
186
|
+
fontWeight: "600",
|
|
187
|
+
},
|
|
188
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useUserProfile Hook
|
|
3
|
+
* Generic hook for user profile configuration
|
|
4
|
+
* Returns profile data for display in settings or profile screens
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useMemo } from "react";
|
|
8
|
+
import { useAuth } from "./useAuth";
|
|
9
|
+
|
|
10
|
+
export interface UserProfileData {
|
|
11
|
+
displayName: string;
|
|
12
|
+
userId?: string;
|
|
13
|
+
isAnonymous: boolean;
|
|
14
|
+
avatarUrl?: string;
|
|
15
|
+
accountSettingsRoute?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UseUserProfileParams {
|
|
19
|
+
anonymousDisplayName?: string;
|
|
20
|
+
guestDisplayName?: string;
|
|
21
|
+
accountRoute?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const useUserProfile = (
|
|
25
|
+
params?: UseUserProfileParams,
|
|
26
|
+
): UserProfileData | undefined => {
|
|
27
|
+
const { user } = useAuth();
|
|
28
|
+
|
|
29
|
+
const anonymousName = params?.anonymousDisplayName || "Anonymous User";
|
|
30
|
+
const guestName = params?.guestDisplayName || "Guest User";
|
|
31
|
+
const accountRoute = params?.accountRoute || "Account";
|
|
32
|
+
|
|
33
|
+
return useMemo(() => {
|
|
34
|
+
if (!user) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isAnonymous = user.isAnonymous || false;
|
|
39
|
+
|
|
40
|
+
if (isAnonymous) {
|
|
41
|
+
return {
|
|
42
|
+
displayName: anonymousName,
|
|
43
|
+
userId: user.uid,
|
|
44
|
+
isAnonymous: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
accountSettingsRoute: accountRoute,
|
|
50
|
+
displayName: user.displayName || user.email || guestName,
|
|
51
|
+
userId: user.uid,
|
|
52
|
+
isAnonymous: false,
|
|
53
|
+
avatarUrl: user.photoURL || undefined,
|
|
54
|
+
};
|
|
55
|
+
}, [user, anonymousName, guestName, accountRoute]);
|
|
56
|
+
};
|
|
@@ -8,7 +8,12 @@ import { AuthError } from "../../domain/errors/AuthError";
|
|
|
8
8
|
/**
|
|
9
9
|
* Map AuthError code to localization key
|
|
10
10
|
*/
|
|
11
|
-
export function getAuthErrorLocalizationKey(error:
|
|
11
|
+
export function getAuthErrorLocalizationKey(error: unknown): string {
|
|
12
|
+
// Handle non-Error types
|
|
13
|
+
if (!(error instanceof Error)) {
|
|
14
|
+
return "auth.errors.unknownError";
|
|
15
|
+
}
|
|
16
|
+
|
|
12
17
|
const code = error instanceof AuthError ? error.code : undefined;
|
|
13
18
|
|
|
14
19
|
// Map error codes to localization keys
|