@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
224 lines (207 loc) • 6.71 kB
JavaScript
"use strict";
import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
import { useOxy } from '../context/OxyContext';
import { useThemeColors } from '../styles';
import { toast } from '../../lib/sonner';
import StepBasedScreen from '../components/StepBasedScreen';
import SignUpWelcomeStep from './steps/SignUpWelcomeStep';
import SignUpIdentityStep from './steps/SignUpIdentityStep';
import SignUpSecurityStep from './steps/SignUpSecurityStep';
import SignUpSummaryStep from './steps/SignUpSummaryStep';
import { TTLCache, registerCacheForCleanup } from '../../utils/cache';
// Types for better type safety
import { jsx as _jsx } from "react/jsx-runtime";
// Constants
const USERNAME_MIN_LENGTH = 3;
const PASSWORD_MIN_LENGTH = 8;
// Email validation regex
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
// Main component
const SignUpScreen = ({
navigate,
goBack,
onAuthenticated,
theme
}) => {
const {
signUp,
oxyServices
} = useOxy();
const colors = useThemeColors(theme);
// Form data state
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
// Validation state
const [validationState, setValidationState] = useState({
status: 'idle',
message: ''
});
// Error message state
const [errorMessage, setErrorMessage] = useState('');
// Username validation with caching - uses centralized cache
const usernameCache = useRef(new TTLCache(5 * 60 * 1000)); // 5 minutes cache
// Register cache for cleanup on mount
useEffect(() => {
registerCacheForCleanup(usernameCache.current);
return () => {
usernameCache.current.clear();
};
}, []);
const validateUsername = useCallback(async usernameToValidate => {
if (!usernameToValidate || usernameToValidate.length < USERNAME_MIN_LENGTH) {
setValidationState({
status: 'invalid',
message: 'Username must be at least 3 characters'
});
return false;
}
// Check cache first
const cached = usernameCache.current.get(usernameToValidate);
if (cached !== null) {
const isValid = cached;
setValidationState({
status: isValid ? 'valid' : 'invalid',
message: isValid ? '' : 'Username is already taken'
});
return isValid;
}
setValidationState({
status: 'validating',
message: ''
});
try {
const result = await oxyServices.checkUsernameAvailability(usernameToValidate);
const isValid = result.available;
// Cache the result
usernameCache.current.set(usernameToValidate, isValid);
setValidationState({
status: isValid ? 'valid' : 'invalid',
message: isValid ? '' : result.message || 'Username is already taken'
});
return isValid;
} catch (error) {
console.error('Username validation error:', error);
setValidationState({
status: 'invalid',
message: 'Unable to validate username. Please try again.'
});
return false;
}
}, [oxyServices]);
// Email validation
const validateEmail = useCallback(emailToValidate => {
return EMAIL_REGEX.test(emailToValidate);
}, []);
// Password validation
const validatePassword = useCallback(passwordToValidate => {
return passwordToValidate.length >= PASSWORD_MIN_LENGTH;
}, []);
// Handle form completion
const handleComplete = useCallback(async stepData => {
if (!username || !email || !password) {
toast.error('Please fill in all required fields');
return;
}
if (!validateEmail(email)) {
toast.error('Please enter a valid email address');
return;
}
if (!validatePassword(password)) {
toast.error('Password must be at least 8 characters long');
return;
}
if (password !== confirmPassword) {
toast.error('Passwords do not match');
return;
}
try {
setIsLoading(true);
const user = await signUp(username, email, password);
toast.success('Account created successfully! Welcome to Oxy!');
// Navigate to welcome screen or handle authentication
navigate('WelcomeNewUser', {
newUser: user
});
} catch (error) {
toast.error(error.message || 'Sign up failed');
} finally {
setIsLoading(false);
}
}, [username, email, password, confirmPassword, validateEmail, validatePassword, signUp, navigate]);
// Step configurations
const steps = useMemo(() => [{
id: 'welcome',
component: SignUpWelcomeStep,
canProceed: () => true
}, {
id: 'identity',
component: SignUpIdentityStep,
canProceed: () => !!(username.trim() && email.trim() && validateEmail(email) && validationState.status === 'valid'),
onEnter: () => {
// Auto-validate username when entering this step
if (username && validationState.status === 'idle') {
validateUsername(username);
}
}
}, {
id: 'security',
component: SignUpSecurityStep,
canProceed: () => !!(password && validatePassword(password) && password === confirmPassword)
}, {
id: 'summary',
component: SignUpSummaryStep,
canProceed: () => true
}], [username, email, password, confirmPassword, validationState.status, validateEmail, validatePassword, validateUsername]);
// Step data for the reusable component
const stepData = useMemo(() => [
// Welcome step - no data needed
{},
// Identity step
{
username,
email,
setUsername,
setEmail,
validationState,
setValidationState,
setErrorMessage,
validateEmail,
validateUsername
},
// Security step
{
password,
confirmPassword,
setPassword,
setConfirmPassword,
showPassword,
showConfirmPassword,
setShowPassword,
setShowConfirmPassword,
setErrorMessage,
validatePassword
},
// Summary step
{
isLoading
}], [username, email, password, confirmPassword, showPassword, showConfirmPassword, validationState, errorMessage, validateEmail, validatePassword, isLoading]);
return /*#__PURE__*/_jsx(StepBasedScreen, {
steps: steps,
stepData: stepData,
onComplete: handleComplete,
navigate: navigate,
goBack: goBack,
onAuthenticated: onAuthenticated,
theme: theme,
showProgressIndicator: true,
enableAnimations: true,
oxyServices: oxyServices
});
};
export default SignUpScreen;
//# sourceMappingURL=SignUpScreen.js.map