UNPKG

@oxyhq/services

Version:

Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀

762 lines (752 loc) • 24.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); var _OxyContext = require("../context/OxyContext"); var _OxyLogo = _interopRequireDefault(require("../components/OxyLogo")); var _bottomSheet = require("../components/bottomSheet"); var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg")); var _sonner = require("../../lib/sonner"); var _jsxRuntime = require("react/jsx-runtime"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } // Add icon import const SignUpScreen = ({ navigate, goBack, theme }) => { // Form data states const [username, setUsername] = (0, _react.useState)(''); const [email, setEmail] = (0, _react.useState)(''); const [password, setPassword] = (0, _react.useState)(''); const [confirmPassword, setConfirmPassword] = (0, _react.useState)(''); const [errorMessage, setErrorMessage] = (0, _react.useState)(''); // Multi-step form states const [currentStep, setCurrentStep] = (0, _react.useState)(0); const fadeAnim = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current; const slideAnim = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current; const heightAnim = (0, _react.useRef)(new _reactNative.Animated.Value(400)).current; // Initial height value const [containerHeight, setContainerHeight] = (0, _react.useState)(400); // Default height const { signUp, isLoading, user, isAuthenticated } = (0, _OxyContext.useOxy)(); const isDarkTheme = theme === 'dark'; const textColor = isDarkTheme ? '#FFFFFF' : '#000000'; const backgroundColor = isDarkTheme ? '#121212' : '#FFFFFF'; const inputBackgroundColor = isDarkTheme ? '#333333' : '#F5F5F5'; const placeholderColor = isDarkTheme ? '#AAAAAA' : '#999999'; const primaryColor = '#d169e5'; const borderColor = isDarkTheme ? '#444444' : '#E0E0E0'; // If user is already authenticated, show user info and account center option // Animation functions const animateTransition = nextStep => { // Fade out _reactNative.Animated.timing(fadeAnim, { toValue: 0, duration: 250, useNativeDriver: true }).start(() => { setCurrentStep(nextStep); // Reset slide position slideAnim.setValue(-100); // Fade in and slide _reactNative.Animated.parallel([_reactNative.Animated.timing(fadeAnim, { toValue: 1, duration: 250, useNativeDriver: true }), _reactNative.Animated.timing(slideAnim, { toValue: 0, duration: 300, useNativeDriver: true })]).start(); }); }; const nextStep = () => { if (currentStep < 3) { animateTransition(currentStep + 1); } }; const prevStep = () => { if (currentStep > 0) { animateTransition(currentStep - 1); } }; if (user && isAuthenticated) { return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_bottomSheet.BottomSheetScrollView, { style: [styles.scrollContainer, { backgroundColor, padding: 20 }], children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, { style: [styles.welcomeTitle, { color: textColor, textAlign: 'center' }], children: ["Welcome, ", user.username, "!"] }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.userInfoContainer, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.userInfoText, { color: textColor }], children: "You are already signed in." }), user.email && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, { style: [styles.userInfoText, { color: isDarkTheme ? '#BBBBBB' : '#666666' }], children: ["Email: ", user.email] })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.actionButtonsContainer, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: [styles.button, { backgroundColor: primaryColor }], onPress: () => navigate('AccountCenter'), children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.buttonText, children: "Go to Account Center" }) }) })] }); } const validateEmail = email => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; const handleSignUp = async () => { // Validate inputs if (!username || !email || !password || !confirmPassword) { _sonner.toast.error('Please fill in all fields'); return; } if (!validateEmail(email)) { _sonner.toast.error('Please enter a valid email address'); return; } if (password !== confirmPassword) { _sonner.toast.error('Passwords do not match'); return; } if (password.length < 8) { _sonner.toast.error('Password must be at least 8 characters long'); return; } try { setErrorMessage(''); await signUp(username, email, password); _sonner.toast.success('Account created successfully! Welcome to Oxy!'); // The authentication state change will be handled through context } catch (error) { _sonner.toast.error(error.message || 'Sign up failed'); } }; // Step components const renderWelcomeStep = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, { style: [styles.stepContainer, { opacity: fadeAnim, transform: [{ translateX: slideAnim }] }], children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.welcomeImageContainer, children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeSvg.default, { width: 220, height: 120, viewBox: "0 0 220 120", children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Path, { d: "M30 100 Q60 20 110 60 Q160 100 190 40", stroke: "#d169e5", strokeWidth: "8", fill: "none" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Circle, { cx: "60", cy: "60", r: "18", fill: "#d169e5", opacity: "0.18" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Circle, { cx: "110", cy: "60", r: "24", fill: "#d169e5", opacity: "0.25" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Circle, { cx: "170", cy: "50", r: "14", fill: "#d169e5", opacity: "0.15" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Circle, { cx: "110", cy: "60", r: "32", fill: "#fff", opacity: "0.7" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Circle, { cx: "100", cy: "55", r: "4", fill: "#d169e5" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Circle, { cx: "120", cy: "55", r: "4", fill: "#d169e5" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Path, { d: "M104 68 Q110 75 116 68", stroke: "#d169e5", strokeWidth: "2", fill: "none", strokeLinecap: "round" })] }) }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.header, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.welcomeTitle], children: "Create a Oxy Account" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.placeholder })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.welcomeText, { color: textColor }], children: "We're excited to have you join us. Let's get your account set up in just a few easy steps." }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: [styles.button, { backgroundColor: primaryColor }], onPress: nextStep, testID: "welcome-next-button", children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.buttonText, children: "Get Started" }) }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.footerTextContainer, children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, { style: [styles.footerText, { color: textColor }], children: ["Already have an account?", ' '] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { onPress: () => navigate('SignIn'), children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.linkText, { color: primaryColor }], children: "Sign In" }) })] })] }); const renderIdentityStep = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, { style: [styles.stepContainer, { opacity: fadeAnim, transform: [{ translateX: slideAnim }] }], children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.stepTitle, { color: textColor }], children: "Who are you?" }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.inputContainer, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.label, { color: textColor }], children: "Username" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, { style: [styles.input, { backgroundColor: inputBackgroundColor, borderColor, color: textColor }], placeholder: "Choose a username", placeholderTextColor: placeholderColor, value: username, onChangeText: setUsername, autoCapitalize: "none", testID: "username-input" })] }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.inputContainer, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.label, { color: textColor }], children: "Email" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, { style: [styles.input, { backgroundColor: inputBackgroundColor, borderColor, color: textColor }], placeholder: "Enter your email", placeholderTextColor: placeholderColor, value: email, onChangeText: setEmail, autoCapitalize: "none", keyboardType: "email-address", testID: "email-input" })] }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.navigationButtons, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: [styles.navButton, styles.backButton], onPress: prevStep, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.navButtonText, { color: textColor }], children: "Back" }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: [styles.navButton, styles.nextButton, { backgroundColor: primaryColor }], onPress: nextStep, disabled: !username || !email || !validateEmail(email), children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.navButtonText, children: "Next" }) })] })] }); const renderSecurityStep = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, { style: [styles.stepContainer, { opacity: fadeAnim, transform: [{ translateX: slideAnim }] }], children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.stepTitle, { color: textColor }], children: "Secure your account" }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.inputContainer, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.label, { color: textColor }], children: "Password" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, { style: [styles.input, { backgroundColor: inputBackgroundColor, borderColor, color: textColor }], placeholder: "Create a password", placeholderTextColor: placeholderColor, value: password, onChangeText: setPassword, secureTextEntry: true, testID: "password-input" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.passwordHint, { color: isDarkTheme ? '#AAAAAA' : '#666666' }], children: "Password must be at least 8 characters long" })] }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.inputContainer, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.label, { color: textColor }], children: "Confirm Password" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, { style: [styles.input, { backgroundColor: inputBackgroundColor, borderColor, color: textColor }], placeholder: "Confirm your password", placeholderTextColor: placeholderColor, value: confirmPassword, onChangeText: setConfirmPassword, secureTextEntry: true, testID: "confirm-password-input" })] }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.navigationButtons, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: [styles.navButton, styles.backButton], onPress: prevStep, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.navButtonText, { color: textColor }], children: "Back" }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: [styles.navButton, styles.nextButton, { backgroundColor: primaryColor }], onPress: nextStep, disabled: !password || password.length < 8 || password !== confirmPassword, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.navButtonText, children: "Next" }) })] })] }); const renderSummaryStep = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, { style: [styles.stepContainer, { opacity: fadeAnim, transform: [{ translateX: slideAnim }] }], children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.stepTitle, { color: textColor }], children: "Ready to join" }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.summaryContainer, children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.summaryRow, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.summaryLabel, { color: isDarkTheme ? '#AAAAAA' : '#666666' }], children: "Username:" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.summaryValue, { color: textColor }], children: username })] }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.summaryRow, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.summaryLabel, { color: isDarkTheme ? '#AAAAAA' : '#666666' }], children: "Email:" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.summaryValue, { color: textColor }], children: email })] })] }), errorMessage ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.errorContainer, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.errorText, children: errorMessage }) }) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.navigationButtons, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: [styles.navButton, styles.backButton], onPress: prevStep, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.navButtonText, { color: textColor }], children: "Back" }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: [styles.button, { opacity: isLoading ? 0.8 : 1 }], onPress: handleSignUp, disabled: isLoading, testID: "signup-button", children: isLoading ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, { color: "#FFFFFF", size: "small" }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.buttonText, children: "Create Account" }) })] })] }); // Progress indicators const renderProgressIndicators = () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.progressContainer, children: [0, 1, 2, 3].map(step => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: [styles.progressDot, currentStep === step ? { backgroundColor: primaryColor, width: 24 } : { backgroundColor: isDarkTheme ? '#444444' : '#E0E0E0' }] }, step)) }); // Render step based on current step value const renderCurrentStep = () => { switch (currentStep) { case 0: return renderWelcomeStep(); case 1: return renderIdentityStep(); case 2: return renderSecurityStep(); case 3: return renderSummaryStep(); default: return null; } }; return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_bottomSheet.BottomSheetScrollView, { contentContainerStyle: styles.scrollContainer, keyboardShouldPersistTaps: "handled", children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyLogo.default, { style: { marginBottom: 24 }, width: 50, height: 50 }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.formContainer, children: renderCurrentStep() }), currentStep > 0 && renderProgressIndicators()] }); }; const styles = _reactNative.StyleSheet.create({ scrollContainer: { padding: 20 }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }, placeholder: { width: 40 }, formContainer: { width: '100%', marginTop: 8 }, stepContainer: { width: '100%', paddingVertical: 8, paddingHorizontal: 0, marginBottom: 8 }, inputContainer: { marginBottom: 18 }, label: { fontSize: 15, marginBottom: 8, fontWeight: '500', letterSpacing: 0.1 }, input: { height: 48, borderRadius: 16, paddingHorizontal: 16, borderWidth: 1, fontSize: 16, backgroundColor: '#F5F5F5', borderColor: '#E0E0E0', marginBottom: 2 }, button: { backgroundColor: '#d169e5', height: 48, borderRadius: 24, alignItems: 'center', justifyContent: 'center', marginTop: 24, shadowColor: '#d169e5', shadowOpacity: 0.12, shadowOffset: { width: 0, height: 2 }, shadowRadius: 8, elevation: 2 }, buttonText: { color: '#FFFFFF', fontSize: 17, fontWeight: '700', letterSpacing: 0.2 }, footerTextContainer: { flexDirection: 'row', justifyContent: 'center', marginTop: 28 }, footerText: { fontSize: 15, color: '#888' }, linkText: { fontSize: 15, fontWeight: '700', color: '#d169e5' }, errorContainer: { backgroundColor: '#FFE4EC', padding: 14, borderRadius: 18, marginBottom: 16, borderWidth: 1, borderColor: '#F8BBD0' }, errorText: { color: '#D32F2F', fontSize: 15, fontWeight: '500' }, userInfoContainer: { padding: 20, marginVertical: 20, backgroundColor: '#F5F5F5', borderRadius: 24, alignItems: 'center', shadowColor: '#000', shadowOpacity: 0.04, shadowOffset: { width: 0, height: 1 }, shadowRadius: 4, elevation: 1 }, userInfoText: { fontSize: 16, marginBottom: 8, textAlign: 'center' }, actionButtonsContainer: { marginTop: 24 }, // Multi-step form styles welcomeTitle: { fontFamily: _reactNative.Platform.OS === 'web' ? 'Phudu' // Use CSS font name directly for web : 'Phudu-Bold', // Use exact font name as registered with Font.loadAsync fontWeight: _reactNative.Platform.OS === 'web' ? 'bold' : undefined, // Only apply fontWeight on web fontSize: 54, marginBottom: 24 }, welcomeText: { fontSize: 16, textAlign: 'left', marginBottom: 30, lineHeight: 24, color: '#444' }, welcomeImageContainer: { alignItems: 'center', justifyContent: 'center', marginVertical: 30 }, stepTitle: { fontFamily: _reactNative.Platform.OS === 'web' ? 'Phudu' // Use CSS font name directly for web : 'Phudu-Bold', // Use exact font name as registered with Font.loadAsync fontWeight: _reactNative.Platform.OS === 'web' ? 'bold' : undefined, // Only apply fontWeight on web fontSize: 34, marginBottom: 20, color: '#d169e5', maxWidth: '90%' }, navigationButtons: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginTop: 28 }, navButton: { borderRadius: 24, height: 44, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 28, backgroundColor: '#F3E5F5' }, backButton: { backgroundColor: 'transparent', borderWidth: 1, borderColor: '#E0E0E0' }, nextButton: { minWidth: 100, backgroundColor: '#d169e5' }, navButtonText: { fontSize: 16, fontWeight: '700', color: '#d169e5' }, passwordHint: { fontSize: 12, marginTop: 4, color: '#888' }, progressContainer: { flexDirection: 'row', justifyContent: 'center', marginBottom: 20, marginTop: 8 }, progressDot: { height: 10, width: 10, borderRadius: 5, marginHorizontal: 6, backgroundColor: '#E0E0E0', borderWidth: 2, borderColor: '#fff', shadowColor: '#d169e5', shadowOpacity: 0.08, shadowOffset: { width: 0, height: 1 }, shadowRadius: 2, elevation: 1 }, summaryContainer: { padding: 0, marginBottom: 24 }, summaryRow: { flexDirection: 'row', marginBottom: 10 }, summaryLabel: { fontSize: 15, width: 90, color: '#888' }, summaryValue: { fontSize: 15, fontWeight: '600', flex: 1, color: '#222' } }); var _default = exports.default = SignUpScreen; //# sourceMappingURL=SignUpScreen.js.map