UNPKG

@oxyhq/services

Version:

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

830 lines (817 loc) • 23.9 kB
"use strict"; import React, { useState, useEffect, useRef } from 'react'; import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator, ScrollView, Alert, TextInput, Animated } from 'react-native'; import { useOxy } from '../context/OxyContext'; import Avatar from '../components/Avatar'; import OxyIcon from '../components/icon/OxyIcon'; import { Ionicons } from '@expo/vector-icons'; import { toast } from '../../lib/sonner'; import { fontFamilies } from '../styles/fonts'; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; const AccountSettingsScreen = ({ onClose, theme, goBack, navigate }) => { const { user, oxyServices, isLoading: authLoading } = useOxy(); const [isLoading, setIsLoading] = useState(false); const [isSaving, setIsSaving] = useState(false); // Animation refs const saveButtonScale = useRef(new Animated.Value(1)).current; // Form state const [displayName, setDisplayName] = useState(''); const [username, setUsername] = useState(''); const [email, setEmail] = useState(''); const [bio, setBio] = useState(''); const [location, setLocation] = useState(''); const [website, setWebsite] = useState(''); const [avatarUrl, setAvatarUrl] = useState(''); // Editing states const [editingField, setEditingField] = useState(null); // Temporary input states for inline editing const [tempDisplayName, setTempDisplayName] = useState(''); const [tempUsername, setTempUsername] = useState(''); const [tempEmail, setTempEmail] = useState(''); const [tempBio, setTempBio] = useState(''); const [tempLocation, setTempLocation] = useState(''); const [tempWebsite, setTempWebsite] = useState(''); const isDarkTheme = theme === 'dark'; const backgroundColor = isDarkTheme ? '#121212' : '#f2f2f2'; const primaryColor = '#007AFF'; // Animation functions const animateSaveButton = toValue => { Animated.spring(saveButtonScale, { toValue, useNativeDriver: true, tension: 150, friction: 8 }).start(); }; // Load user data useEffect(() => { if (user) { const userDisplayName = typeof user.name === 'string' ? user.name : user.name?.full || user.name?.first || ''; setDisplayName(userDisplayName); setUsername(user.username || ''); setEmail(user.email || ''); setBio(user.bio || ''); setLocation(user.location || ''); setWebsite(user.website || ''); setAvatarUrl(user.avatar?.url || ''); } }, [user]); const handleSave = async () => { if (!user) return; try { setIsSaving(true); animateSaveButton(0.95); // Scale down slightly for animation const updates = { username, email, bio, location, website }; // Handle name field if (displayName) { updates.name = displayName; } // Handle avatar if (avatarUrl !== user.avatar?.url) { updates.avatar = { url: avatarUrl }; } await oxyServices.updateProfile(updates); toast.success('Profile updated successfully'); animateSaveButton(1); // Scale back to normal if (onClose) { onClose(); } else if (goBack) { goBack(); } } catch (error) { toast.error(error.message || 'Failed to update profile'); animateSaveButton(1); // Scale back to normal on error } finally { setIsSaving(false); } }; const handleAvatarUpdate = () => { Alert.alert('Update Avatar', 'Choose how you want to update your profile picture', [{ text: 'Cancel', style: 'cancel' }, { text: 'Use Mock URL', onPress: () => { const mockUrl = `https://ui-avatars.com/api/?name=${displayName || username}&background=random`; setAvatarUrl(mockUrl); } }, { text: 'Remove Avatar', onPress: () => setAvatarUrl(''), style: 'destructive' }]); }; const startEditing = (type, currentValue) => { switch (type) { case 'displayName': setTempDisplayName(currentValue); break; case 'username': setTempUsername(currentValue); break; case 'email': setTempEmail(currentValue); break; case 'bio': setTempBio(currentValue); break; case 'location': setTempLocation(currentValue); break; case 'website': setTempWebsite(currentValue); break; } setEditingField(type); }; const saveField = type => { animateSaveButton(0.95); // Scale down slightly for animation switch (type) { case 'displayName': setDisplayName(tempDisplayName); break; case 'username': setUsername(tempUsername); break; case 'email': setEmail(tempEmail); break; case 'bio': setBio(tempBio); break; case 'location': setLocation(tempLocation); break; case 'website': setWebsite(tempWebsite); break; } // Brief delay for animation, then reset and close editing setTimeout(() => { animateSaveButton(1); setEditingField(null); }, 150); }; const cancelEditing = () => { setEditingField(null); }; const getFieldLabel = type => { const labels = { displayName: 'Display Name', username: 'Username', email: 'Email', bio: 'Bio', location: 'Location', website: 'Website' }; return labels[type] || 'Field'; }; const getFieldIcon = type => { const icons = { displayName: { name: 'person', color: '#007AFF' }, username: { name: 'at', color: '#5856D6' }, email: { name: 'mail', color: '#FF9500' }, bio: { name: 'document-text', color: '#34C759' }, location: { name: 'location', color: '#FF3B30' }, website: { name: 'link', color: '#32D74B' } }; return icons[type] || { name: 'person', color: '#007AFF' }; }; const renderEditingField = type => { const fieldConfig = { displayName: { label: 'Display Name', value: displayName, placeholder: 'Enter your display name', icon: 'person', color: '#007AFF', multiline: false, keyboardType: 'default' }, username: { label: 'Username', value: username, placeholder: 'Choose a username', icon: 'at', color: '#5856D6', multiline: false, keyboardType: 'default' }, email: { label: 'Email', value: email, placeholder: 'Enter your email address', icon: 'mail', color: '#FF9500', multiline: false, keyboardType: 'email-address' }, bio: { label: 'Bio', value: bio, placeholder: 'Tell people about yourself...', icon: 'document-text', color: '#34C759', multiline: true, keyboardType: 'default' }, location: { label: 'Location', value: location, placeholder: 'Enter your location', icon: 'location', color: '#FF3B30', multiline: false, keyboardType: 'default' }, website: { label: 'Website', value: website, placeholder: 'Enter your website URL', icon: 'link', color: '#32D74B', multiline: false, keyboardType: 'url' } }; const config = fieldConfig[type]; if (!config) return null; const tempValue = (() => { switch (type) { case 'displayName': return tempDisplayName; case 'username': return tempUsername; case 'email': return tempEmail; case 'bio': return tempBio; case 'location': return tempLocation; case 'website': return tempWebsite; default: return ''; } })(); const setTempValue = text => { switch (type) { case 'displayName': setTempDisplayName(text); break; case 'username': setTempUsername(text); break; case 'email': setTempEmail(text); break; case 'bio': setTempBio(text); break; case 'location': setTempLocation(text); break; case 'website': setTempWebsite(text); break; } }; return /*#__PURE__*/_jsx(View, { style: styles.editingFieldContainer, children: /*#__PURE__*/_jsx(View, { style: styles.editingFieldContent, children: /*#__PURE__*/_jsxs(View, { style: styles.newValueSection, children: [/*#__PURE__*/_jsx(Text, { style: styles.editingFieldLabel, children: `Enter ${config.label.toLowerCase()}:` }), /*#__PURE__*/_jsx(TextInput, { style: [config.multiline ? styles.editingFieldTextArea : styles.editingFieldInput, { backgroundColor: isDarkTheme ? '#333' : '#fff', color: isDarkTheme ? '#fff' : '#000', borderColor: primaryColor }], value: tempValue, onChangeText: setTempValue, placeholder: config.placeholder, placeholderTextColor: isDarkTheme ? '#aaa' : '#999', multiline: config.multiline, numberOfLines: config.multiline ? 6 : 1, keyboardType: config.keyboardType, autoFocus: true, selectionColor: primaryColor })] }) }) }); }; const renderField = (type, label, value, placeholder, icon, iconColor, multiline = false, keyboardType = 'default', isFirst = false, isLast = false) => { const itemStyles = [styles.settingItem, isFirst && styles.firstSettingItem, isLast && styles.lastSettingItem]; return /*#__PURE__*/_jsxs(TouchableOpacity, { style: itemStyles, onPress: () => startEditing(type, value), children: [/*#__PURE__*/_jsxs(View, { style: styles.settingInfo, children: [/*#__PURE__*/_jsx(OxyIcon, { name: icon, size: 20, color: iconColor, style: styles.settingIcon }), /*#__PURE__*/_jsxs(View, { children: [/*#__PURE__*/_jsx(Text, { style: styles.settingLabel, children: label }), /*#__PURE__*/_jsx(Text, { style: styles.settingDescription, children: value || placeholder })] })] }), /*#__PURE__*/_jsx(OxyIcon, { name: "chevron-forward", size: 16, color: "#ccc" })] }); }; if (authLoading || !user) { return /*#__PURE__*/_jsx(View, { style: [styles.container, { backgroundColor, justifyContent: 'center' }], children: /*#__PURE__*/_jsx(ActivityIndicator, { size: "large", color: primaryColor }) }); } return /*#__PURE__*/_jsxs(View, { style: [styles.container, { backgroundColor }], children: [/*#__PURE__*/_jsx(View, { style: styles.header, children: editingField ? /*#__PURE__*/_jsxs(View, { style: styles.editingHeader, children: [/*#__PURE__*/_jsxs(View, { style: styles.editingHeaderTop, children: [/*#__PURE__*/_jsx(TouchableOpacity, { style: styles.cancelButton, onPress: cancelEditing, children: /*#__PURE__*/_jsx(Ionicons, { name: "close", size: 24, color: "#666" }) }), /*#__PURE__*/_jsx(Animated.View, { style: { transform: [{ scale: saveButtonScale }] }, children: /*#__PURE__*/_jsx(TouchableOpacity, { style: [styles.saveHeaderButton, { opacity: isSaving ? 0.7 : 1, backgroundColor: editingField ? getFieldIcon(editingField).color : '#007AFF' }], onPress: () => saveField(editingField), disabled: isSaving, children: isSaving ? /*#__PURE__*/_jsx(ActivityIndicator, { size: "small", color: "#fff" }) : /*#__PURE__*/_jsx(Text, { style: styles.saveButtonText, children: "Save" }) }) })] }), /*#__PURE__*/_jsx(View, { style: styles.editingHeaderBottom, children: /*#__PURE__*/_jsxs(View, { style: styles.headerTitleWithIcon, children: [/*#__PURE__*/_jsx(OxyIcon, { name: getFieldIcon(editingField).name, size: 50, color: getFieldIcon(editingField).color, style: styles.headerIcon }), /*#__PURE__*/_jsx(Text, { style: styles.headerTitleLarge, children: getFieldLabel(editingField) })] }) })] }) : /*#__PURE__*/_jsxs(View, { style: styles.normalHeader, children: [/*#__PURE__*/_jsx(TouchableOpacity, { style: styles.cancelButton, onPress: onClose || goBack, children: /*#__PURE__*/_jsx(Ionicons, { name: "close", size: 24, color: "#666" }) }), /*#__PURE__*/_jsx(Text, { style: styles.headerTitle, children: "Account Settings" }), /*#__PURE__*/_jsx(Animated.View, { style: { transform: [{ scale: saveButtonScale }] }, children: /*#__PURE__*/_jsx(TouchableOpacity, { style: [styles.saveIconButton, { opacity: isSaving ? 0.7 : 1 }], onPress: handleSave, disabled: isSaving, children: isSaving ? /*#__PURE__*/_jsx(ActivityIndicator, { size: "small", color: primaryColor }) : /*#__PURE__*/_jsx(Ionicons, { name: "checkmark", size: 24, color: primaryColor }) }) })] }) }), /*#__PURE__*/_jsx(ScrollView, { style: editingField ? styles.contentEditing : styles.content, children: editingField ? /*#__PURE__*/ // Show only the editing interface when editing _jsx(View, { style: styles.editingOnlyContainer, children: renderEditingField(editingField) }) : /*#__PURE__*/ // Show all settings when not editing _jsxs(_Fragment, { children: [/*#__PURE__*/_jsxs(View, { style: styles.section, children: [/*#__PURE__*/_jsx(Text, { style: styles.sectionTitle, children: "Profile Picture" }), /*#__PURE__*/_jsxs(TouchableOpacity, { style: [styles.settingItem, styles.firstSettingItem, styles.lastSettingItem], onPress: handleAvatarUpdate, children: [/*#__PURE__*/_jsx(View, { style: styles.userIcon, children: /*#__PURE__*/_jsx(Avatar, { uri: avatarUrl, name: displayName || username, size: 50, theme: theme }) }), /*#__PURE__*/_jsx(View, { style: styles.settingInfo, children: /*#__PURE__*/_jsxs(View, { children: [/*#__PURE__*/_jsx(Text, { style: styles.settingLabel, children: "Profile Photo" }), /*#__PURE__*/_jsx(Text, { style: styles.settingDescription, children: avatarUrl ? 'Tap to change your profile picture' : 'Tap to add a profile picture' })] }) }), /*#__PURE__*/_jsx(OxyIcon, { name: "chevron-forward", size: 16, color: "#ccc" })] })] }), /*#__PURE__*/_jsxs(View, { style: styles.section, children: [/*#__PURE__*/_jsx(Text, { style: styles.sectionTitle, children: "Basic Information" }), renderField('displayName', 'Display Name', displayName, 'Add your display name', 'person', '#007AFF', false, 'default', true, false), renderField('username', 'Username', username, 'Choose a username', 'at', '#5856D6', false, 'default', false, false), renderField('email', 'Email', email, 'Add your email address', 'mail', '#FF9500', false, 'email-address', false, true)] }), /*#__PURE__*/_jsxs(View, { style: styles.section, children: [/*#__PURE__*/_jsx(Text, { style: styles.sectionTitle, children: "About You" }), renderField('bio', 'Bio', bio, 'Tell people about yourself', 'document-text', '#34C759', true, 'default', true, false), renderField('location', 'Location', location, 'Add your location', 'location', '#FF3B30', false, 'default', false, false), renderField('website', 'Website', website, 'Add your website', 'link', '#32D74B', false, 'url', false, true)] }), /*#__PURE__*/_jsxs(View, { style: styles.section, children: [/*#__PURE__*/_jsx(Text, { style: styles.sectionTitle, children: "Quick Actions" }), /*#__PURE__*/_jsxs(TouchableOpacity, { style: [styles.settingItem, styles.firstSettingItem], onPress: () => toast.info('Privacy settings coming soon!'), children: [/*#__PURE__*/_jsxs(View, { style: styles.settingInfo, children: [/*#__PURE__*/_jsx(OxyIcon, { name: "shield-checkmark", size: 20, color: "#8E8E93", style: styles.settingIcon }), /*#__PURE__*/_jsxs(View, { children: [/*#__PURE__*/_jsx(Text, { style: styles.settingLabel, children: "Privacy Settings" }), /*#__PURE__*/_jsx(Text, { style: styles.settingDescription, children: "Control who can see your profile" })] })] }), /*#__PURE__*/_jsx(OxyIcon, { name: "chevron-forward", size: 16, color: "#ccc" })] }), /*#__PURE__*/_jsxs(TouchableOpacity, { style: [styles.settingItem, styles.lastSettingItem], onPress: () => toast.info('Account verification coming soon!'), children: [/*#__PURE__*/_jsxs(View, { style: styles.settingInfo, children: [/*#__PURE__*/_jsx(OxyIcon, { name: "checkmark-circle", size: 20, color: "#30D158", style: styles.settingIcon }), /*#__PURE__*/_jsxs(View, { children: [/*#__PURE__*/_jsx(Text, { style: styles.settingLabel, children: "Verify Account" }), /*#__PURE__*/_jsx(Text, { style: styles.settingDescription, children: "Get a verified badge" })] })] }), /*#__PURE__*/_jsx(OxyIcon, { name: "chevron-forward", size: 16, color: "#ccc" })] })] })] }) })] }); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f2f2f2' }, header: { paddingHorizontal: 20, paddingVertical: 10, backgroundColor: '#fff', borderBottomWidth: 1, borderBottomColor: '#e0e0e0' }, normalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, editingHeader: { flexDirection: 'column' }, editingHeaderTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }, editingHeaderBottom: { flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-start' }, headerTitle: { fontSize: 24, fontWeight: 'bold', color: '#000', fontFamily: fontFamilies.phuduBold }, headerTitleWithIcon: { flexDirection: 'column', alignItems: 'flex-start', flex: 1, justifyContent: 'flex-start', maxWidth: '90%' }, headerTitleLarge: { fontSize: 48, fontWeight: '800', color: '#000', fontFamily: fontFamilies.phuduExtraBold, textAlign: 'left' }, headerIcon: { marginBottom: 2 }, cancelButton: { padding: 5 }, saveHeaderButton: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20, minWidth: 60, alignItems: 'center', justifyContent: 'center' }, saveIconButton: { padding: 5 }, saveButtonText: { color: '#fff', fontSize: 16, fontWeight: '600', fontFamily: fontFamilies.phuduSemiBold }, content: { flex: 1, padding: 16 }, contentEditing: { flex: 1, padding: 0 }, section: { marginBottom: 24 }, sectionTitle: { fontSize: 16, fontWeight: '600', color: '#333', marginBottom: 12, fontFamily: fontFamilies.phuduSemiBold }, settingItem: { backgroundColor: '#fff', padding: 16, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 2 }, firstSettingItem: { borderTopLeftRadius: 24, borderTopRightRadius: 24 }, lastSettingItem: { borderBottomLeftRadius: 24, borderBottomRightRadius: 24, marginBottom: 8 }, settingInfo: { flexDirection: 'row', alignItems: 'center', flex: 1 }, settingIcon: { marginRight: 12 }, settingLabel: { fontSize: 16, fontWeight: '500', color: '#333', marginBottom: 2 }, settingDescription: { fontSize: 14, color: '#666' }, userIcon: { marginRight: 12 }, // Inline editing styles editingContainer: { flex: 1 }, editingActions: { flexDirection: 'row', alignItems: 'center' }, editingButton: { padding: 8 }, editingButtonText: { fontSize: 16, fontWeight: '500' }, inlineInput: { backgroundColor: '#f8f8f8', borderWidth: 1, borderColor: '#e0e0e0', borderRadius: 8, padding: 12, fontSize: 16, minHeight: 44 }, inlineTextArea: { backgroundColor: '#f8f8f8', borderWidth: 1, borderColor: '#e0e0e0', borderRadius: 8, padding: 12, fontSize: 16, minHeight: 100, textAlignVertical: 'top' }, // Editing-only mode styles editingOnlyContainer: { flex: 1 }, editingFieldContainer: { backgroundColor: '#fff', padding: 16, flex: 1 }, editingFieldHeader: { marginBottom: 16 }, editingFieldTitleContainer: { flexDirection: 'row', alignItems: 'center' }, editingFieldIcon: { marginRight: 12 }, editingFieldTitle: { fontSize: 20, fontWeight: '600', color: '#000' }, editingFieldContent: { flex: 1 }, newValueSection: { flex: 1 }, editingFieldLabel: { fontSize: 16, fontWeight: '600', color: '#333', marginBottom: 12, fontFamily: fontFamilies.phuduSemiBold }, editingFieldInput: { backgroundColor: '#fff', borderWidth: 2, borderColor: '#e0e0e0', borderRadius: 12, padding: 16, fontSize: 17, minHeight: 52, fontWeight: '400' }, editingFieldTextArea: { backgroundColor: '#fff', borderWidth: 2, borderColor: '#e0e0e0', borderRadius: 12, padding: 16, fontSize: 17, minHeight: 120, textAlignVertical: 'top', fontWeight: '400' } }); export default AccountSettingsScreen; //# sourceMappingURL=AccountSettingsScreen.js.map