UNPKG

@oxyhq/services

Version:

OxyHQ Expo/React Native SDK — UI components, screens, and native features

640 lines (625 loc) 22.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); var _vectorIcons = require("@expo/vector-icons"); var _useThemeStyles = require("../hooks/useThemeStyles.js"); var _useColorScheme = require("../hooks/useColorScheme.js"); var _themeUtils = require("../utils/themeUtils.js"); var _index = require("../components/index.js"); var _useI18n = require("../hooks/useI18n.js"); var _OxyContext = require("../context/OxyContext.js"); var _useProfileEditing = require("../hooks/useProfileEditing.js"); var _fonts = require("../styles/fonts.js"); var _sonner = require("../../lib/sonner"); var _core = require("@oxyhq/core"); var _jsxRuntime = require("react/jsx-runtime"); 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); } /** * Field types supported by EditProfileFieldScreen */ /** * Field configuration for each field type */ /** * EditProfileFieldScreen - A dedicated screen for editing profile fields * * Navigate to this screen with a fieldType prop to edit that specific field. * * @example * navigate('EditProfileField', { fieldType: 'username' }) */ const EditProfileFieldScreen = ({ goBack, onClose, theme, fieldType = 'displayName' }) => { const { user } = (0, _OxyContext.useOxy)(); const { t } = (0, _useI18n.useI18n)(); const { saveProfile, updateField, isSaving } = (0, _useProfileEditing.useProfileEditing)(); const colorScheme = (0, _useColorScheme.useColorScheme)(); const normalizedTheme = (0, _themeUtils.normalizeTheme)(theme); const themeStyles = (0, _useThemeStyles.useThemeStyles)(normalizedTheme, colorScheme); const colors = themeStyles.colors; // State for field values const [fieldValues, setFieldValues] = (0, _react.useState)({}); const [fieldErrors, setFieldErrors] = (0, _react.useState)({}); // State for list fields (locations, links) const [listItems, setListItems] = (0, _react.useState)([]); const [newItemValue, setNewItemValue] = (0, _react.useState)(''); // Get field configuration based on fieldType const fieldConfig = (0, _react.useMemo)(() => { switch (fieldType) { case 'displayName': return { title: t('editProfile.items.displayName.title') || 'Display Name', subtitle: t('editProfile.items.displayName.subtitle') || 'This is how your name will appear to others', fields: [{ key: 'displayName', label: t('editProfile.items.displayName.firstName') || 'First Name', placeholder: t('editProfile.items.displayName.firstNamePlaceholder') || 'Enter first name' }, { key: 'lastName', label: t('editProfile.items.displayName.lastName') || 'Last Name', placeholder: t('editProfile.items.displayName.lastNamePlaceholder') || 'Enter last name (optional)' }] }; case 'username': return { title: t('editProfile.items.username.title') || 'Username', subtitle: t('editProfile.items.username.subtitle') || 'Your unique identifier on the platform', fields: [{ key: 'username', label: t('editProfile.items.username.label') || 'Username', placeholder: t('editProfile.items.username.placeholder') || 'Choose a username', validation: value => { if (!value.trim()) { return t('editProfile.items.username.required') || 'Username is required'; } if (value.length < 3) { return t('editProfile.items.username.tooShort') || 'Username must be at least 3 characters'; } return undefined; }, inputProps: { autoCapitalize: 'none', autoCorrect: false } }] }; case 'email': return { title: t('editProfile.items.email.title') || 'Email', subtitle: t('editProfile.items.email.subtitle') || 'Your primary email address', fields: [{ key: 'email', label: t('editProfile.items.email.label') || 'Email Address', placeholder: t('editProfile.items.email.placeholder') || 'Enter your email address', type: 'email', validation: value => { if (!_core.EMAIL_REGEX.test(value)) { return t('editProfile.items.email.invalid') || 'Please enter a valid email address'; } return undefined; }, inputProps: { keyboardType: 'email-address', autoCapitalize: 'none', autoCorrect: false } }] }; case 'bio': return { title: t('editProfile.items.bio.title') || 'Bio', subtitle: t('editProfile.items.bio.subtitle') || 'Tell people a bit about yourself', fields: [{ key: 'bio', label: t('editProfile.items.bio.label') || 'Bio', placeholder: t('editProfile.items.bio.placeholder') || 'Tell people about yourself...', type: 'textarea', inputProps: { multiline: true, numberOfLines: 6, textAlignVertical: 'top' } }] }; case 'phone': return { title: t('editProfile.items.phone.title') || 'Phone Number', subtitle: t('editProfile.items.phone.subtitle') || 'Your contact phone number', fields: [{ key: 'phone', label: t('editProfile.items.phone.label') || 'Phone Number', placeholder: t('editProfile.items.phone.placeholder') || 'Enter your phone number', inputProps: { keyboardType: 'phone-pad', autoCapitalize: 'none', autoCorrect: false } }] }; case 'address': return { title: t('editProfile.items.address.title') || 'Address', subtitle: t('editProfile.items.address.subtitle') || 'Your physical address', fields: [{ key: 'address', label: t('editProfile.items.address.label') || 'Address', placeholder: t('editProfile.items.address.placeholder') || 'Enter your address', type: 'textarea', inputProps: { multiline: true, numberOfLines: 3, textAlignVertical: 'top' } }] }; case 'birthday': return { title: t('editProfile.items.birthday.title') || 'Birthday', subtitle: t('editProfile.items.birthday.subtitle') || 'Your date of birth', fields: [{ key: 'birthday', label: t('editProfile.items.birthday.label') || 'Birthday', placeholder: t('editProfile.items.birthday.placeholder') || 'YYYY-MM-DD', inputProps: { autoCapitalize: 'none', autoCorrect: false } }] }; case 'locations': return { title: t('editProfile.items.locations.title') || 'Locations', subtitle: t('editProfile.items.locations.subtitle') || 'Places you\'ve been or live', fields: [], isList: true }; case 'links': return { title: t('editProfile.items.links.title') || 'Links', subtitle: t('editProfile.items.links.subtitle') || 'Share your website, social profiles, etc.', fields: [], isList: true }; default: return { title: 'Edit Field', fields: [] }; } }, [fieldType, t]); // Initialize field values from user data (0, _react.useEffect)(() => { if (!user) return; // Cast user to any to access dynamic properties const userData = user; if (fieldConfig.isList) { if (fieldType === 'locations') { const locations = Array.isArray(userData.locations) ? userData.locations : []; setListItems(locations.map((loc, i) => ({ id: loc.id || `location-${i}`, name: loc.name || '', ...loc }))); } else if (fieldType === 'links') { const linksMetadata = Array.isArray(userData.linksMetadata) ? userData.linksMetadata : []; const links = Array.isArray(userData.links) ? userData.links : []; // Use linksMetadata if available, otherwise convert links array if (linksMetadata.length > 0) { setListItems(linksMetadata.map((link, i) => ({ id: link.id || `link-${i}`, url: link.url || link.link || '', title: link.title || '', ...link }))); } else { setListItems(links.map((item, i) => { const url = typeof item === 'string' ? item : item.link || item.url || ''; return { id: `link-${i}`, url, title: url.replace(/^https?:\/\//, '').replace(/\/$/, '') }; })); } } } else { const initialValues = {}; fieldConfig.fields.forEach(field => { if (field.key === 'displayName') { initialValues[field.key] = String(userData.displayName || userData.name?.first || ''); } else if (field.key === 'lastName') { initialValues[field.key] = String(userData.lastName || userData.name?.last || ''); } else if (field.key === 'birthday') { initialValues[field.key] = String(userData.birthday || userData.dateOfBirth || ''); } else if (field.key === 'address') { initialValues[field.key] = String(userData.address || userData.location || ''); } else { initialValues[field.key] = String(userData[field.key] || ''); } }); setFieldValues(initialValues); } }, [user, fieldConfig, fieldType]); // Field change handler const handleFieldChange = (0, _react.useCallback)((key, value) => { setFieldValues(prev => ({ ...prev, [key]: value })); if (fieldErrors[key]) { setFieldErrors(prev => ({ ...prev, [key]: undefined })); } }, [fieldErrors]); // Validate all fields const validateFields = (0, _react.useCallback)(() => { const errors = {}; let isValid = true; fieldConfig.fields.forEach(field => { if (field.validation) { const error = field.validation(fieldValues[field.key] || ''); if (error) { errors[field.key] = error; isValid = false; } } }); setFieldErrors(errors); return isValid; }, [fieldConfig.fields, fieldValues]); // Add item to list const handleAddItem = (0, _react.useCallback)(() => { if (!newItemValue.trim()) return; if (fieldType === 'locations') { const newItem = { id: `location-${Date.now()}`, name: newItemValue.trim() }; setListItems(prev => [...prev, newItem]); } else if (fieldType === 'links') { const newItem = { id: `link-${Date.now()}`, url: newItemValue.trim(), title: newItemValue.replace(/^https?:\/\//, '').replace(/\/$/, '') }; setListItems(prev => [...prev, newItem]); } setNewItemValue(''); }, [newItemValue, fieldType]); // Remove item from list const handleRemoveItem = (0, _react.useCallback)(id => { setListItems(prev => prev.filter(item => item.id !== id)); }, []); // Save handler const handleSave = async () => { if (fieldConfig.isList) { let success = false; if (fieldType === 'locations') { success = await saveProfile({ locations: listItems }); } else if (fieldType === 'links') { success = await saveProfile({ linksMetadata: listItems, links: listItems.map(item => item.url) }); } if (success) { _sonner.toast.success(t('common.saved') || 'Saved successfully'); (onClose || goBack)?.(); } } else { if (!validateFields()) return; let success = false; if (fieldType === 'displayName') { success = await saveProfile({ displayName: fieldValues.displayName, lastName: fieldValues.lastName }); } else { const key = fieldConfig.fields[0]?.key; if (key) { success = await updateField(key, fieldValues[key]); } } if (success) { _sonner.toast.success(t('common.saved') || 'Saved successfully'); (onClose || goBack)?.(); } } }; // Render a single field input const renderField = (field, index) => { const isTextarea = field.type === 'textarea'; return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.inputGroup, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.label, { color: colors.text }], children: field.label }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, { style: [isTextarea ? styles.textArea : styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: fieldErrors[field.key] ? '#FF3B30' : colors.border }], value: fieldValues[field.key] || '', onChangeText: value => handleFieldChange(field.key, value), placeholder: field.placeholder, placeholderTextColor: colors.secondaryText, autoFocus: index === 0, selectionColor: colors.tint, ...field.inputProps }), fieldErrors[field.key] && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: styles.errorText, children: fieldErrors[field.key] })] }, field.key); }; // Render list content (locations or links) const renderListContent = () => { const addLabel = fieldType === 'locations' ? t('editProfile.items.locations.add') || 'Add Location' : t('editProfile.items.links.add') || 'Add Link'; const placeholder = fieldType === 'locations' ? t('editProfile.items.locations.placeholder') || 'Enter location name' : t('editProfile.items.links.placeholder') || 'Enter URL'; const listTitle = fieldType === 'locations' ? t('editProfile.items.locations.yourLocations') || 'Your Locations' : t('editProfile.items.links.yourLinks') || 'Your Links'; return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.inputGroup, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.label, { color: colors.text }], children: addLabel }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.addItemRow, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, { style: [styles.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, flex: 1 }], value: newItemValue, onChangeText: setNewItemValue, placeholder: placeholder, placeholderTextColor: colors.secondaryText, selectionColor: colors.tint, autoCapitalize: "none", autoCorrect: false, onSubmitEditing: handleAddItem, returnKeyType: "done", keyboardType: fieldType === 'links' ? 'url' : 'default' }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { style: [styles.addButton, { backgroundColor: newItemValue.trim() ? colors.tint : colors.border }], onPress: handleAddItem, disabled: !newItemValue.trim(), children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, { name: "add", size: 20, color: "#fff" }) })] })] }), listItems.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.listSection, children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, { style: [styles.listTitle, { color: colors.text }], children: [listTitle, " (", listItems.length, ")"] }), listItems.map(item => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: [styles.listItem, { backgroundColor: colors.card, borderColor: colors.border }], children: [fieldType === 'links' && item.image && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, { source: { uri: item.image }, style: styles.linkImage }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.listItemContent, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.listItemTitle, { color: colors.text }], numberOfLines: 1, children: fieldType === 'locations' ? item.name : item.title || item.url }), fieldType === 'links' && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.listItemSubtitle, { color: colors.secondaryText }], numberOfLines: 1, children: item.url })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, { onPress: () => handleRemoveItem(item.id), style: styles.removeButton, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, { name: "trash-outline", size: 18, color: "#FF3B30" }) })] }, item.id))] })] }); }; return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.KeyboardAvoidingView, { style: [styles.container, { backgroundColor: normalizedTheme === 'dark' ? '#000000' : '#F5F5F5' }], behavior: _reactNative.Platform.OS === 'ios' ? 'padding' : undefined, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_index.Header, { title: "", subtitle: "", theme: normalizedTheme, onBack: onClose || goBack, variant: "minimal", elevation: "none", rightAction: { text: isSaving ? t('common.saving') || 'Saving...' : t('common.save') || 'Save', onPress: handleSave, disabled: isSaving, loading: isSaving } }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, { style: styles.content, contentContainerStyle: styles.contentContainer, showsVerticalScrollIndicator: false, keyboardShouldPersistTaps: "handled", children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, { style: styles.titleContainer, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.bigTitle, { color: colors.text }], children: fieldConfig.title }), fieldConfig.subtitle && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { style: [styles.bigSubtitle, { color: colors.secondaryText }], children: fieldConfig.subtitle })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { style: [styles.formCard, { backgroundColor: colors.card }], children: fieldConfig.isList ? renderListContent() : fieldConfig.fields.map(renderField) })] })] }); }; const styles = _reactNative.StyleSheet.create({ container: { flex: 1 }, content: { flex: 1 }, contentContainer: { padding: 16, paddingTop: 24, paddingBottom: 40 }, titleContainer: { marginBottom: 24 }, bigTitle: { fontSize: 34, fontWeight: _reactNative.Platform.OS === 'web' ? 'bold' : undefined, fontFamily: _fonts.fontFamilies.interBold, lineHeight: 40, marginBottom: 8, letterSpacing: -0.5 }, bigSubtitle: { fontSize: 16, lineHeight: 22, opacity: 0.7, marginTop: 4 }, formCard: { borderRadius: 16, padding: 16, gap: 16 }, inputGroup: { gap: 8 }, label: { fontSize: 14, fontWeight: '600', fontFamily: _fonts.fontFamilies.interSemiBold }, input: { borderWidth: _reactNative.StyleSheet.hairlineWidth, borderRadius: 12, padding: 16, fontSize: 16, minHeight: 52 }, textArea: { borderWidth: _reactNative.StyleSheet.hairlineWidth, borderRadius: 12, padding: 16, fontSize: 16, minHeight: 140 }, errorText: { fontSize: 12, color: '#FF3B30', marginTop: 4 }, addItemRow: { flexDirection: 'row', gap: 8, alignItems: 'center' }, addButton: { width: 52, height: 52, borderRadius: 12, alignItems: 'center', justifyContent: 'center' }, listSection: { marginTop: 8 }, listTitle: { fontSize: 16, fontWeight: '600', fontFamily: _fonts.fontFamilies.interSemiBold, marginBottom: 12 }, listItem: { flexDirection: 'row', alignItems: 'center', padding: 16, borderRadius: 12, borderWidth: _reactNative.StyleSheet.hairlineWidth, gap: 12, marginBottom: 8 }, listItemContent: { flex: 1, gap: 4 }, listItemTitle: { fontSize: 16, fontWeight: '500' }, listItemSubtitle: { fontSize: 13 }, linkImage: { width: 40, height: 40, borderRadius: 8 }, removeButton: { padding: 8 } }); var _default = exports.default = /*#__PURE__*/_react.default.memo(EditProfileFieldScreen); //# sourceMappingURL=EditProfileFieldScreen.js.map