@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
241 lines (226 loc) • 7.11 kB
JavaScript
;
import React, { useState, useEffect } from 'react';
import { TouchableOpacity, StyleSheet, 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 { jsx as _jsx } from "react/jsx-runtime";
/**
* An animated follow button with interactive state changes and preventDefault support
*
* @example
* ```tsx
* // Basic usage
* <FollowButton userId="123" />
*
* // With custom styling
* <FollowButton
* userId="123"
* initiallyFollowing={true}
* size="large"
* style={{ borderRadius: 12 }}
* onFollowChange={(isFollowing) => console.log(`User is now ${isFollowing ? 'followed' : 'unfollowed'}`)}
* />
*
* // Inside a pressable container (prevents parent actions)
* <TouchableOpacity onPress={() => navigateToProfile()}>
* <View>
* <Text>User Profile</Text>
* <FollowButton
* userId="123"
* preventParentActions={true} // Default: true
* />
* </View>
* </TouchableOpacity>
*
* // Custom onPress handler
* <FollowButton
* userId="123"
* onPress={(event) => {
* event.preventDefault(); // Custom preventDefault
* // Custom logic here
* }}
* />
* ```
*/
const FollowButton = ({
userId,
initiallyFollowing = false,
size = 'medium',
onFollowChange,
style,
textStyle,
disabled = false,
showLoadingState = true,
preventParentActions = true,
onPress
}) => {
const {
oxyServices,
isAuthenticated
} = useOxy();
const [isFollowing, setIsFollowing] = useState(initiallyFollowing);
const [isLoading, setIsLoading] = useState(false);
// Animation values
const animationProgress = useSharedValue(initiallyFollowing ? 1 : 0);
const scale = useSharedValue(1);
// Update the animation value when isFollowing changes
useEffect(() => {
animationProgress.value = withTiming(isFollowing ? 1 : 0, {
duration: 300,
easing: Easing.bezier(0.25, 0.1, 0.25, 1)
});
}, [isFollowing, animationProgress]);
// The button press handler with preventDefault support
const handlePress = async event => {
// Prevent parent actions if enabled (e.g., if inside a link or pressable container)
if (preventParentActions && event) {
// For React Native Web compatibility
if (Platform.OS === 'web' && event.preventDefault) {
event.preventDefault();
}
// Stop event propagation to prevent parent TouchableOpacity/Pressable actions
if (event.stopPropagation) {
event.stopPropagation();
}
// For React Native, prevent gesture bubbling
if (event.nativeEvent && event.nativeEvent.stopPropagation) {
event.nativeEvent.stopPropagation();
}
}
// If custom onPress is provided, use it instead of default behavior
if (onPress) {
onPress(event);
return;
}
if (disabled || isLoading || !isAuthenticated) return;
// Touch feedback animation
scale.value = withSpring(0.95, {
damping: 10
}, () => {
scale.value = withSpring(1);
});
setIsLoading(true);
try {
// This should be replaced with actual API call to your services
if (isFollowing) {
// Unfollow API call would go here
// await oxyServices.user.unfollowUser(userId);
console.log(`Unfollowing user: ${userId}`);
await new Promise(resolve => setTimeout(resolve, 500)); // Simulating API call
} else {
// Follow API call would go here
// await oxyServices.user.followUser(userId);
console.log(`Following user: ${userId}`);
await new Promise(resolve => setTimeout(resolve, 500)); // Simulating API call
}
// Toggle following state with animation
const newFollowingState = !isFollowing;
setIsFollowing(newFollowingState);
// Call the callback if provided
if (onFollowChange) {
onFollowChange(newFollowingState);
}
// Show success toast
toast.success(newFollowingState ? 'Following user!' : 'Unfollowed user');
} catch (error) {
console.error('Follow action failed:', error);
toast.error('Failed to update follow status. Please try again.');
} finally {
setIsLoading(false);
}
};
// Animated styles for the button
const animatedButtonStyle = useAnimatedStyle(() => {
const backgroundColor = interpolateColor(animationProgress.value, [0, 1], ['#d169e5', '#FFFFFF']);
const borderColor = interpolateColor(animationProgress.value, [0, 1], ['#d169e5', '#d169e5']);
// Add a slight scaling effect during the transition
const transitionScale = 1 + 0.05 * Math.sin(animationProgress.value * Math.PI);
return {
backgroundColor,
borderColor,
borderWidth: 1,
transform: [{
scale: scale.value * transitionScale
}]
};
});
// Animated styles for the text
const animatedTextStyle = useAnimatedStyle(() => {
const color = interpolateColor(animationProgress.value, [0, 1], ['#FFFFFF', '#d169e5']);
return {
color
};
});
// Get size-specific styles
const getSizeStyles = () => {
switch (size) {
case 'small':
return {
button: {
paddingVertical: 6,
paddingHorizontal: 12
},
text: {
fontSize: 12
}
};
case 'large':
return {
button: {
paddingVertical: 12,
paddingHorizontal: 24
},
text: {
fontSize: 18
}
};
default:
// medium
return {
button: {
paddingVertical: 8,
paddingHorizontal: 16
},
text: {
fontSize: 14
}
};
}
};
const sizeStyles = getSizeStyles();
return /*#__PURE__*/_jsx(TouchableOpacity, {
activeOpacity: 0.8,
onPress: handlePress,
disabled: disabled || isLoading || !isAuthenticated,
children: /*#__PURE__*/_jsx(Animated.View, {
style: [styles.button, sizeStyles.button, animatedButtonStyle, style],
children: isLoading && showLoadingState ? /*#__PURE__*/_jsx(ActivityIndicator, {
size: "small",
color: isFollowing ? '#d169e5' : '#FFFFFF'
}) : /*#__PURE__*/_jsx(Animated.Text, {
style: [styles.text, sizeStyles.text, animatedTextStyle, textStyle],
children: isFollowing ? 'Following' : 'Follow'
})
})
});
};
const styles = StyleSheet.create({
button: {
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
borderRadius: 100
},
text: {
fontFamily: Platform.select({
web: 'Phudu',
default: fontFamilies.phuduSemiBold
}),
fontWeight: Platform.OS === 'web' ? '600' : undefined,
textAlign: 'center'
}
});
export default FollowButton;
//# sourceMappingURL=FollowButton.js.map