UNPKG

@oxyhq/services

Version:

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

836 lines (823 loc) • 27 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); var _OxyContext = require("../context/OxyContext"); var _Avatar = _interopRequireDefault(require("../components/Avatar")); var _OxyIcon = _interopRequireDefault(require("../components/icon/OxyIcon")); var _vectorIcons = require("@expo/vector-icons"); var _sonner = require("../../lib/sonner"); var _fonts = require("../styles/fonts"); var _jsxRuntime = require("react/jsx-runtime"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } const AccountSettingsScreen = ({ onClose, theme, goBack, navigate }) => { const { user, oxyServices, isLoading: authLoading } = (0, _OxyContext.useOxy)(); const [isLoading, setIsLoading] = (0, _react.useState)(false); const [isSaving, setIsSaving] = (0, _react.useState)(false); // Animation refs const saveButtonScale = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current; // Form state const [displayName, setDisplayName] = (0, _react.useState)(''); const [username, setUsername] = (0, _react.useState)(''); const [email, setEmail] = (0, _react.useState)(''); const [bio, setBio] = (0, _react.useState)(''); const [location, setLocation] = (0, _react.useState)(''); const [website, setWebsite] = (0, _react.useState)(''); const [avatarUrl, setAvatarUrl] = (0, _react.useState)(''); // Editing states const [editingField, setEditingField] = (0, _react.useState)(null); // Temporary input states for inline editing const [tempDisplayName, setTempDisplayName] = (0, _react.useState)(''); const [tempUsername, setTempUsername] = (0, _react.useState)(''); const [tempEmail, setTempEmail] = (0, _react.useState)(''); const [tempBio, setTempBio] = (0, _react.useState)(''); const [tempLocation, setTempLocation] = (0, _react.useState)(''); const [tempWebsite, setTempWebsite] = (0, _react.useState)(''); const isDarkTheme = theme === 'dark'; const backgroundColor = isDarkTheme ? '#121212' : '#f2f2f2'; const primaryColor = '#007AFF'; // Animation functions const animateSaveButton = toValue => { _reactNative.Animated.spring(saveButtonScale, { toValue, useNativeDriver: true, tension: 150, friction: 8 }).start(); }; // Load user data (0, _react.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); _sonner.toast.success('Profile updated successfully'); animateSaveButton(1); // Scale back to normal if (onClose) { onClose(); } else if (goBack) { goBack(); } } catch (error) { _sonner.toast.error(error.message || 'Failed to update profile'); animateSaveButton(1); // Scale back to normal on error } finally { setIsSaving(false); } }; const handleAvatarUpdate = () => { _reactNative.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__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.editingFieldContainer, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.editingFieldContent, children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.newValueSection, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.editingFieldLabel, children: `Enter ${config.label.toLowerCase()}:` }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.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__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, { style: itemStyles, onPress: () => startEditing(type, value), children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.settingInfo, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyIcon.default, { name: icon, size: 20, color: iconColor, style: styles.settingIcon }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.settingLabel, children: label }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.settingDescription, children: value || placeholder })] })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyIcon.default, { name: "chevron-forward", size: 16, color: "#ccc" })] }); }; if (authLoading || !user) { return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: [styles.container, { backgroundColor, justifyContent: 'center' }], children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, { size: "large", color: primaryColor }) }); } return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: [styles.container, { backgroundColor }], children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.header, children: editingField ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.editingHeader, children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.editingHeaderTop, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: styles.cancelButton, onPress: cancelEditing, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, { name: "close", size: 24, color: "#666" }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, { style: { transform: [{ scale: saveButtonScale }] }, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: [styles.saveHeaderButton, { opacity: isSaving ? 0.7 : 1, backgroundColor: editingField ? getFieldIcon(editingField).color : '#007AFF' }], onPress: () => saveField(editingField), disabled: isSaving, children: isSaving ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, { size: "small", color: "#fff" }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.saveButtonText, children: "Save" }) }) })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.editingHeaderBottom, children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.headerTitleWithIcon, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyIcon.default, { name: getFieldIcon(editingField).name, size: 50, color: getFieldIcon(editingField).color, style: styles.headerIcon }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.headerTitleLarge, children: getFieldLabel(editingField) })] }) })] }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.normalHeader, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: styles.cancelButton, onPress: onClose || goBack, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, { name: "close", size: 24, color: "#666" }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.headerTitle, children: "Account Settings" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, { style: { transform: [{ scale: saveButtonScale }] }, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: [styles.saveIconButton, { opacity: isSaving ? 0.7 : 1 }], onPress: handleSave, disabled: isSaving, children: isSaving ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, { size: "small", color: primaryColor }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, { name: "checkmark", size: 24, color: primaryColor }) }) })] }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, { style: editingField ? styles.contentEditing : styles.content, children: editingField ? /*#__PURE__*/ // Show only the editing interface when editing (0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.editingOnlyContainer, children: renderEditingField(editingField) }) : /*#__PURE__*/ // Show all settings when not editing (0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.section, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.sectionTitle, children: "Profile Picture" }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, { style: [styles.settingItem, styles.firstSettingItem, styles.lastSettingItem], onPress: handleAvatarUpdate, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.userIcon, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Avatar.default, { uri: avatarUrl, name: displayName || username, size: 50, theme: theme }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: styles.settingInfo, children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.settingLabel, children: "Profile Photo" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.settingDescription, children: avatarUrl ? 'Tap to change your profile picture' : 'Tap to add a profile picture' })] }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyIcon.default, { name: "chevron-forward", size: 16, color: "#ccc" })] })] }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.section, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.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__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.section, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.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__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.section, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.sectionTitle, children: "Quick Actions" }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, { style: [styles.settingItem, styles.firstSettingItem], onPress: () => _sonner.toast.info('Privacy settings coming soon!'), children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.settingInfo, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyIcon.default, { name: "shield-checkmark", size: 20, color: "#8E8E93", style: styles.settingIcon }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.settingLabel, children: "Privacy Settings" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.settingDescription, children: "Control who can see your profile" })] })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyIcon.default, { name: "chevron-forward", size: 16, color: "#ccc" })] }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, { style: [styles.settingItem, styles.lastSettingItem], onPress: () => _sonner.toast.info('Account verification coming soon!'), children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.settingInfo, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyIcon.default, { name: "checkmark-circle", size: 20, color: "#30D158", style: styles.settingIcon }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.settingLabel, children: "Verify Account" }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.settingDescription, children: "Get a verified badge" })] })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_OxyIcon.default, { name: "chevron-forward", size: 16, color: "#ccc" })] })] })] }) })] }); }; const styles = _reactNative.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: _fonts.fontFamilies.phuduBold }, headerTitleWithIcon: { flexDirection: 'column', alignItems: 'flex-start', flex: 1, justifyContent: 'flex-start', maxWidth: '90%' }, headerTitleLarge: { fontSize: 48, fontWeight: '800', color: '#000', fontFamily: _fonts.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: _fonts.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: _fonts.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: _fonts.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' } }); var _default = exports.default = AccountSettingsScreen; //# sourceMappingURL=AccountSettingsScreen.js.map