@umituz/react-native-auth 1.11.0 → 2.0.1
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 +0 -0
- package/lib/__tests__/services/AuthCoreService.test.d.ts +4 -0
- package/lib/__tests__/services/AuthCoreService.test.js +198 -0
- package/lib/__tests__/services/AuthPackage.test.d.ts +4 -0
- package/lib/__tests__/services/AuthPackage.test.js +177 -0
- package/lib/__tests__/services/GuestModeService.test.d.ts +4 -0
- package/lib/__tests__/services/GuestModeService.test.js +141 -0
- package/lib/__tests__/utils/AuthValidation.test.d.ts +4 -0
- package/lib/__tests__/utils/AuthValidation.test.js +222 -0
- package/lib/application/ports/IAuthProvider.d.ts +42 -0
- package/lib/application/ports/IAuthProvider.js +5 -0
- package/lib/application/ports/IAuthService.d.ts +48 -0
- package/lib/application/ports/IAuthService.js +5 -0
- package/lib/domain/entities/AuthUser.d.ts +12 -0
- package/lib/domain/entities/AuthUser.js +5 -0
- package/lib/domain/errors/AuthError.d.ts +36 -0
- package/lib/domain/errors/AuthError.js +76 -0
- package/lib/domain/value-objects/AuthConfig.d.ts +16 -0
- package/lib/domain/value-objects/AuthConfig.js +14 -0
- package/lib/index.d.ts +45 -0
- package/lib/index.js +59 -0
- package/lib/infrastructure/adapters/StorageProviderAdapter.d.ts +16 -0
- package/lib/infrastructure/adapters/StorageProviderAdapter.js +72 -0
- package/lib/infrastructure/adapters/UIProviderAdapter.d.ts +18 -0
- package/lib/infrastructure/adapters/UIProviderAdapter.js +28 -0
- package/lib/infrastructure/providers/FirebaseAuthProvider.d.ts +19 -0
- package/lib/infrastructure/providers/FirebaseAuthProvider.js +94 -0
- package/lib/infrastructure/services/AuthCoreService.d.ts +22 -0
- package/lib/infrastructure/services/AuthCoreService.js +102 -0
- package/lib/infrastructure/services/AuthEventService.d.ts +28 -0
- package/lib/infrastructure/services/AuthEventService.js +88 -0
- package/lib/infrastructure/services/AuthPackage.d.ts +62 -0
- package/lib/infrastructure/services/AuthPackage.js +91 -0
- package/lib/infrastructure/services/AuthService.d.ts +42 -0
- package/lib/infrastructure/services/AuthService.js +123 -0
- package/lib/infrastructure/services/GuestModeService.d.ts +23 -0
- package/lib/infrastructure/services/GuestModeService.js +69 -0
- package/lib/infrastructure/storage/GuestModeStorage.d.ts +16 -0
- package/lib/infrastructure/storage/GuestModeStorage.js +73 -0
- package/lib/infrastructure/utils/AuthErrorMapper.d.ts +8 -0
- package/lib/infrastructure/utils/AuthErrorMapper.js +51 -0
- package/lib/infrastructure/utils/AuthEventEmitter.d.ts +12 -0
- package/lib/infrastructure/utils/AuthEventEmitter.js +25 -0
- package/lib/infrastructure/utils/AuthValidation.d.ts +49 -0
- package/lib/infrastructure/utils/AuthValidation.js +133 -0
- package/lib/infrastructure/utils/UserMapper.d.ts +15 -0
- package/lib/infrastructure/utils/UserMapper.js +16 -0
- package/lib/presentation/components/AuthContainer.d.ts +10 -0
- package/lib/presentation/components/AuthContainer.js +27 -0
- package/lib/presentation/components/AuthDivider.d.ts +6 -0
- package/lib/presentation/components/AuthDivider.js +36 -0
- package/lib/presentation/components/AuthErrorDisplay.d.ts +10 -0
- package/lib/presentation/components/AuthErrorDisplay.js +24 -0
- package/lib/presentation/components/AuthFormCard.d.ts +10 -0
- package/lib/presentation/components/AuthFormCard.js +19 -0
- package/lib/presentation/components/AuthGradientBackground.d.ts +6 -0
- package/lib/presentation/components/AuthGradientBackground.js +8 -0
- package/lib/presentation/components/AuthHeader.d.ts +11 -0
- package/lib/presentation/components/AuthHeader.js +38 -0
- package/lib/presentation/components/AuthLegalLinks.d.ts +28 -0
- package/lib/presentation/components/AuthLegalLinks.js +54 -0
- package/lib/presentation/components/AuthLink.d.ts +13 -0
- package/lib/presentation/components/AuthLink.js +27 -0
- package/lib/presentation/components/LoginForm.d.ts +10 -0
- package/lib/presentation/components/LoginForm.js +27 -0
- package/lib/presentation/components/PasswordMatchIndicator.d.ts +9 -0
- package/lib/presentation/components/PasswordMatchIndicator.js +30 -0
- package/lib/presentation/components/PasswordStrengthIndicator.d.ts +11 -0
- package/lib/presentation/components/PasswordStrengthIndicator.js +60 -0
- package/lib/presentation/components/RegisterForm.d.ts +14 -0
- package/lib/presentation/components/RegisterForm.js +30 -0
- package/lib/presentation/hooks/useAuth.d.ts +44 -0
- package/lib/presentation/hooks/useAuth.js +38 -0
- package/lib/presentation/hooks/useAuthActions.d.ts +15 -0
- package/lib/presentation/hooks/useAuthActions.js +162 -0
- package/lib/presentation/hooks/useAuthState.d.ts +19 -0
- package/lib/presentation/hooks/useAuthState.js +79 -0
- package/lib/presentation/hooks/useLoginForm.d.ts +21 -0
- package/lib/presentation/hooks/useLoginForm.js +131 -0
- package/lib/presentation/hooks/useRegisterForm.d.ts +31 -0
- package/lib/presentation/hooks/useRegisterForm.js +136 -0
- package/lib/presentation/navigation/AuthNavigator.d.ts +28 -0
- package/lib/presentation/navigation/AuthNavigator.js +37 -0
- package/lib/presentation/screens/LoginScreen.d.ts +6 -0
- package/lib/presentation/screens/LoginScreen.js +15 -0
- package/lib/presentation/screens/RegisterScreen.d.ts +12 -0
- package/lib/presentation/screens/RegisterScreen.js +15 -0
- package/lib/presentation/utils/getAuthErrorMessage.d.ts +8 -0
- package/lib/presentation/utils/getAuthErrorMessage.js +69 -0
- package/package.json +12 -4
- package/src/__tests__/services/AuthCoreService.test.ts +247 -0
- package/src/__tests__/services/AuthPackage.test.ts +226 -0
- package/src/__tests__/services/GuestModeService.test.ts +194 -0
- package/src/__tests__/utils/AuthValidation.test.ts +270 -0
- package/src/application/ports/IAuthProvider.ts +0 -0
- package/src/application/ports/IAuthService.ts +0 -0
- package/src/domain/entities/AuthUser.ts +0 -0
- package/src/domain/errors/AuthError.ts +0 -0
- package/src/domain/value-objects/AuthConfig.ts +0 -0
- package/src/index.ts +4 -0
- package/src/infrastructure/adapters/StorageProviderAdapter.ts +73 -0
- package/src/infrastructure/adapters/UIProviderAdapter.ts +39 -0
- package/src/infrastructure/providers/FirebaseAuthProvider.ts +10 -2
- package/src/infrastructure/services/AuthCoreService.ts +138 -0
- package/src/infrastructure/services/AuthEventService.ts +115 -0
- package/src/infrastructure/services/AuthPackage.ts +148 -0
- package/src/infrastructure/services/AuthService.ts +62 -128
- package/src/infrastructure/services/GuestModeService.ts +86 -0
- package/src/infrastructure/storage/GuestModeStorage.ts +40 -14
- package/src/infrastructure/utils/AuthErrorMapper.ts +7 -3
- package/src/infrastructure/utils/AuthEventEmitter.ts +0 -0
- package/src/infrastructure/utils/AuthValidation.ts +47 -17
- package/src/infrastructure/utils/UserMapper.ts +0 -0
- package/src/presentation/components/AuthContainer.tsx +0 -0
- package/src/presentation/components/AuthDivider.tsx +0 -0
- package/src/presentation/components/AuthErrorDisplay.tsx +0 -0
- package/src/presentation/components/AuthFormCard.tsx +0 -0
- package/src/presentation/components/AuthGradientBackground.tsx +0 -0
- package/src/presentation/components/AuthHeader.tsx +0 -0
- package/src/presentation/components/AuthLegalLinks.tsx +0 -0
- package/src/presentation/components/AuthLink.tsx +0 -0
- package/src/presentation/components/LoginForm.tsx +0 -0
- package/src/presentation/components/PasswordMatchIndicator.tsx +50 -0
- package/src/presentation/components/PasswordStrengthIndicator.tsx +118 -0
- package/src/presentation/components/RegisterForm.tsx +10 -0
- package/src/presentation/hooks/useAuth.ts +0 -0
- package/src/presentation/hooks/useAuthActions.ts +8 -11
- package/src/presentation/hooks/useAuthState.ts +10 -0
- package/src/presentation/hooks/useLoginForm.ts +0 -0
- package/src/presentation/hooks/useRegisterForm.ts +40 -18
- package/src/presentation/navigation/AuthNavigator.tsx +2 -2
- package/src/presentation/screens/LoginScreen.tsx +3 -6
- package/src/presentation/screens/RegisterScreen.tsx +3 -6
- package/src/presentation/utils/getAuthErrorMessage.ts +0 -0
- package/src/types/external.d.ts +68 -0
- package/LICENSE +0 -22
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
3
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
4
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
5
|
+
export const AuthDivider = () => {
|
|
6
|
+
const tokens = useAppDesignTokens();
|
|
7
|
+
const { t } = useLocalization();
|
|
8
|
+
return (_jsxs(View, { style: styles.divider, children: [_jsx(View, { style: [
|
|
9
|
+
styles.dividerLine,
|
|
10
|
+
{ backgroundColor: tokens.colors.borderLight || "#E5E5E5" },
|
|
11
|
+
] }), _jsx(Text, { style: [
|
|
12
|
+
styles.dividerText,
|
|
13
|
+
{ color: tokens.colors.textSecondary || "#999999" },
|
|
14
|
+
], children: t("general.or") }), _jsx(View, { style: [
|
|
15
|
+
styles.dividerLine,
|
|
16
|
+
{ backgroundColor: tokens.colors.borderLight || "#E5E5E5" },
|
|
17
|
+
] })] }));
|
|
18
|
+
};
|
|
19
|
+
const styles = StyleSheet.create({
|
|
20
|
+
divider: {
|
|
21
|
+
flexDirection: "row",
|
|
22
|
+
alignItems: "center",
|
|
23
|
+
marginVertical: 20,
|
|
24
|
+
},
|
|
25
|
+
dividerLine: {
|
|
26
|
+
flex: 1,
|
|
27
|
+
height: 1,
|
|
28
|
+
},
|
|
29
|
+
dividerText: {
|
|
30
|
+
marginHorizontal: 16,
|
|
31
|
+
fontSize: 13,
|
|
32
|
+
fontWeight: "500",
|
|
33
|
+
textTransform: "uppercase",
|
|
34
|
+
letterSpacing: 0.5,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
3
|
+
export const AuthErrorDisplay = ({ error, }) => {
|
|
4
|
+
if (!error) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
return (_jsx(View, { style: styles.errorContainer, children: _jsx(Text, { style: styles.errorText, children: error }) }));
|
|
8
|
+
};
|
|
9
|
+
const styles = StyleSheet.create({
|
|
10
|
+
errorContainer: {
|
|
11
|
+
marginBottom: 16,
|
|
12
|
+
padding: 14,
|
|
13
|
+
borderRadius: 12,
|
|
14
|
+
backgroundColor: "rgba(255, 59, 48, 0.1)",
|
|
15
|
+
borderWidth: 1,
|
|
16
|
+
borderColor: "rgba(255, 59, 48, 0.2)",
|
|
17
|
+
},
|
|
18
|
+
errorText: {
|
|
19
|
+
color: "#FF3B30",
|
|
20
|
+
fontSize: 14,
|
|
21
|
+
textAlign: "center",
|
|
22
|
+
fontWeight: "500",
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { View, StyleSheet } from "react-native";
|
|
3
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
4
|
+
export const AuthFormCard = ({ children }) => {
|
|
5
|
+
const tokens = useAppDesignTokens();
|
|
6
|
+
return (_jsx(View, { style: [
|
|
7
|
+
styles.formCard,
|
|
8
|
+
{ backgroundColor: tokens.colors.surface || "#FFFFFF" },
|
|
9
|
+
], children: _jsx(View, { style: styles.form, children: children }) }));
|
|
10
|
+
};
|
|
11
|
+
const styles = StyleSheet.create({
|
|
12
|
+
formCard: {
|
|
13
|
+
borderRadius: 24,
|
|
14
|
+
padding: 24,
|
|
15
|
+
},
|
|
16
|
+
form: {
|
|
17
|
+
width: "100%",
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { StyleSheet } from "react-native";
|
|
3
|
+
import { LinearGradient } from "expo-linear-gradient";
|
|
4
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
5
|
+
export const AuthGradientBackground = () => {
|
|
6
|
+
const tokens = useAppDesignTokens();
|
|
7
|
+
return (_jsx(LinearGradient, { colors: [tokens.colors.primary, tokens.colors.secondary], start: { x: 0, y: 0 }, end: { x: 1, y: 1 }, style: StyleSheet.absoluteFill }));
|
|
8
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
3
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
4
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
5
|
+
export const AuthHeader = ({ title, subtitle }) => {
|
|
6
|
+
const tokens = useAppDesignTokens();
|
|
7
|
+
const { t } = useLocalization();
|
|
8
|
+
return (_jsxs(View, { style: styles.header, children: [_jsx(Text, { style: [
|
|
9
|
+
styles.title,
|
|
10
|
+
{ color: tokens.colors.onPrimary || "#FFFFFF" },
|
|
11
|
+
], children: title }), (subtitle || t("auth.subtitle")) && (_jsx(Text, { style: [
|
|
12
|
+
styles.subtitle,
|
|
13
|
+
{
|
|
14
|
+
color: tokens.colors.textInverse || "rgba(255, 255, 255, 0.9)",
|
|
15
|
+
},
|
|
16
|
+
], children: subtitle || t("auth.subtitle") }))] }));
|
|
17
|
+
};
|
|
18
|
+
const styles = StyleSheet.create({
|
|
19
|
+
header: {
|
|
20
|
+
marginBottom: 28,
|
|
21
|
+
alignItems: "center",
|
|
22
|
+
paddingHorizontal: 20,
|
|
23
|
+
},
|
|
24
|
+
title: {
|
|
25
|
+
fontSize: 36,
|
|
26
|
+
fontWeight: "700",
|
|
27
|
+
marginBottom: 8,
|
|
28
|
+
textAlign: "center",
|
|
29
|
+
letterSpacing: -0.5,
|
|
30
|
+
},
|
|
31
|
+
subtitle: {
|
|
32
|
+
fontSize: 16,
|
|
33
|
+
textAlign: "center",
|
|
34
|
+
lineHeight: 22,
|
|
35
|
+
fontWeight: "400",
|
|
36
|
+
marginTop: 4,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Legal Links Component
|
|
3
|
+
* Display Terms of Service and Privacy Policy links
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
export interface AuthLegalLinksProps {
|
|
7
|
+
/**
|
|
8
|
+
* Terms of Service URL
|
|
9
|
+
*/
|
|
10
|
+
termsUrl?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Privacy Policy URL
|
|
13
|
+
*/
|
|
14
|
+
privacyUrl?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Callback when Terms of Service is pressed
|
|
17
|
+
*/
|
|
18
|
+
onTermsPress?: () => void;
|
|
19
|
+
/**
|
|
20
|
+
* Callback when Privacy Policy is pressed
|
|
21
|
+
*/
|
|
22
|
+
onPrivacyPress?: () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Custom text before links
|
|
25
|
+
*/
|
|
26
|
+
prefixText?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const AuthLegalLinks: React.FC<AuthLegalLinksProps>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, StyleSheet, Linking } from "react-native";
|
|
3
|
+
import { AtomicButton, AtomicText } from "@umituz/react-native-design-system-atoms";
|
|
4
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
5
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
6
|
+
export const AuthLegalLinks = ({ termsUrl, privacyUrl, onTermsPress, onPrivacyPress, prefixText, }) => {
|
|
7
|
+
const tokens = useAppDesignTokens();
|
|
8
|
+
const { t } = useLocalization();
|
|
9
|
+
const handleTermsPress = async () => {
|
|
10
|
+
if (onTermsPress) {
|
|
11
|
+
onTermsPress();
|
|
12
|
+
}
|
|
13
|
+
else if (termsUrl) {
|
|
14
|
+
await Linking.openURL(termsUrl);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const handlePrivacyPress = async () => {
|
|
18
|
+
if (onPrivacyPress) {
|
|
19
|
+
onPrivacyPress();
|
|
20
|
+
}
|
|
21
|
+
else if (privacyUrl) {
|
|
22
|
+
await Linking.openURL(privacyUrl);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const hasTerms = termsUrl || onTermsPress;
|
|
26
|
+
const hasPrivacy = privacyUrl || onPrivacyPress;
|
|
27
|
+
if (!hasTerms && !hasPrivacy) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return (_jsxs(View, { style: styles.container, children: [prefixText && (_jsx(AtomicText, { type: "bodySmall", color: "secondary", style: styles.prefixText, children: prefixText })), _jsxs(View, { style: styles.linksContainer, children: [hasTerms && (_jsx(AtomicButton, { variant: "text", onPress: handleTermsPress, style: styles.linkButton, children: _jsx(AtomicText, { type: "bodySmall", color: "primary", children: t("auth.termsOfService") || "Terms of Service" }) })), hasTerms && hasPrivacy && (_jsx(AtomicText, { type: "bodySmall", color: "secondary", style: styles.separator, children: " • " })), hasPrivacy && (_jsx(AtomicButton, { variant: "text", onPress: handlePrivacyPress, style: styles.linkButton, children: _jsx(AtomicText, { type: "bodySmall", color: "primary", children: t("auth.privacyPolicy") || "Privacy Policy" }) }))] })] }));
|
|
31
|
+
};
|
|
32
|
+
const styles = StyleSheet.create({
|
|
33
|
+
container: {
|
|
34
|
+
marginTop: 16,
|
|
35
|
+
alignItems: "center",
|
|
36
|
+
},
|
|
37
|
+
prefixText: {
|
|
38
|
+
marginBottom: 8,
|
|
39
|
+
textAlign: "center",
|
|
40
|
+
},
|
|
41
|
+
linksContainer: {
|
|
42
|
+
flexDirection: "row",
|
|
43
|
+
alignItems: "center",
|
|
44
|
+
justifyContent: "center",
|
|
45
|
+
flexWrap: "wrap",
|
|
46
|
+
},
|
|
47
|
+
linkButton: {
|
|
48
|
+
paddingHorizontal: 4,
|
|
49
|
+
paddingVertical: 4,
|
|
50
|
+
},
|
|
51
|
+
separator: {
|
|
52
|
+
marginHorizontal: 4,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Link Component
|
|
3
|
+
* Link text with button for navigation between auth screens
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
interface AuthLinkProps {
|
|
7
|
+
text: string;
|
|
8
|
+
linkText: string;
|
|
9
|
+
onPress: () => void;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const AuthLink: React.FC<AuthLinkProps>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
3
|
+
import { AtomicButton } from "@umituz/react-native-design-system-atoms";
|
|
4
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
5
|
+
export const AuthLink = ({ text, linkText, onPress, disabled = false, }) => {
|
|
6
|
+
const tokens = useAppDesignTokens();
|
|
7
|
+
return (_jsxs(View, { style: styles.container, children: [_jsxs(Text, { style: [
|
|
8
|
+
styles.text,
|
|
9
|
+
{ color: tokens.colors.textSecondary || "#666666" },
|
|
10
|
+
], children: [text, " "] }), _jsx(AtomicButton, { variant: "text", onPress: onPress, disabled: disabled, style: styles.button, children: linkText })] }));
|
|
11
|
+
};
|
|
12
|
+
const styles = StyleSheet.create({
|
|
13
|
+
container: {
|
|
14
|
+
flexDirection: "row",
|
|
15
|
+
justifyContent: "center",
|
|
16
|
+
alignItems: "center",
|
|
17
|
+
marginTop: 8,
|
|
18
|
+
paddingTop: 8,
|
|
19
|
+
},
|
|
20
|
+
text: {
|
|
21
|
+
fontSize: 15,
|
|
22
|
+
fontWeight: "400",
|
|
23
|
+
},
|
|
24
|
+
button: {
|
|
25
|
+
paddingHorizontal: 4,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, StyleSheet } from "react-native";
|
|
3
|
+
import { AtomicInput, AtomicButton } from "@umituz/react-native-design-system-atoms";
|
|
4
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
5
|
+
import { useLoginForm } from "../hooks/useLoginForm";
|
|
6
|
+
import { AuthErrorDisplay } from "./AuthErrorDisplay";
|
|
7
|
+
import { AuthDivider } from "./AuthDivider";
|
|
8
|
+
import { AuthLink } from "./AuthLink";
|
|
9
|
+
export const LoginForm = ({ onNavigateToRegister, }) => {
|
|
10
|
+
const { t } = useLocalization();
|
|
11
|
+
const { email, password, emailError, passwordError, loading, handleEmailChange, handlePasswordChange, handleSignIn, handleContinueAsGuest, displayError, } = useLoginForm();
|
|
12
|
+
return (_jsxs(_Fragment, { children: [_jsx(View, { style: styles.inputContainer, children: _jsx(AtomicInput, { label: t("auth.email"), value: email, onChangeText: handleEmailChange, placeholder: t("auth.emailPlaceholder"), keyboardType: "email-address", autoCapitalize: "none", disabled: loading, state: emailError ? "error" : "default", helperText: emailError || undefined }) }), _jsx(View, { style: styles.inputContainer, children: _jsx(AtomicInput, { label: t("auth.password"), value: password, onChangeText: handlePasswordChange, placeholder: t("auth.passwordPlaceholder"), secureTextEntry: true, autoCapitalize: "none", disabled: loading, state: passwordError ? "error" : "default", helperText: passwordError || undefined }) }), _jsx(AuthErrorDisplay, { error: displayError }), _jsx(View, { style: styles.buttonContainer, children: _jsx(AtomicButton, { variant: "primary", onPress: handleSignIn, disabled: loading || !email.trim() || !password.trim(), fullWidth: true, style: styles.signInButton, children: t("auth.signIn") }) }), _jsx(AuthDivider, {}), _jsx(View, { style: styles.buttonContainer, children: _jsx(AtomicButton, { variant: "outline", onPress: handleContinueAsGuest, disabled: loading, fullWidth: true, style: styles.guestButton, testID: "continue-as-guest-button", children: t("auth.continueAsGuest") }) }), _jsx(AuthLink, { text: t("auth.dontHaveAccount"), linkText: t("auth.createAccount"), onPress: onNavigateToRegister, disabled: loading })] }));
|
|
13
|
+
};
|
|
14
|
+
const styles = StyleSheet.create({
|
|
15
|
+
inputContainer: {
|
|
16
|
+
marginBottom: 20,
|
|
17
|
+
},
|
|
18
|
+
buttonContainer: {
|
|
19
|
+
marginBottom: 16,
|
|
20
|
+
},
|
|
21
|
+
signInButton: {
|
|
22
|
+
minHeight: 52,
|
|
23
|
+
},
|
|
24
|
+
guestButton: {
|
|
25
|
+
minHeight: 52,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Password Match Indicator Component
|
|
3
|
+
* Shows whether passwords match
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
export interface PasswordMatchIndicatorProps {
|
|
7
|
+
isMatch: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const PasswordMatchIndicator: React.FC<PasswordMatchIndicatorProps>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
3
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
4
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
5
|
+
export const PasswordMatchIndicator = ({ isMatch, }) => {
|
|
6
|
+
const tokens = useAppDesignTokens();
|
|
7
|
+
const { t } = useLocalization();
|
|
8
|
+
const color = isMatch ? tokens.colors.success : tokens.colors.error;
|
|
9
|
+
const text = isMatch
|
|
10
|
+
? t("auth.passwordsMatch", { defaultValue: "Passwords match" })
|
|
11
|
+
: t("auth.passwordsDontMatch", { defaultValue: "Passwords don't match" });
|
|
12
|
+
return (_jsxs(View, { style: styles.container, children: [_jsx(View, { style: [styles.dot, { backgroundColor: color }] }), _jsx(Text, { style: [styles.text, { color }], children: text })] }));
|
|
13
|
+
};
|
|
14
|
+
const styles = StyleSheet.create({
|
|
15
|
+
container: {
|
|
16
|
+
flexDirection: "row",
|
|
17
|
+
alignItems: "center",
|
|
18
|
+
gap: 6,
|
|
19
|
+
marginTop: 8,
|
|
20
|
+
},
|
|
21
|
+
dot: {
|
|
22
|
+
width: 6,
|
|
23
|
+
height: 6,
|
|
24
|
+
borderRadius: 3,
|
|
25
|
+
},
|
|
26
|
+
text: {
|
|
27
|
+
fontSize: 12,
|
|
28
|
+
fontWeight: "500",
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Password Strength Indicator Component
|
|
3
|
+
* Shows password requirements with visual feedback
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
import type { PasswordRequirements } from "../../infrastructure/utils/AuthValidation";
|
|
7
|
+
export interface PasswordStrengthIndicatorProps {
|
|
8
|
+
requirements: PasswordRequirements;
|
|
9
|
+
showLabels?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const PasswordStrengthIndicator: React.FC<PasswordStrengthIndicatorProps>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
3
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
4
|
+
const RequirementDot = ({ label, isValid, successColor, pendingColor, }) => {
|
|
5
|
+
const color = isValid ? successColor : pendingColor;
|
|
6
|
+
return (_jsxs(View, { style: styles.requirement, children: [_jsx(View, { style: [styles.dot, { backgroundColor: color }] }), _jsx(Text, { style: [styles.label, { color }], children: label })] }));
|
|
7
|
+
};
|
|
8
|
+
export const PasswordStrengthIndicator = ({ requirements, showLabels = true }) => {
|
|
9
|
+
const tokens = useAppDesignTokens();
|
|
10
|
+
const successColor = tokens.colors.success;
|
|
11
|
+
const pendingColor = tokens.colors.textTertiary;
|
|
12
|
+
const items = [
|
|
13
|
+
{ key: "minLength", label: "8+", isValid: requirements.hasMinLength },
|
|
14
|
+
{ key: "uppercase", label: "A-Z", isValid: requirements.hasUppercase },
|
|
15
|
+
{ key: "lowercase", label: "a-z", isValid: requirements.hasLowercase },
|
|
16
|
+
{ key: "number", label: "0-9", isValid: requirements.hasNumber },
|
|
17
|
+
{ key: "special", label: "!@#", isValid: requirements.hasSpecialChar },
|
|
18
|
+
];
|
|
19
|
+
if (!showLabels) {
|
|
20
|
+
return (_jsx(View, { style: styles.dotsOnly, children: items.map((item) => (_jsx(View, { style: [
|
|
21
|
+
styles.dotOnly,
|
|
22
|
+
{
|
|
23
|
+
backgroundColor: item.isValid ? successColor : pendingColor,
|
|
24
|
+
},
|
|
25
|
+
] }, item.key))) }));
|
|
26
|
+
}
|
|
27
|
+
return (_jsx(View, { style: styles.container, children: items.map((item) => (_jsx(RequirementDot, { label: item.label, isValid: item.isValid, successColor: successColor, pendingColor: pendingColor }, item.key))) }));
|
|
28
|
+
};
|
|
29
|
+
const styles = StyleSheet.create({
|
|
30
|
+
container: {
|
|
31
|
+
flexDirection: "row",
|
|
32
|
+
flexWrap: "wrap",
|
|
33
|
+
gap: 12,
|
|
34
|
+
marginTop: 8,
|
|
35
|
+
},
|
|
36
|
+
dotsOnly: {
|
|
37
|
+
flexDirection: "row",
|
|
38
|
+
gap: 6,
|
|
39
|
+
marginTop: 8,
|
|
40
|
+
},
|
|
41
|
+
requirement: {
|
|
42
|
+
flexDirection: "row",
|
|
43
|
+
alignItems: "center",
|
|
44
|
+
gap: 4,
|
|
45
|
+
},
|
|
46
|
+
dot: {
|
|
47
|
+
width: 6,
|
|
48
|
+
height: 6,
|
|
49
|
+
borderRadius: 3,
|
|
50
|
+
},
|
|
51
|
+
dotOnly: {
|
|
52
|
+
width: 8,
|
|
53
|
+
height: 8,
|
|
54
|
+
borderRadius: 4,
|
|
55
|
+
},
|
|
56
|
+
label: {
|
|
57
|
+
fontSize: 11,
|
|
58
|
+
fontWeight: "500",
|
|
59
|
+
},
|
|
60
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register Form Component
|
|
3
|
+
* Single Responsibility: Render register form UI
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
interface RegisterFormProps {
|
|
7
|
+
onNavigateToLogin: () => void;
|
|
8
|
+
termsUrl?: string;
|
|
9
|
+
privacyUrl?: string;
|
|
10
|
+
onTermsPress?: () => void;
|
|
11
|
+
onPrivacyPress?: () => void;
|
|
12
|
+
}
|
|
13
|
+
export declare const RegisterForm: React.FC<RegisterFormProps>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { View, StyleSheet } from "react-native";
|
|
3
|
+
import { AtomicInput, AtomicButton } from "@umituz/react-native-design-system-atoms";
|
|
4
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
5
|
+
import { useRegisterForm } from "../hooks/useRegisterForm";
|
|
6
|
+
import { AuthErrorDisplay } from "./AuthErrorDisplay";
|
|
7
|
+
import { AuthLink } from "./AuthLink";
|
|
8
|
+
import { AuthLegalLinks } from "./AuthLegalLinks";
|
|
9
|
+
import { PasswordStrengthIndicator } from "./PasswordStrengthIndicator";
|
|
10
|
+
import { PasswordMatchIndicator } from "./PasswordMatchIndicator";
|
|
11
|
+
export const RegisterForm = ({ onNavigateToLogin, termsUrl, privacyUrl, onTermsPress, onPrivacyPress, }) => {
|
|
12
|
+
const { t } = useLocalization();
|
|
13
|
+
const { displayName, email, password, confirmPassword, fieldErrors, loading, passwordRequirements, passwordsMatch, handleDisplayNameChange, handleEmailChange, handlePasswordChange, handleConfirmPasswordChange, handleSignUp, displayError, } = useRegisterForm();
|
|
14
|
+
return (_jsxs(_Fragment, { children: [_jsx(View, { style: styles.inputContainer, children: _jsx(AtomicInput, { label: t("auth.displayName") || "Full Name", value: displayName, onChangeText: handleDisplayNameChange, placeholder: t("auth.displayNamePlaceholder") || "Enter your full name", autoCapitalize: "words", disabled: loading, state: fieldErrors.displayName ? "error" : "default", helperText: fieldErrors.displayName || undefined }) }), _jsx(View, { style: styles.inputContainer, children: _jsx(AtomicInput, { label: t("auth.email"), value: email, onChangeText: handleEmailChange, placeholder: t("auth.emailPlaceholder"), keyboardType: "email-address", autoCapitalize: "none", disabled: loading, state: fieldErrors.email ? "error" : "default", helperText: fieldErrors.email || undefined }) }), _jsxs(View, { style: styles.inputContainer, children: [_jsx(AtomicInput, { label: t("auth.password"), value: password, onChangeText: handlePasswordChange, placeholder: t("auth.passwordPlaceholder"), secureTextEntry: true, autoCapitalize: "none", disabled: loading, state: fieldErrors.password ? "error" : "default", helperText: fieldErrors.password || undefined }), password.length > 0 && (_jsx(PasswordStrengthIndicator, { requirements: passwordRequirements }))] }), _jsxs(View, { style: styles.inputContainer, children: [_jsx(AtomicInput, { label: t("auth.confirmPassword") || "Confirm Password", value: confirmPassword, onChangeText: handleConfirmPasswordChange, placeholder: t("auth.confirmPasswordPlaceholder") || "Confirm your password", secureTextEntry: true, autoCapitalize: "none", disabled: loading, state: fieldErrors.confirmPassword ? "error" : "default", helperText: fieldErrors.confirmPassword || undefined }), confirmPassword.length > 0 && (_jsx(PasswordMatchIndicator, { isMatch: passwordsMatch }))] }), _jsx(AuthErrorDisplay, { error: displayError }), _jsx(View, { style: styles.buttonContainer, children: _jsx(AtomicButton, { variant: "primary", onPress: handleSignUp, disabled: loading ||
|
|
15
|
+
!email.trim() ||
|
|
16
|
+
!password.trim() ||
|
|
17
|
+
!confirmPassword.trim(), fullWidth: true, style: styles.signUpButton, children: t("auth.signUp") }) }), _jsx(AuthLink, { text: t("auth.alreadyHaveAccount"), linkText: t("auth.signIn"), onPress: onNavigateToLogin, disabled: loading }), _jsx(AuthLegalLinks, { termsUrl: termsUrl, privacyUrl: privacyUrl, onTermsPress: onTermsPress, onPrivacyPress: onPrivacyPress, prefixText: t("auth.bySigningUp") || "By signing up, you agree to our" })] }));
|
|
18
|
+
};
|
|
19
|
+
const styles = StyleSheet.create({
|
|
20
|
+
inputContainer: {
|
|
21
|
+
marginBottom: 20,
|
|
22
|
+
},
|
|
23
|
+
buttonContainer: {
|
|
24
|
+
marginBottom: 16,
|
|
25
|
+
marginTop: 8,
|
|
26
|
+
},
|
|
27
|
+
signUpButton: {
|
|
28
|
+
minHeight: 52,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAuth Hook
|
|
3
|
+
* React hook for authentication state management
|
|
4
|
+
*
|
|
5
|
+
* Uses provider-agnostic AuthUser type.
|
|
6
|
+
* Adds app-specific state (guest mode, error handling) on top of provider auth.
|
|
7
|
+
*/
|
|
8
|
+
import type { AuthUser } from "../../domain/entities/AuthUser";
|
|
9
|
+
export interface UseAuthResult {
|
|
10
|
+
/** Current authenticated user */
|
|
11
|
+
user: AuthUser | null;
|
|
12
|
+
/** Whether auth state is loading */
|
|
13
|
+
loading: boolean;
|
|
14
|
+
/** Whether user is in guest mode */
|
|
15
|
+
isGuest: boolean;
|
|
16
|
+
/** Whether user is authenticated */
|
|
17
|
+
isAuthenticated: boolean;
|
|
18
|
+
/** Current error message */
|
|
19
|
+
error: string | null;
|
|
20
|
+
/** Sign up function */
|
|
21
|
+
signUp: (email: string, password: string, displayName?: string) => Promise<void>;
|
|
22
|
+
/** Sign in function */
|
|
23
|
+
signIn: (email: string, password: string) => Promise<void>;
|
|
24
|
+
/** Sign out function */
|
|
25
|
+
signOut: () => Promise<void>;
|
|
26
|
+
/** Continue as guest function */
|
|
27
|
+
continueAsGuest: () => Promise<void>;
|
|
28
|
+
/** Set error manually (for form validation, etc.) */
|
|
29
|
+
setError: (error: string | null) => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Hook for authentication state management
|
|
33
|
+
*
|
|
34
|
+
* Uses Firebase Auth's built-in state management and adds app-specific features:
|
|
35
|
+
* - Guest mode support
|
|
36
|
+
* - Error handling
|
|
37
|
+
* - Loading states
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const { user, isAuthenticated, signIn, signUp, signOut } = useAuth();
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function useAuth(): UseAuthResult;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAuth Hook
|
|
3
|
+
* React hook for authentication state management
|
|
4
|
+
*
|
|
5
|
+
* Uses provider-agnostic AuthUser type.
|
|
6
|
+
* Adds app-specific state (guest mode, error handling) on top of provider auth.
|
|
7
|
+
*/
|
|
8
|
+
import { useAuthState } from "./useAuthState";
|
|
9
|
+
import { useAuthActions } from "./useAuthActions";
|
|
10
|
+
/**
|
|
11
|
+
* Hook for authentication state management
|
|
12
|
+
*
|
|
13
|
+
* Uses Firebase Auth's built-in state management and adds app-specific features:
|
|
14
|
+
* - Guest mode support
|
|
15
|
+
* - Error handling
|
|
16
|
+
* - Loading states
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const { user, isAuthenticated, signIn, signUp, signOut } = useAuth();
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function useAuth() {
|
|
24
|
+
const state = useAuthState();
|
|
25
|
+
const actions = useAuthActions(state);
|
|
26
|
+
return {
|
|
27
|
+
user: state.user,
|
|
28
|
+
loading: state.loading,
|
|
29
|
+
isGuest: state.isGuest,
|
|
30
|
+
isAuthenticated: state.isAuthenticated,
|
|
31
|
+
error: state.error,
|
|
32
|
+
signUp: actions.signUp,
|
|
33
|
+
signIn: actions.signIn,
|
|
34
|
+
signOut: actions.signOut,
|
|
35
|
+
continueAsGuest: actions.continueAsGuest,
|
|
36
|
+
setError: state.setError,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAuthActions Hook
|
|
3
|
+
* Single Responsibility: Handle authentication actions
|
|
4
|
+
*/
|
|
5
|
+
import type { UseAuthStateResult } from "./useAuthState";
|
|
6
|
+
export interface UseAuthActionsResult {
|
|
7
|
+
signUp: (email: string, password: string, displayName?: string) => Promise<void>;
|
|
8
|
+
signIn: (email: string, password: string) => Promise<void>;
|
|
9
|
+
signOut: () => Promise<void>;
|
|
10
|
+
continueAsGuest: () => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Hook for authentication actions
|
|
14
|
+
*/
|
|
15
|
+
export declare function useAuthActions(state: UseAuthStateResult): UseAuthActionsResult;
|