@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
208 lines (195 loc) • 6.03 kB
JavaScript
;
import { useEffect, useCallback } from 'react';
import { TouchableOpacity, Text, Platform, ActivityIndicator } from 'react-native';
import Animated, { useSharedValue, useAnimatedStyle, withSpring, interpolateColor, Easing, withTiming } from 'react-native-reanimated';
import { useOxy } from '../context/OxyContext';
import { fontFamilies } from '../styles/fonts';
import { toast } from '../../lib/sonner';
import { useFollow } from '../hooks/useFollow';
import { useThemeColors } from '../styles/theme';
// Create animated TouchableOpacity
import { jsx as _jsx } from "react/jsx-runtime";
const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity);
const AnimatedText = Animated.createAnimatedComponent(Text);
const FollowButton = ({
userId,
initiallyFollowing = false,
size = 'medium',
onFollowChange,
style,
textStyle,
disabled = false,
showLoadingState = true,
preventParentActions = true,
theme = 'light'
}) => {
const {
oxyServices,
isAuthenticated
} = useOxy();
const colors = useThemeColors(theme);
const {
isFollowing,
isLoading,
error,
toggleFollow,
setFollowStatus,
fetchStatus,
clearError
} = useFollow(userId);
// Animation values
const animationProgress = useSharedValue(isFollowing ? 1 : 0);
const scale = useSharedValue(1);
// Button press handler with animation
const handlePress = useCallback(async event => {
if (preventParentActions && event && event.preventDefault) {
event.preventDefault();
event.stopPropagation?.();
}
if (disabled || isLoading) return;
// Press animation
scale.value = withTiming(0.95, {
duration: 100
}, finished => {
if (finished) {
scale.value = withSpring(1, {
damping: 15,
stiffness: 200
});
}
});
try {
await toggleFollow?.();
if (onFollowChange) onFollowChange(!isFollowing);
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
toast.error(error.message || 'Failed to update follow status');
}
}, [disabled, isLoading, toggleFollow, onFollowChange, isFollowing, preventParentActions, scale]);
// Initialize Zustand state with initial value if not already set
useEffect(() => {
if (userId && !isFollowing && initiallyFollowing) {
setFollowStatus?.(initiallyFollowing);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [userId, initiallyFollowing]);
// Fetch latest follow status from backend on mount if authenticated
useEffect(() => {
if (userId && isAuthenticated) {
fetchStatus?.();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [userId, oxyServices, isAuthenticated]);
// Animate button on follow/unfollow
useEffect(() => {
animationProgress.value = withTiming(isFollowing ? 1 : 0, {
duration: 300,
easing: Easing.inOut(Easing.ease)
});
}, [isFollowing]);
// Animated styles for better performance
const animatedButtonStyle = useAnimatedStyle(() => {
return {
transform: [{
scale: scale.value
}],
backgroundColor: interpolateColor(animationProgress.value, [0, 1], [colors.background, colors.primary]),
borderColor: interpolateColor(animationProgress.value, [0, 1], [colors.border, colors.primary])
};
}, [colors]);
const animatedTextStyle = useAnimatedStyle(() => {
return {
color: interpolateColor(animationProgress.value, [0, 1], [colors.text, '#FFFFFF'])
};
}, [colors]);
// Get base button style (without state-specific colors since they're animated)
const getBaseButtonStyle = () => {
const baseStyle = {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
...Platform.select({
web: {
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
},
default: {
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2
},
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 2
}
})
};
// Size-specific styles
let sizeStyle = {};
if (size === 'small') {
sizeStyle = {
paddingVertical: 6,
paddingHorizontal: 12,
minWidth: 70,
borderRadius: 35
};
} else if (size === 'large') {
sizeStyle = {
paddingVertical: 12,
paddingHorizontal: 24,
minWidth: 120,
borderRadius: 35
};
} else {
// medium
sizeStyle = {
paddingVertical: 8,
paddingHorizontal: 16,
minWidth: 90,
borderRadius: 35
};
}
return [baseStyle, sizeStyle, style];
};
// Get base text style (without state-specific colors since they're animated)
const getBaseTextStyle = () => {
const baseTextStyle = {
fontFamily: fontFamilies.phuduSemiBold,
fontWeight: '600'
};
// Size-specific text styles
let sizeTextStyle = {};
if (size === 'small') {
sizeTextStyle = {
fontSize: 13
};
} else if (size === 'large') {
sizeTextStyle = {
fontSize: 16
};
} else {
// medium
sizeTextStyle = {
fontSize: 15
};
}
return [baseTextStyle, sizeTextStyle, textStyle];
};
return /*#__PURE__*/_jsx(AnimatedTouchableOpacity, {
style: [getBaseButtonStyle(), animatedButtonStyle],
onPress: handlePress,
disabled: disabled || isLoading,
activeOpacity: 0.8,
children: showLoadingState && isLoading ? /*#__PURE__*/_jsx(ActivityIndicator, {
size: size === 'small' ? 'small' : 'small',
color: isFollowing ? '#FFFFFF' : colors.primary
}) : /*#__PURE__*/_jsx(AnimatedText, {
style: [getBaseTextStyle(), animatedTextStyle],
children: isFollowing ? 'Following' : 'Follow'
})
});
};
export { FollowButton };
export default FollowButton;
//# sourceMappingURL=FollowButton.js.map