@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
376 lines (359 loc) • 10.6 kB
JavaScript
"use strict";
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
import { View, KeyboardAvoidingView, ScrollView, StatusBar, Platform } from 'react-native';
import Animated, { useSharedValue, useAnimatedStyle, withTiming, runOnJS } from 'react-native-reanimated';
import { useThemeColors, createAuthStyles } from '../styles';
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
// Individual animated progress dot
const AnimatedProgressDot = ({
isActive,
colors,
styles
}) => {
const width = useSharedValue(isActive ? 12 : 6);
const backgroundColor = useSharedValue(isActive ? colors.primary : colors.border);
useEffect(() => {
width.value = withTiming(isActive ? 12 : 6, {
duration: 300
});
backgroundColor.value = withTiming(isActive ? colors.primary : colors.border, {
duration: 300
});
}, [isActive, colors.primary, colors.border, width, backgroundColor]);
const animatedStyle = useAnimatedStyle(() => ({
width: width.value,
backgroundColor: backgroundColor.value
}));
return /*#__PURE__*/_jsx(Animated.View, {
style: [styles.progressDot, animatedStyle]
});
};
// Progress indicator component
const ProgressIndicator = ({
currentStep,
totalSteps,
colors,
styles
}) => /*#__PURE__*/_jsx(View, {
style: styles.progressContainer,
children: Array.from({
length: totalSteps
}, (_, index) => /*#__PURE__*/_jsx(AnimatedProgressDot, {
isActive: currentStep === index,
colors: colors,
styles: styles
}, index))
});
// Step container with animations
const AnimatedStepContainer = ({
children,
fadeAnim,
slideAnim,
scaleAnim,
styles,
stepKey
}) => {
const animatedStyle = useAnimatedStyle(() => ({
opacity: fadeAnim.value,
transform: [{
translateX: slideAnim.value
}, {
scale: scaleAnim.value
}]
}));
return /*#__PURE__*/_jsx(Animated.View, {
style: [styles.stepContainer, animatedStyle],
children: children
}, stepKey);
};
const StepBasedScreen = ({
steps,
initialStep = 0,
showProgressIndicator = true,
enableAnimations = true,
onStepChange,
onComplete,
stepData = [],
navigate,
goBack,
onAuthenticated,
theme,
oxyServices
}) => {
const colors = useThemeColors(theme);
const styles = useMemo(() => ({
...createAuthStyles(colors, theme),
// Additional styles for step components
modernHeader: {
alignItems: 'flex-start',
width: '100%',
marginBottom: 24
},
modernTitle: {
fontFamily: Platform.OS === 'web' ? 'Phudu' : 'Phudu-Bold',
fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
fontSize: 42,
lineHeight: 50.4,
// 42 * 1.2
marginBottom: 12,
textAlign: 'left'
},
modernSubtitle: {
fontSize: 18,
lineHeight: 24,
textAlign: 'left',
opacity: 0.8
},
modernInputContainer: {
width: '100%'
},
button: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 18,
paddingHorizontal: 32,
borderRadius: 16,
marginVertical: 8,
gap: 8,
width: '100%',
...Platform.select({
web: {
boxShadow: '0 4px 8px rgba(0,0,0,0.3)'
},
default: {
shadowOffset: {
width: 0,
height: 4
},
shadowOpacity: 0.3,
shadowRadius: 8,
elevation: 6
}
})
},
buttonText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
letterSpacing: 0.5
},
footerText: {
fontSize: 14,
lineHeight: 20
},
footerTextContainer: {
flexDirection: 'row',
justifyContent: 'center',
marginTop: 16
},
linkText: {
fontSize: 14,
lineHeight: 20,
fontWeight: '600',
textDecorationLine: 'underline'
},
progressContainer: {
flexDirection: 'row',
width: '100%',
justifyContent: 'center',
marginTop: 24,
// Space for bottom sheet handle (~20px) + small buffer
marginBottom: 24 // Equal spacing below dots
},
progressDot: {
height: 6,
width: 6,
borderRadius: 3,
marginHorizontal: 3,
backgroundColor: colors.border
}
}), [colors, theme]);
// State management
const [state, setState] = useState({
currentStep: initialStep,
stepData: stepData,
isTransitioning: false
});
// Update state when stepData prop changes
useEffect(() => {
setState(prevState => ({
...prevState,
stepData: stepData
}));
}, [stepData]);
// Animation values
const fadeAnim = useSharedValue(1);
const slideAnim = useSharedValue(0);
const scaleAnim = useSharedValue(1);
// Refs for animation callbacks
const onStepChangeRef = useRef(onStepChange);
const onCompleteRef = useRef(onComplete);
onStepChangeRef.current = onStepChange;
onCompleteRef.current = onComplete;
// Update step data
const updateStepData = useCallback((stepIndex, data) => {
setState(prev => ({
...prev,
stepData: prev.stepData.map((item, index) => index === stepIndex ? data : item)
}));
}, [setState]);
// Animation transition function
const animateTransition = useCallback(nextStep => {
if (!enableAnimations) {
setState(prev => ({
...prev,
currentStep: nextStep
}));
onStepChangeRef.current?.(nextStep, steps.length);
return;
}
setState(prev => ({
...prev,
isTransitioning: true
}));
const applyStepChange = (targetStep, totalSteps) => {
setState(prev => ({
...prev,
currentStep: targetStep,
isTransitioning: false
}));
onStepChangeRef.current?.(targetStep, totalSteps);
// Prepare next step animation
fadeAnim.value = 0;
scaleAnim.value = 0.98;
slideAnim.value = 0;
fadeAnim.value = withTiming(1, {
duration: 220
});
scaleAnim.value = withTiming(1, {
duration: 220
});
};
// Animate current step out
scaleAnim.value = withTiming(0.98, {
duration: 180
});
fadeAnim.value = withTiming(0, {
duration: 180
}, finished => {
if (finished) {
runOnJS(applyStepChange)(nextStep, steps.length);
}
});
}, [enableAnimations, steps.length, fadeAnim, scaleAnim, slideAnim]);
// Navigation functions
const nextStep = useCallback(() => {
if (state.isTransitioning) return;
const currentStepConfig = steps[state.currentStep];
if (currentStepConfig?.canProceed) {
const stepData = state.stepData[state.currentStep];
if (!currentStepConfig.canProceed(stepData)) {
return; // Step validation failed
}
}
if (state.currentStep < steps.length - 1) {
// Call onExit for current step
currentStepConfig?.onExit?.();
animateTransition(state.currentStep + 1);
// Call onEnter for next step
const nextStepConfig = steps[state.currentStep + 1];
nextStepConfig?.onEnter?.();
} else {
// Final step - call onComplete
onCompleteRef.current?.(state.stepData);
}
}, [state.currentStep, state.stepData, state.isTransitioning, steps, animateTransition]);
const prevStep = useCallback(() => {
if (state.isTransitioning) return;
if (state.currentStep > 0) {
// Call onExit for current step
const currentStepConfig = steps[state.currentStep];
currentStepConfig?.onExit?.();
animateTransition(state.currentStep - 1);
// Call onEnter for previous step
const prevStepConfig = steps[state.currentStep - 1];
prevStepConfig?.onEnter?.();
} else {
// First step - go back
goBack?.();
}
}, [state.currentStep, state.isTransitioning, steps, animateTransition, goBack]);
const goToStep = useCallback(stepIndex => {
if (state.isTransitioning || stepIndex < 0 || stepIndex >= steps.length) return;
if (stepIndex !== state.currentStep) {
// Call onExit for current step
const currentStepConfig = steps[state.currentStep];
currentStepConfig?.onExit?.();
animateTransition(stepIndex);
// Call onEnter for target step
const targetStepConfig = steps[stepIndex];
targetStepConfig?.onEnter?.();
}
}, [state.currentStep, state.isTransitioning, steps, animateTransition]);
// Get current step component
const currentStepConfig = steps[state.currentStep];
const CurrentStepComponent = currentStepConfig?.component;
// Enhanced props for the step component
const stepProps = {
...currentStepConfig?.props,
// Common props
colors,
styles,
theme,
navigate,
goBack,
onAuthenticated,
oxyServices,
// Step navigation
nextStep,
prevStep,
goToStep,
currentStep: state.currentStep,
totalSteps: steps.length,
// Step data - spread the step data properties directly as props
...state.stepData[state.currentStep],
// Step data management
updateStepData: data => updateStepData(state.currentStep, data),
allStepData: state.stepData,
// State
isTransitioning: state.isTransitioning,
// Animation refs (for components that need direct access)
fadeAnim,
slideAnim,
scaleAnim
};
return /*#__PURE__*/_jsxs(KeyboardAvoidingView, {
style: [styles.container],
behavior: undefined,
children: [/*#__PURE__*/_jsx(StatusBar, {
barStyle: theme === 'dark' ? 'light-content' : 'dark-content',
backgroundColor: colors.background
}), /*#__PURE__*/_jsxs(ScrollView, {
contentContainerStyle: styles.scrollContent,
showsVerticalScrollIndicator: false,
keyboardShouldPersistTaps: "handled",
bounces: false,
alwaysBounceVertical: false,
overScrollMode: "never",
removeClippedSubviews: true,
children: [showProgressIndicator && steps.length > 1 && /*#__PURE__*/_jsx(ProgressIndicator, {
currentStep: state.currentStep,
totalSteps: steps.length,
colors: colors,
styles: styles
}), /*#__PURE__*/_jsx(AnimatedStepContainer, {
fadeAnim: fadeAnim,
slideAnim: slideAnim,
scaleAnim: scaleAnim,
styles: styles,
stepKey: `step-${state.currentStep}`,
children: CurrentStepComponent && /*#__PURE__*/_jsx(CurrentStepComponent, {
...stepProps
})
})]
})]
});
};
export default StepBasedScreen;
//# sourceMappingURL=StepBasedScreen.js.map