@umituz/react-native-auth 3.6.72 → 3.6.74
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 +1 -1
- package/src/domain/value-objects/AuthConfig.ts +9 -56
- package/src/index.ts +15 -111
- package/src/infrastructure/adapters/StorageProviderAdapter.ts +1 -4
- package/src/infrastructure/providers/FirebaseAuthProvider.ts +5 -32
- package/src/infrastructure/repositories/AuthRepository.ts +0 -5
- package/src/infrastructure/services/AnonymousModeService.ts +6 -20
- package/src/infrastructure/services/AuthEventService.ts +2 -4
- package/src/infrastructure/services/initializeAuth.ts +2 -11
- package/src/infrastructure/utils/AuthErrorMapper.ts +0 -4
- package/src/infrastructure/utils/authStateHandler.ts +2 -4
- package/src/infrastructure/utils/listener/anonymousSignInHandler.ts +2 -22
- package/src/infrastructure/utils/listener/listenerLifecycle.util.ts +144 -0
- package/src/infrastructure/utils/listener/listenerState.util.ts +125 -0
- package/src/init/createAuthInitModule.ts +3 -22
- package/src/presentation/components/AccountActions.tsx +11 -43
- package/src/presentation/components/AuthBottomSheet.tsx +1 -17
- package/src/presentation/components/ProfileSection.tsx +78 -108
- package/src/presentation/components/RegisterForm.tsx +6 -28
- package/src/presentation/hooks/useAccountManagement.ts +0 -7
- package/src/presentation/hooks/useAuth.ts +2 -4
- package/src/presentation/hooks/useAuthBottomSheet.ts +23 -79
- package/src/presentation/hooks/useGoogleAuth.ts +0 -3
- package/src/presentation/hooks/useLoginForm.ts +26 -30
- package/src/presentation/hooks/useProfileEdit.ts +56 -64
- package/src/presentation/hooks/useRegisterForm.ts +41 -44
- package/src/presentation/providers/AuthProvider.tsx +2 -7
- package/src/presentation/stores/authModalStore.ts +0 -7
- package/src/presentation/stores/authStore.ts +1 -28
- package/src/presentation/stores/initializeAuthListener.ts +30 -160
- package/src/presentation/utils/accountDeleteHandler.util.ts +0 -51
- package/src/presentation/utils/authTransition.util.ts +72 -0
- package/src/presentation/utils/form/formFieldState.util.ts +82 -0
- package/src/presentation/utils/form/formValidation.util.ts +173 -0
- package/src/presentation/utils/socialAuthHandler.util.ts +106 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Listener Lifecycle Utilities
|
|
3
|
+
* Handles subscription and cleanup logic for auth listener
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Auth, User } from "firebase/auth";
|
|
7
|
+
import type { AuthActions } from "../../../types/auth-store.types";
|
|
8
|
+
import { createAnonymousSignInHandler } from "./anonymousSignInHandler";
|
|
9
|
+
import {
|
|
10
|
+
isListenerInitialized,
|
|
11
|
+
completeInitialization,
|
|
12
|
+
setUnsubscribe,
|
|
13
|
+
incrementRefCount,
|
|
14
|
+
decrementRefCount,
|
|
15
|
+
startAnonymousSignIn,
|
|
16
|
+
completeAnonymousSignIn,
|
|
17
|
+
resetListenerState,
|
|
18
|
+
} from "./listenerState.util";
|
|
19
|
+
import { onIdTokenChanged } from "firebase/auth";
|
|
20
|
+
import { getAuthService } from "../../../infrastructure/services/AuthService";
|
|
21
|
+
|
|
22
|
+
type Store = AuthActions & { isAnonymous: boolean };
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create unsubscribe function that decrements ref count
|
|
26
|
+
*/
|
|
27
|
+
export function createUnsubscribeHandler(): () => void {
|
|
28
|
+
return () => {
|
|
29
|
+
const { shouldCleanup } = decrementRefCount();
|
|
30
|
+
|
|
31
|
+
if (shouldCleanup) {
|
|
32
|
+
resetListenerState();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Return existing unsubscribe if already initialized
|
|
39
|
+
* Returns null if initialization is in progress
|
|
40
|
+
*/
|
|
41
|
+
export function handleExistingInitialization(): (() => void) | null {
|
|
42
|
+
if (!isListenerInitialized()) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
incrementRefCount();
|
|
47
|
+
return createUnsubscribeHandler();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Return no-op if initialization is in progress
|
|
52
|
+
*/
|
|
53
|
+
export function handleInitializationInProgress(): () => void {
|
|
54
|
+
return () => {
|
|
55
|
+
// No-op - handled by initial initialization
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Setup Firebase auth listener
|
|
61
|
+
*/
|
|
62
|
+
export function setupAuthListener(
|
|
63
|
+
auth: Auth,
|
|
64
|
+
store: Store,
|
|
65
|
+
autoAnonymousSignIn: boolean,
|
|
66
|
+
onAuthStateChange?: (user: User | null) => void
|
|
67
|
+
): void {
|
|
68
|
+
const service = getAuthService();
|
|
69
|
+
|
|
70
|
+
if (service) {
|
|
71
|
+
const isAnonymous = service.getIsAnonymousMode();
|
|
72
|
+
if (isAnonymous) {
|
|
73
|
+
store.setIsAnonymous(true);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const unsubscribe = onIdTokenChanged(auth, (user) => {
|
|
78
|
+
handleAuthStateChange(user, store, auth, autoAnonymousSignIn, onAuthStateChange);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
setUnsubscribe(unsubscribe);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Handle auth state change from Firebase
|
|
86
|
+
*/
|
|
87
|
+
function handleAuthStateChange(
|
|
88
|
+
user: User | null,
|
|
89
|
+
store: Store,
|
|
90
|
+
auth: Auth,
|
|
91
|
+
autoAnonymousSignIn: boolean,
|
|
92
|
+
onAuthStateChange?: (user: User | null) => void
|
|
93
|
+
): void {
|
|
94
|
+
if (!user && autoAnonymousSignIn) {
|
|
95
|
+
handleAnonymousMode(store, auth);
|
|
96
|
+
store.setFirebaseUser(null);
|
|
97
|
+
completeInitialization();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
store.setFirebaseUser(user);
|
|
102
|
+
store.setInitialized(true);
|
|
103
|
+
|
|
104
|
+
// Handle conversion from anonymous
|
|
105
|
+
if (user && !user.isAnonymous && store.isAnonymous) {
|
|
106
|
+
store.setIsAnonymous(false);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
onAuthStateChange?.(user);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Handle anonymous mode sign-in
|
|
114
|
+
*/
|
|
115
|
+
function handleAnonymousMode(store: Store, auth: Auth): void {
|
|
116
|
+
if (!startAnonymousSignIn()) {
|
|
117
|
+
return; // Already signing in
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const handleAnonymousSignIn = createAnonymousSignInHandler(auth, store);
|
|
121
|
+
|
|
122
|
+
// Start sign-in without blocking
|
|
123
|
+
void (async () => {
|
|
124
|
+
try {
|
|
125
|
+
await handleAnonymousSignIn();
|
|
126
|
+
} finally {
|
|
127
|
+
completeAnonymousSignIn();
|
|
128
|
+
}
|
|
129
|
+
})();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Handle case where Firebase auth is not available
|
|
134
|
+
*/
|
|
135
|
+
export function handleNoFirebaseAuth(store: Store): () => void {
|
|
136
|
+
completeInitialization();
|
|
137
|
+
store.setLoading(false);
|
|
138
|
+
store.setInitialized(true);
|
|
139
|
+
return () => {};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function completeListenerSetup() {
|
|
143
|
+
completeInitialization();
|
|
144
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Listener State Management
|
|
3
|
+
* Manages the state of Firebase auth listener initialization and lifecycle
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ListenerState {
|
|
7
|
+
initialized: boolean;
|
|
8
|
+
refCount: number;
|
|
9
|
+
initializationInProgress: boolean;
|
|
10
|
+
anonymousSignInInProgress: boolean;
|
|
11
|
+
unsubscribe: (() => void) | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const state: ListenerState = {
|
|
15
|
+
initialized: false,
|
|
16
|
+
refCount: 0,
|
|
17
|
+
initializationInProgress: false,
|
|
18
|
+
anonymousSignInInProgress: false,
|
|
19
|
+
unsubscribe: null,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if listener is initialized
|
|
24
|
+
*/
|
|
25
|
+
export function isListenerInitialized(): boolean {
|
|
26
|
+
return state.initialized;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if initialization is in progress
|
|
31
|
+
*/
|
|
32
|
+
export function isInitializationInProgress(): boolean {
|
|
33
|
+
return state.initializationInProgress;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if anonymous sign-in is in progress
|
|
38
|
+
*/
|
|
39
|
+
export function isAnonymousSignInInProgress(): boolean {
|
|
40
|
+
return state.anonymousSignInInProgress;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get current reference count
|
|
45
|
+
*/
|
|
46
|
+
export function getRefCount(): number {
|
|
47
|
+
return state.refCount;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Mark initialization as started
|
|
52
|
+
*/
|
|
53
|
+
export function startInitialization(): boolean {
|
|
54
|
+
if (state.initializationInProgress) {
|
|
55
|
+
return false; // Already initializing
|
|
56
|
+
}
|
|
57
|
+
state.initializationInProgress = true;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Complete initialization
|
|
63
|
+
*/
|
|
64
|
+
export function completeInitialization(): void {
|
|
65
|
+
state.initializationInProgress = false;
|
|
66
|
+
state.initialized = true;
|
|
67
|
+
state.refCount = 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Set the unsubscribe function
|
|
72
|
+
*/
|
|
73
|
+
export function setUnsubscribe(fn: (() => void) | null): void {
|
|
74
|
+
state.unsubscribe = fn;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Increment reference count when a new subscriber joins
|
|
79
|
+
*/
|
|
80
|
+
export function incrementRefCount(): number {
|
|
81
|
+
state.refCount++;
|
|
82
|
+
return state.refCount;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Decrement reference count when a subscriber leaves
|
|
87
|
+
* Returns true if cleanup should be performed
|
|
88
|
+
*/
|
|
89
|
+
export function decrementRefCount(): { shouldCleanup: boolean; count: number } {
|
|
90
|
+
state.refCount--;
|
|
91
|
+
const shouldCleanup = state.refCount <= 0 && state.unsubscribe !== null;
|
|
92
|
+
return { shouldCleanup, count: state.refCount };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Mark anonymous sign-in as in progress
|
|
97
|
+
*/
|
|
98
|
+
export function startAnonymousSignIn(): boolean {
|
|
99
|
+
if (state.anonymousSignInInProgress) {
|
|
100
|
+
return false; // Already signing in
|
|
101
|
+
}
|
|
102
|
+
state.anonymousSignInInProgress = true;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Mark anonymous sign-in as complete
|
|
108
|
+
*/
|
|
109
|
+
export function completeAnonymousSignIn(): void {
|
|
110
|
+
state.anonymousSignInInProgress = false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Reset all state (for testing)
|
|
115
|
+
*/
|
|
116
|
+
export function resetListenerState(): void {
|
|
117
|
+
if (state.unsubscribe) {
|
|
118
|
+
state.unsubscribe();
|
|
119
|
+
}
|
|
120
|
+
state.initialized = false;
|
|
121
|
+
state.refCount = 0;
|
|
122
|
+
state.initializationInProgress = false;
|
|
123
|
+
state.anonymousSignInInProgress = false;
|
|
124
|
+
state.unsubscribe = null;
|
|
125
|
+
}
|
|
@@ -87,24 +87,12 @@ export function createAuthInitModule(
|
|
|
87
87
|
autoAnonymousSignIn,
|
|
88
88
|
storageProvider,
|
|
89
89
|
onUserConverted: async (anonymousId: string, authenticatedId: string) => {
|
|
90
|
-
if (__DEV__) {
|
|
91
|
-
console.log('[createAuthInitModule] User converted:', {
|
|
92
|
-
anonymousId: anonymousId.slice(0, 8),
|
|
93
|
-
authenticatedId: authenticatedId.slice(0, 8),
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
90
|
// Restore purchases after conversion (if callback provided)
|
|
98
91
|
if (onRestorePurchases) {
|
|
99
92
|
try {
|
|
100
93
|
await onRestorePurchases();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
} catch (error) {
|
|
105
|
-
if (__DEV__) {
|
|
106
|
-
console.error('[createAuthInitModule] Restore failed:', error);
|
|
107
|
-
}
|
|
94
|
+
} catch {
|
|
95
|
+
// Silently fail - purchase restoration errors are handled elsewhere
|
|
108
96
|
}
|
|
109
97
|
}
|
|
110
98
|
|
|
@@ -115,15 +103,8 @@ export function createAuthInitModule(
|
|
|
115
103
|
},
|
|
116
104
|
});
|
|
117
105
|
|
|
118
|
-
if (__DEV__) {
|
|
119
|
-
console.log('[createAuthInitModule] Auth initialized');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
106
|
return true;
|
|
123
|
-
} catch
|
|
124
|
-
if (__DEV__) {
|
|
125
|
-
console.error('[createAuthInitModule] Error:', error);
|
|
126
|
-
}
|
|
107
|
+
} catch {
|
|
127
108
|
return false;
|
|
128
109
|
}
|
|
129
110
|
},
|
|
@@ -47,12 +47,7 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
47
47
|
const handleLogout = () => {
|
|
48
48
|
alert.show(AlertType.WARNING, AlertMode.MODAL, logoutConfirmTitle, logoutConfirmMessage, {
|
|
49
49
|
actions: [
|
|
50
|
-
{
|
|
51
|
-
id: "cancel",
|
|
52
|
-
label: cancelText,
|
|
53
|
-
style: "secondary",
|
|
54
|
-
onPress: () => {},
|
|
55
|
-
},
|
|
50
|
+
{ id: "cancel", label: cancelText, style: "secondary", onPress: () => {} },
|
|
56
51
|
{
|
|
57
52
|
id: "confirm",
|
|
58
53
|
label: logoutText,
|
|
@@ -60,10 +55,8 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
60
55
|
onPress: async () => {
|
|
61
56
|
try {
|
|
62
57
|
await onLogout();
|
|
63
|
-
} catch
|
|
64
|
-
|
|
65
|
-
console.error("[AccountActions] Logout failed:", error);
|
|
66
|
-
}
|
|
58
|
+
} catch {
|
|
59
|
+
// Silently fail - logout error handling is managed elsewhere
|
|
67
60
|
}
|
|
68
61
|
},
|
|
69
62
|
},
|
|
@@ -74,12 +67,7 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
74
67
|
const handleDeleteAccount = () => {
|
|
75
68
|
alert.show(AlertType.ERROR, AlertMode.MODAL, deleteConfirmTitle, deleteConfirmMessage, {
|
|
76
69
|
actions: [
|
|
77
|
-
{
|
|
78
|
-
id: "cancel",
|
|
79
|
-
label: cancelText,
|
|
80
|
-
style: "secondary",
|
|
81
|
-
onPress: () => {},
|
|
82
|
-
},
|
|
70
|
+
{ id: "cancel", label: cancelText, style: "secondary", onPress: () => {} },
|
|
83
71
|
{
|
|
84
72
|
id: "confirm",
|
|
85
73
|
label: deleteAccountText,
|
|
@@ -99,40 +87,22 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
99
87
|
return (
|
|
100
88
|
<View style={styles.container}>
|
|
101
89
|
{showChangePassword && onChangePassword && changePasswordText && (
|
|
102
|
-
<TouchableOpacity
|
|
103
|
-
style={[actionButtonStyle.container, { borderColor: tokens.colors.border }]}
|
|
104
|
-
onPress={onChangePassword}
|
|
105
|
-
activeOpacity={0.7}
|
|
106
|
-
>
|
|
90
|
+
<TouchableOpacity style={[actionButtonStyle.container, { borderColor: tokens.colors.border }]} onPress={onChangePassword} activeOpacity={0.7}>
|
|
107
91
|
<AtomicIcon name="key-outline" size="md" color="textPrimary" />
|
|
108
|
-
<AtomicText style={actionButtonStyle.text} color="textPrimary">
|
|
109
|
-
{changePasswordText}
|
|
110
|
-
</AtomicText>
|
|
92
|
+
<AtomicText style={actionButtonStyle.text} color="textPrimary">{changePasswordText}</AtomicText>
|
|
111
93
|
<AtomicIcon name="chevron-forward" size="sm" color="textSecondary" />
|
|
112
94
|
</TouchableOpacity>
|
|
113
95
|
)}
|
|
114
96
|
|
|
115
|
-
<TouchableOpacity
|
|
116
|
-
style={[actionButtonStyle.container, { borderColor: tokens.colors.border }]}
|
|
117
|
-
onPress={handleLogout}
|
|
118
|
-
activeOpacity={0.7}
|
|
119
|
-
>
|
|
97
|
+
<TouchableOpacity style={[actionButtonStyle.container, { borderColor: tokens.colors.border }]} onPress={handleLogout} activeOpacity={0.7}>
|
|
120
98
|
<AtomicIcon name="log-out-outline" size="md" color="error" />
|
|
121
|
-
<AtomicText style={actionButtonStyle.text} color="error">
|
|
122
|
-
{logoutText}
|
|
123
|
-
</AtomicText>
|
|
99
|
+
<AtomicText style={actionButtonStyle.text} color="error">{logoutText}</AtomicText>
|
|
124
100
|
<AtomicIcon name="chevron-forward" size="sm" color="textSecondary" />
|
|
125
101
|
</TouchableOpacity>
|
|
126
102
|
|
|
127
|
-
<TouchableOpacity
|
|
128
|
-
style={[actionButtonStyle.container, { borderColor: tokens.colors.border }]}
|
|
129
|
-
onPress={handleDeleteAccount}
|
|
130
|
-
activeOpacity={0.7}
|
|
131
|
-
>
|
|
103
|
+
<TouchableOpacity style={[actionButtonStyle.container, { borderColor: tokens.colors.border }]} onPress={handleDeleteAccount} activeOpacity={0.7}>
|
|
132
104
|
<AtomicIcon name="trash-outline" size="md" color="error" />
|
|
133
|
-
<AtomicText style={actionButtonStyle.text} color="error">
|
|
134
|
-
{deleteAccountText}
|
|
135
|
-
</AtomicText>
|
|
105
|
+
<AtomicText style={actionButtonStyle.text} color="error">{deleteAccountText}</AtomicText>
|
|
136
106
|
<AtomicIcon name="chevron-forward" size="sm" color="textSecondary" />
|
|
137
107
|
</TouchableOpacity>
|
|
138
108
|
</View>
|
|
@@ -140,7 +110,5 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
140
110
|
};
|
|
141
111
|
|
|
142
112
|
const styles = StyleSheet.create({
|
|
143
|
-
container: {
|
|
144
|
-
gap: 12,
|
|
145
|
-
},
|
|
113
|
+
container: { gap: 12 },
|
|
146
114
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
import { View, TouchableOpacity, ScrollView } from "react-native";
|
|
3
3
|
import {
|
|
4
4
|
useAppDesignTokens,
|
|
@@ -63,22 +63,6 @@ export const AuthBottomSheet: React.FC<AuthBottomSheetProps> = ({
|
|
|
63
63
|
handleAppleSignIn,
|
|
64
64
|
} = useAuthBottomSheet({ socialConfig, onGoogleSignIn, onAppleSignIn, onAuthSuccess });
|
|
65
65
|
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
if (__DEV__) {
|
|
68
|
-
console.log("[AuthBottomSheet] Rendered with:", {
|
|
69
|
-
mode,
|
|
70
|
-
providersCount: providers.length,
|
|
71
|
-
hasModalRef: !!modalRef.current,
|
|
72
|
-
hasTermsUrl: !!termsUrl,
|
|
73
|
-
hasPrivacyUrl: !!privacyUrl,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}, [mode, providers.length, termsUrl, privacyUrl, modalRef]);
|
|
77
|
-
|
|
78
|
-
if (__DEV__) {
|
|
79
|
-
console.log("[AuthBottomSheet] Rendering...");
|
|
80
|
-
}
|
|
81
|
-
|
|
82
66
|
return (
|
|
83
67
|
<BottomSheetModal
|
|
84
68
|
ref={modalRef}
|
|
@@ -9,128 +9,98 @@ import { useAppDesignTokens, AtomicText, AtomicIcon, AtomicAvatar } from "@umitu
|
|
|
9
9
|
import { ProfileBenefitsList } from "./ProfileBenefitsList";
|
|
10
10
|
|
|
11
11
|
export interface ProfileSectionConfig {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
displayName?: string;
|
|
13
|
+
userId?: string;
|
|
14
|
+
isAnonymous: boolean;
|
|
15
|
+
avatarUrl?: string;
|
|
16
|
+
accountSettingsRoute?: string;
|
|
17
|
+
benefits?: string[];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export interface ProfileSectionProps {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
profile: ProfileSectionConfig;
|
|
22
|
+
onPress?: () => void;
|
|
23
|
+
onSignIn?: () => void;
|
|
24
|
+
signInText?: string;
|
|
25
|
+
anonymousText?: string;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export const ProfileSection: React.FC<ProfileSectionProps> = ({
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
profile,
|
|
30
|
+
onPress,
|
|
31
|
+
onSignIn,
|
|
32
|
+
signInText,
|
|
33
|
+
anonymousText,
|
|
34
34
|
}) => {
|
|
35
|
-
|
|
35
|
+
const tokens = useAppDesignTokens();
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
const handlePress = () => {
|
|
38
|
+
if (profile.isAnonymous && onSignIn) {
|
|
39
|
+
onSignIn();
|
|
40
|
+
} else if (onPress) {
|
|
41
|
+
onPress();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
45
|
+
return (
|
|
46
|
+
<TouchableOpacity
|
|
47
|
+
style={[styles.container, { backgroundColor: tokens.colors.surface }]}
|
|
48
|
+
onPress={handlePress}
|
|
49
|
+
activeOpacity={0.7}
|
|
50
|
+
disabled={!onPress && !onSignIn}
|
|
51
|
+
>
|
|
52
|
+
<View style={styles.content}>
|
|
53
|
+
<View style={styles.avatarContainer}>
|
|
54
|
+
<AtomicAvatar
|
|
55
|
+
source={profile.avatarUrl ? { uri: profile.avatarUrl } : undefined}
|
|
56
|
+
name={profile.displayName || (profile.isAnonymous ? anonymousText : signInText)}
|
|
57
|
+
size="md"
|
|
58
|
+
/>
|
|
59
|
+
</View>
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<AtomicText
|
|
72
|
-
type="bodySmall"
|
|
73
|
-
color="textSecondary"
|
|
74
|
-
numberOfLines={1}
|
|
75
|
-
>
|
|
76
|
-
{profile.userId}
|
|
77
|
-
</AtomicText>
|
|
78
|
-
)}
|
|
79
|
-
</View>
|
|
61
|
+
<View style={styles.info}>
|
|
62
|
+
<AtomicText type="titleMedium" color="textPrimary" numberOfLines={1} style={styles.displayName}>
|
|
63
|
+
{profile.displayName}
|
|
64
|
+
</AtomicText>
|
|
65
|
+
{profile.userId && (
|
|
66
|
+
<AtomicText type="bodySmall" color="textSecondary" numberOfLines={1}>
|
|
67
|
+
{profile.userId}
|
|
68
|
+
</AtomicText>
|
|
69
|
+
)}
|
|
70
|
+
</View>
|
|
80
71
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
72
|
+
{onPress && !profile.isAnonymous && (
|
|
73
|
+
<AtomicIcon name="chevron-forward" size="sm" color="textSecondary" />
|
|
74
|
+
)}
|
|
75
|
+
</View>
|
|
85
76
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
77
|
+
{profile.isAnonymous && onSignIn && (
|
|
78
|
+
<View style={[styles.ctaContainer, { borderTopColor: tokens.colors.border }]}>
|
|
79
|
+
{profile.benefits && profile.benefits.length > 0 && (
|
|
80
|
+
<ProfileBenefitsList benefits={profile.benefits} />
|
|
81
|
+
)}
|
|
91
82
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
83
|
+
<TouchableOpacity
|
|
84
|
+
style={[styles.ctaButton, { backgroundColor: tokens.colors.primary }]}
|
|
85
|
+
onPress={onSignIn}
|
|
86
|
+
activeOpacity={0.8}
|
|
87
|
+
>
|
|
88
|
+
<AtomicText type="labelLarge" style={{ color: tokens.colors.onPrimary }}>
|
|
89
|
+
{signInText}
|
|
90
|
+
</AtomicText>
|
|
91
|
+
</TouchableOpacity>
|
|
92
|
+
</View>
|
|
93
|
+
)}
|
|
94
|
+
</TouchableOpacity>
|
|
95
|
+
);
|
|
105
96
|
};
|
|
106
97
|
|
|
107
98
|
const styles = StyleSheet.create({
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
alignItems: "center",
|
|
116
|
-
},
|
|
117
|
-
avatarContainer: {
|
|
118
|
-
marginRight: 12,
|
|
119
|
-
},
|
|
120
|
-
info: {
|
|
121
|
-
flex: 1,
|
|
122
|
-
},
|
|
123
|
-
displayName: {
|
|
124
|
-
marginBottom: 2,
|
|
125
|
-
},
|
|
126
|
-
ctaContainer: {
|
|
127
|
-
marginTop: 12,
|
|
128
|
-
paddingTop: 12,
|
|
129
|
-
borderTopWidth: 1,
|
|
130
|
-
},
|
|
131
|
-
ctaButton: {
|
|
132
|
-
paddingVertical: 12,
|
|
133
|
-
borderRadius: 8,
|
|
134
|
-
alignItems: "center",
|
|
135
|
-
},
|
|
99
|
+
container: { borderRadius: 12, padding: 16, marginBottom: 16 },
|
|
100
|
+
content: { flexDirection: "row", alignItems: "center" },
|
|
101
|
+
avatarContainer: { marginRight: 12 },
|
|
102
|
+
info: { flex: 1 },
|
|
103
|
+
displayName: { marginBottom: 2 },
|
|
104
|
+
ctaContainer: { marginTop: 12, paddingTop: 12, borderTopWidth: 1 },
|
|
105
|
+
ctaButton: { paddingVertical: 12, borderRadius: 8, alignItems: "center" },
|
|
136
106
|
});
|