@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
307 lines (297 loc) • 10.2 kB
JavaScript
"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 _styles = require("../styles");
var _vectorIcons = require("@expo/vector-icons");
var _sonner = require("../../lib/sonner");
var _components = require("../components");
var _useI18n = require("../hooks/useI18n");
var _languageUtils = require("../../utils/languageUtils");
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); }
/**
* LanguageSelectorScreen - Optimized for performance
*
* Performance optimizations:
* - useMemo for language items to prevent recreation on every render
* - useCallback for handlers to prevent unnecessary re-renders
* - Memoized current language section
*/
const LanguageSelectorScreen = ({
goBack,
onClose,
theme,
navigate
}) => {
const {
user,
currentLanguage,
setLanguage,
oxyServices,
isAuthenticated
} = (0, _OxyContext.useOxy)();
const {
t
} = (0, _useI18n.useI18n)();
const colors = (0, _styles.useThemeColors)(theme);
const [isLoading, setIsLoading] = (0, _react.useState)(false);
// Memoize the language select handler to prevent recreation on every render
const handleLanguageSelect = (0, _react.useCallback)(async languageId => {
if (languageId === currentLanguage || isLoading) {
return; // Already selected or loading
}
setIsLoading(true);
try {
let serverSyncFailed = false;
// If signed in, persist preference to backend user settings
if (isAuthenticated && user?.id) {
try {
await oxyServices.updateProfile({
language: languageId
});
} catch (e) {
// Server sync failed, but we'll save locally anyway
serverSyncFailed = true;
if (__DEV__) {
console.warn('Failed to sync language to server (will save locally only):', e?.message || e);
}
}
}
// Always persist locally for immediate UX and for guests
await setLanguage(languageId);
const selectedLang = _languageUtils.SUPPORTED_LANGUAGES.find(lang => lang.id === languageId);
// Show success message (language is saved locally regardless of server sync)
_sonner.toast.success(t('language.changed', {
lang: selectedLang?.name || languageId
}));
// Log server sync failure only in dev mode (user experience is still good - saved locally)
if (serverSyncFailed && __DEV__) {
console.warn('Language saved locally but server sync failed');
}
setIsLoading(false);
// Close the bottom sheet if possible; otherwise, go back
if (onClose) onClose();else goBack();
} catch (error) {
// Only show error if local storage also failed
console.error('Error saving language preference:', error);
_sonner.toast.error('Failed to save language preference');
setIsLoading(false);
}
}, [currentLanguage, isLoading, isAuthenticated, user?.id, oxyServices, setLanguage, t, onClose, goBack]);
// Memoize language items to prevent recreation on every render
const languageItems = (0, _react.useMemo)(() => _languageUtils.SUPPORTED_LANGUAGES.map(language => ({
id: language.id,
title: language.name,
subtitle: language.nativeName,
customIcon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.languageFlag, {
backgroundColor: `${language.color}20`
}],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.flagEmoji,
children: language.flag
})
}),
iconColor: language.color,
selected: currentLanguage === language.id,
onPress: () => handleLanguageSelect(language.id),
dense: true
})), [currentLanguage, handleLanguageSelect]);
// Memoize current language data to prevent recalculation
const currentLanguageData = (0, _react.useMemo)(() => {
if (!currentLanguage) return null;
return _languageUtils.SUPPORTED_LANGUAGES.find(lang => lang.id === currentLanguage);
}, [currentLanguage]);
// Memoize current language section items
const currentLanguageItems = (0, _react.useMemo)(() => {
if (!currentLanguageData) return [];
return [{
id: `current-${currentLanguageData.id}`,
title: currentLanguageData.name,
subtitle: currentLanguageData.nativeName,
customIcon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.languageFlag, {
backgroundColor: `${currentLanguageData.color}20`
}],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.flagEmoji,
children: currentLanguageData.flag
})
}),
iconColor: currentLanguageData.color,
selected: false,
showChevron: false,
dense: true,
disabled: true
}];
}, [currentLanguageData]);
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.container, {
backgroundColor: '#f2f2f2'
}],
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Header, {
title: t('language.title'),
subtitle: t('language.subtitle'),
theme: theme,
onBack: onClose || goBack,
variant: "minimal",
elevation: "subtle"
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
style: styles.content,
showsVerticalScrollIndicator: false,
removeClippedSubviews: true,
children: [currentLanguage && currentLanguageItems.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Section, {
title: t('language.current'),
theme: theme,
isFirst: true,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.GroupedSection, {
items: currentLanguageItems,
theme: theme
})
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.Section, {
title: t('language.available'),
theme: theme,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.sectionDescription, {
color: colors.secondaryText
}],
children: t('language.subtitle')
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.languageList,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.GroupedSection, {
items: languageItems,
theme: theme
})
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Section, {
theme: theme,
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.infoCard, {
backgroundColor: colors.inputBackground,
borderColor: colors.border
}],
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.infoHeader,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "information-circle",
size: 20,
color: colors.primary
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.infoTitle, {
color: colors.text
}],
children: "Language Settings"
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
style: [styles.infoText, {
color: colors.secondaryText
}],
children: ["\u2022 Language changes apply immediately", '\n', "\u2022 Your preference is saved automatically", '\n', "\u2022 All text and interface elements will update", '\n', "\u2022 You can change this setting anytime"]
})]
})
})]
})]
});
};
const styles = _reactNative.StyleSheet.create({
container: {
flex: 1
},
content: {
flex: 1,
padding: 16
},
section: {
marginBottom: 24
},
sectionTitle: {
fontSize: 16,
fontWeight: '600',
marginBottom: 4
},
sectionDescription: {
fontSize: 14,
lineHeight: 20,
marginBottom: 16
},
languageList: {
marginTop: 8
},
languageFlag: {
width: 38,
height: 38,
borderRadius: 19,
alignItems: 'center',
justifyContent: 'center'
},
flagEmoji: {
fontSize: 18
},
infoSection: {
marginBottom: 24
},
infoCard: {
padding: 16,
borderRadius: 12,
borderWidth: 1
},
infoHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8
},
infoTitle: {
fontSize: 16,
fontWeight: '600',
marginLeft: 8
},
infoText: {
fontSize: 14,
lineHeight: 20
},
currentSection: {
marginBottom: 24
},
currentLabel: {
fontSize: 14,
fontWeight: '500',
marginBottom: 8
},
currentLanguage: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
borderRadius: 12,
borderWidth: 2
},
currentFlag: {
width: 38,
height: 38,
borderRadius: 19,
alignItems: 'center',
justifyContent: 'center',
marginRight: 12
},
currentFlagEmoji: {
fontSize: 18
},
currentInfo: {
flex: 1
},
currentName: {
fontSize: 16,
fontWeight: '600'
},
currentNative: {
fontSize: 14,
marginTop: 2
}
});
// Export memoized component to prevent unnecessary re-renders
var _default = exports.default = /*#__PURE__*/_react.default.memo(LanguageSelectorScreen);
//# sourceMappingURL=LanguageSelectorScreen.js.map