UNPKG

@oxyhq/services

Version:

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

755 lines (746 loc) • 20.9 kB
"use strict"; import React, { useState, useRef } from 'react'; import { View, Text, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator, Platform, Animated } from 'react-native'; import { useOxy } from '../context/OxyContext'; import OxyLogo from '../components/OxyLogo'; import { BottomSheetScrollView } from '../components/bottomSheet'; // Add icon import import Svg, { Path, Circle } from 'react-native-svg'; import { toast } from '../../lib/sonner'; import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime"; const SignUpScreen = ({ navigate, goBack, theme }) => { // Form data states const [username, setUsername] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [errorMessage, setErrorMessage] = useState(''); // Multi-step form states const [currentStep, setCurrentStep] = useState(0); const fadeAnim = useRef(new Animated.Value(1)).current; const slideAnim = useRef(new Animated.Value(0)).current; const heightAnim = useRef(new Animated.Value(400)).current; // Initial height value const [containerHeight, setContainerHeight] = useState(400); // Default height const { signUp, isLoading, user, isAuthenticated } = 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 Animated.timing(fadeAnim, { toValue: 0, duration: 250, useNativeDriver: true }).start(() => { setCurrentStep(nextStep); // Reset slide position slideAnim.setValue(-100); // Fade in and slide Animated.parallel([Animated.timing(fadeAnim, { toValue: 1, duration: 250, useNativeDriver: true }), 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__*/_jsxs(BottomSheetScrollView, { style: [styles.scrollContainer, { backgroundColor, padding: 20 }], children: [/*#__PURE__*/_jsxs(Text, { style: [styles.welcomeTitle, { color: textColor, textAlign: 'center' }], children: ["Welcome, ", user.username, "!"] }), /*#__PURE__*/_jsxs(View, { style: styles.userInfoContainer, children: [/*#__PURE__*/_jsx(Text, { style: [styles.userInfoText, { color: textColor }], children: "You are already signed in." }), user.email && /*#__PURE__*/_jsxs(Text, { style: [styles.userInfoText, { color: isDarkTheme ? '#BBBBBB' : '#666666' }], children: ["Email: ", user.email] })] }), /*#__PURE__*/_jsx(View, { style: styles.actionButtonsContainer, children: /*#__PURE__*/_jsx(TouchableOpacity, { style: [styles.button, { backgroundColor: primaryColor }], onPress: () => navigate('AccountCenter'), children: /*#__PURE__*/_jsx(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) { toast.error('Please fill in all fields'); return; } if (!validateEmail(email)) { toast.error('Please enter a valid email address'); return; } if (password !== confirmPassword) { toast.error('Passwords do not match'); return; } if (password.length < 8) { toast.error('Password must be at least 8 characters long'); return; } try { setErrorMessage(''); await signUp(username, email, password); toast.success('Account created successfully! Welcome to Oxy!'); // The authentication state change will be handled through context } catch (error) { toast.error(error.message || 'Sign up failed'); } }; // Step components const renderWelcomeStep = () => /*#__PURE__*/_jsxs(Animated.View, { style: [styles.stepContainer, { opacity: fadeAnim, transform: [{ translateX: slideAnim }] }], children: [/*#__PURE__*/_jsx(View, { style: styles.welcomeImageContainer, children: /*#__PURE__*/_jsxs(Svg, { width: 220, height: 120, viewBox: "0 0 220 120", children: [/*#__PURE__*/_jsx(Path, { d: "M30 100 Q60 20 110 60 Q160 100 190 40", stroke: "#d169e5", strokeWidth: "8", fill: "none" }), /*#__PURE__*/_jsx(Circle, { cx: "60", cy: "60", r: "18", fill: "#d169e5", opacity: "0.18" }), /*#__PURE__*/_jsx(Circle, { cx: "110", cy: "60", r: "24", fill: "#d169e5", opacity: "0.25" }), /*#__PURE__*/_jsx(Circle, { cx: "170", cy: "50", r: "14", fill: "#d169e5", opacity: "0.15" }), /*#__PURE__*/_jsx(Circle, { cx: "110", cy: "60", r: "32", fill: "#fff", opacity: "0.7" }), /*#__PURE__*/_jsx(Circle, { cx: "100", cy: "55", r: "4", fill: "#d169e5" }), /*#__PURE__*/_jsx(Circle, { cx: "120", cy: "55", r: "4", fill: "#d169e5" }), /*#__PURE__*/_jsx(Path, { d: "M104 68 Q110 75 116 68", stroke: "#d169e5", strokeWidth: "2", fill: "none", strokeLinecap: "round" })] }) }), /*#__PURE__*/_jsxs(View, { style: styles.header, children: [/*#__PURE__*/_jsx(Text, { style: [styles.welcomeTitle], children: "Create a Oxy Account" }), /*#__PURE__*/_jsx(View, { style: styles.placeholder })] }), /*#__PURE__*/_jsx(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__*/_jsx(TouchableOpacity, { style: [styles.button, { backgroundColor: primaryColor }], onPress: nextStep, testID: "welcome-next-button", children: /*#__PURE__*/_jsx(Text, { style: styles.buttonText, children: "Get Started" }) }), /*#__PURE__*/_jsxs(View, { style: styles.footerTextContainer, children: [/*#__PURE__*/_jsxs(Text, { style: [styles.footerText, { color: textColor }], children: ["Already have an account?", ' '] }), /*#__PURE__*/_jsx(TouchableOpacity, { onPress: () => navigate('SignIn'), children: /*#__PURE__*/_jsx(Text, { style: [styles.linkText, { color: primaryColor }], children: "Sign In" }) })] })] }); const renderIdentityStep = () => /*#__PURE__*/_jsxs(Animated.View, { style: [styles.stepContainer, { opacity: fadeAnim, transform: [{ translateX: slideAnim }] }], children: [/*#__PURE__*/_jsx(Text, { style: [styles.stepTitle, { color: textColor }], children: "Who are you?" }), /*#__PURE__*/_jsxs(View, { style: styles.inputContainer, children: [/*#__PURE__*/_jsx(Text, { style: [styles.label, { color: textColor }], children: "Username" }), /*#__PURE__*/_jsx(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__*/_jsxs(View, { style: styles.inputContainer, children: [/*#__PURE__*/_jsx(Text, { style: [styles.label, { color: textColor }], children: "Email" }), /*#__PURE__*/_jsx(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__*/_jsxs(View, { style: styles.navigationButtons, children: [/*#__PURE__*/_jsx(TouchableOpacity, { style: [styles.navButton, styles.backButton], onPress: prevStep, children: /*#__PURE__*/_jsx(Text, { style: [styles.navButtonText, { color: textColor }], children: "Back" }) }), /*#__PURE__*/_jsx(TouchableOpacity, { style: [styles.navButton, styles.nextButton, { backgroundColor: primaryColor }], onPress: nextStep, disabled: !username || !email || !validateEmail(email), children: /*#__PURE__*/_jsx(Text, { style: styles.navButtonText, children: "Next" }) })] })] }); const renderSecurityStep = () => /*#__PURE__*/_jsxs(Animated.View, { style: [styles.stepContainer, { opacity: fadeAnim, transform: [{ translateX: slideAnim }] }], children: [/*#__PURE__*/_jsx(Text, { style: [styles.stepTitle, { color: textColor }], children: "Secure your account" }), /*#__PURE__*/_jsxs(View, { style: styles.inputContainer, children: [/*#__PURE__*/_jsx(Text, { style: [styles.label, { color: textColor }], children: "Password" }), /*#__PURE__*/_jsx(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__*/_jsx(Text, { style: [styles.passwordHint, { color: isDarkTheme ? '#AAAAAA' : '#666666' }], children: "Password must be at least 8 characters long" })] }), /*#__PURE__*/_jsxs(View, { style: styles.inputContainer, children: [/*#__PURE__*/_jsx(Text, { style: [styles.label, { color: textColor }], children: "Confirm Password" }), /*#__PURE__*/_jsx(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__*/_jsxs(View, { style: styles.navigationButtons, children: [/*#__PURE__*/_jsx(TouchableOpacity, { style: [styles.navButton, styles.backButton], onPress: prevStep, children: /*#__PURE__*/_jsx(Text, { style: [styles.navButtonText, { color: textColor }], children: "Back" }) }), /*#__PURE__*/_jsx(TouchableOpacity, { style: [styles.navButton, styles.nextButton, { backgroundColor: primaryColor }], onPress: nextStep, disabled: !password || password.length < 8 || password !== confirmPassword, children: /*#__PURE__*/_jsx(Text, { style: styles.navButtonText, children: "Next" }) })] })] }); const renderSummaryStep = () => /*#__PURE__*/_jsxs(Animated.View, { style: [styles.stepContainer, { opacity: fadeAnim, transform: [{ translateX: slideAnim }] }], children: [/*#__PURE__*/_jsx(Text, { style: [styles.stepTitle, { color: textColor }], children: "Ready to join" }), /*#__PURE__*/_jsxs(View, { style: styles.summaryContainer, children: [/*#__PURE__*/_jsxs(View, { style: styles.summaryRow, children: [/*#__PURE__*/_jsx(Text, { style: [styles.summaryLabel, { color: isDarkTheme ? '#AAAAAA' : '#666666' }], children: "Username:" }), /*#__PURE__*/_jsx(Text, { style: [styles.summaryValue, { color: textColor }], children: username })] }), /*#__PURE__*/_jsxs(View, { style: styles.summaryRow, children: [/*#__PURE__*/_jsx(Text, { style: [styles.summaryLabel, { color: isDarkTheme ? '#AAAAAA' : '#666666' }], children: "Email:" }), /*#__PURE__*/_jsx(Text, { style: [styles.summaryValue, { color: textColor }], children: email })] })] }), errorMessage ? /*#__PURE__*/_jsx(View, { style: styles.errorContainer, children: /*#__PURE__*/_jsx(Text, { style: styles.errorText, children: errorMessage }) }) : null, /*#__PURE__*/_jsxs(View, { style: styles.navigationButtons, children: [/*#__PURE__*/_jsx(TouchableOpacity, { style: [styles.navButton, styles.backButton], onPress: prevStep, children: /*#__PURE__*/_jsx(Text, { style: [styles.navButtonText, { color: textColor }], children: "Back" }) }), /*#__PURE__*/_jsx(TouchableOpacity, { style: [styles.button, { opacity: isLoading ? 0.8 : 1 }], onPress: handleSignUp, disabled: isLoading, testID: "signup-button", children: isLoading ? /*#__PURE__*/_jsx(ActivityIndicator, { color: "#FFFFFF", size: "small" }) : /*#__PURE__*/_jsx(Text, { style: styles.buttonText, children: "Create Account" }) })] })] }); // Progress indicators const renderProgressIndicators = () => /*#__PURE__*/_jsx(View, { style: styles.progressContainer, children: [0, 1, 2, 3].map(step => /*#__PURE__*/_jsx(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__*/_jsxs(BottomSheetScrollView, { contentContainerStyle: styles.scrollContainer, keyboardShouldPersistTaps: "handled", children: [/*#__PURE__*/_jsx(OxyLogo, { style: { marginBottom: 24 }, width: 50, height: 50 }), /*#__PURE__*/_jsx(View, { style: styles.formContainer, children: renderCurrentStep() }), currentStep > 0 && renderProgressIndicators()] }); }; const styles = 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: Platform.OS === 'web' ? 'Phudu' // Use CSS font name directly for web : 'Phudu-Bold', // Use exact font name as registered with Font.loadAsync fontWeight: 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: Platform.OS === 'web' ? 'Phudu' // Use CSS font name directly for web : 'Phudu-Bold', // Use exact font name as registered with Font.loadAsync fontWeight: 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' } }); export default SignUpScreen; //# sourceMappingURL=SignUpScreen.js.map