@oxyhq/services
Version:
Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀
450 lines (447 loc) • 15.3 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 _Avatar = _interopRequireDefault(require("../components/Avatar"));
var _vectorIcons = require("@expo/vector-icons");
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 ProfileScreen = ({
userId,
username,
theme,
goBack
}) => {
const {
oxyServices,
user: currentUser
} = (0, _OxyContext.useOxy)();
const [profile, setProfile] = (0, _react.useState)(null);
const [karmaTotal, setKarmaTotal] = (0, _react.useState)(null);
const [postsCount, setPostsCount] = (0, _react.useState)(null);
const [commentsCount, setCommentsCount] = (0, _react.useState)(null);
const [followersCount, setFollowersCount] = (0, _react.useState)(null);
const [followingCount, setFollowingCount] = (0, _react.useState)(null);
const [isLoading, setIsLoading] = (0, _react.useState)(true);
const [error, setError] = (0, _react.useState)(null);
const isDarkTheme = theme === 'dark';
const backgroundColor = isDarkTheme ? '#121212' : '#FFFFFF';
const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
const primaryColor = '#d169e5';
(0, _react.useEffect)(() => {
console.log('ProfileScreen - userId:', userId);
console.log('ProfileScreen - username:', username);
if (!userId) {
setError('No user ID provided');
setIsLoading(false);
return;
}
setIsLoading(true);
setError(null);
// Load user profile and karma total
Promise.all([oxyServices.getUserById(userId).catch(err => {
console.error('getUserById error:', err);
// If this is the current user and the API call fails, use current user data as fallback
if (currentUser && currentUser.id === userId) {
console.log('API call failed, using current user as fallback:', currentUser);
return currentUser;
}
throw err;
}), oxyServices.getUserKarmaTotal ? oxyServices.getUserKarmaTotal(userId).catch(err => {
console.warn('getUserKarmaTotal error:', err);
return {
total: undefined
};
}) : Promise.resolve({
total: undefined
})]).then(([profileRes, karmaRes]) => {
console.log('Profile loaded:', profileRes);
setProfile(profileRes);
setKarmaTotal(typeof karmaRes.total === 'number' ? karmaRes.total : null);
// Mock data for other stats
// In a real app, these would come from API endpoints
setPostsCount(Math.floor(Math.random() * 50));
setCommentsCount(Math.floor(Math.random() * 100));
setFollowersCount(Math.floor(Math.random() * 200));
setFollowingCount(Math.floor(Math.random() * 100));
}).catch(err => {
console.error('Profile loading error:', err);
// Provide user-friendly error messages based on the error type
let errorMessage = 'Failed to load profile';
if (err.status === 404 || err.message?.includes('not found') || err.message?.includes('Resource not found')) {
if (currentUser && currentUser.id === userId) {
errorMessage = 'Unable to load your profile from the server. This may be due to a temporary service issue.';
} else {
errorMessage = 'This user profile could not be found or may have been removed.';
}
} else if (err.status === 403) {
errorMessage = 'You do not have permission to view this profile.';
} else if (err.status === 500) {
errorMessage = 'Server error occurred while loading the profile. Please try again later.';
} else if (err.message) {
errorMessage = err.message;
}
setError(errorMessage);
}).finally(() => setIsLoading(false));
}, [userId]);
if (isLoading) {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.container, {
backgroundColor,
justifyContent: 'center'
}],
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
size: "large",
color: primaryColor
})
});
}
if (error) {
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: [styles.container, {
backgroundColor
}],
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.errorHeader,
children: [goBack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
onPress: goBack,
style: styles.backButton,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "arrow-back",
size: 24,
color: textColor
})
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.errorTitle, {
color: textColor
}],
children: "Profile Error"
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.errorContent,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "alert-circle",
size: 48,
color: "#D32F2F",
style: styles.errorIcon
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.errorText, {
color: '#D32F2F'
}],
children: error
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.errorSubtext, {
color: textColor
}],
children: "This could happen if the user doesn't exist or the profile service is unavailable."
})]
})]
});
}
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: [styles.container, {
backgroundColor
}],
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
contentContainerStyle: styles.scrollContainer,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.bannerContainer,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.bannerImage
})
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.avatarRow,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.avatarWrapper,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Avatar.default, {
uri: profile?.avatar?.url,
name: profile?.username || username,
size: 96,
theme: theme
})
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.actionButtonWrapper,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.actionButton,
children: "Edit Profile"
})
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.header,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.displayName, {
color: textColor
}],
children: profile?.displayName || profile?.username || username || profile?.id
}), profile?.username && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
style: [styles.subText, {
color: isDarkTheme ? '#BBBBBB' : '#888888'
}],
children: ["@", profile.username]
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.bio, {
color: textColor
}],
children: profile?.bio || 'This user has no bio yet.'
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.infoRow,
children: [profile?.email && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.infoItem,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "mail-outline",
size: 16,
color: isDarkTheme ? '#BBBBBB' : '#888888',
style: {
marginRight: 4
}
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.infoText, {
color: isDarkTheme ? '#BBBBBB' : '#888888'
}],
children: profile.email
})]
}), profile?.createdAt && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.infoItem,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "calendar-outline",
size: 16,
color: isDarkTheme ? '#BBBBBB' : '#888888',
style: {
marginRight: 4
}
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
style: [styles.infoText, {
color: isDarkTheme ? '#BBBBBB' : '#888888'
}],
children: ["Joined ", new Date(profile.createdAt).toLocaleDateString()]
})]
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.divider
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.statsRow,
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.statItem,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.karmaAmount, {
color: primaryColor
}],
children: karmaTotal !== null && karmaTotal !== undefined ? karmaTotal : '--'
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.karmaLabel, {
color: isDarkTheme ? '#BBBBBB' : '#888888'
}],
children: "Karma"
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.statItem,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.karmaAmount, {
color: textColor
}],
children: followersCount !== null ? followersCount : '--'
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.karmaLabel, {
color: isDarkTheme ? '#BBBBBB' : '#888888'
}],
children: "Followers"
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.statItem,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.karmaAmount, {
color: textColor
}],
children: followingCount !== null ? followingCount : '--'
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: [styles.karmaLabel, {
color: isDarkTheme ? '#BBBBBB' : '#888888'
}],
children: "Following"
})]
})]
})]
})]
})
});
};
const styles = _reactNative.StyleSheet.create({
container: {
flex: 1
},
scrollContainer: {
alignItems: 'stretch',
paddingBottom: 40
},
bannerContainer: {
height: 160,
backgroundColor: '#e1bee7',
position: 'relative',
overflow: 'hidden'
},
bannerImage: {
flex: 1,
backgroundColor: '#d169e5'
},
// Placeholder, replace with Image if available
avatarRow: {
flexDirection: 'row',
alignItems: 'flex-end',
marginTop: -56,
paddingHorizontal: 20,
justifyContent: 'space-between',
zIndex: 2
},
avatarWrapper: {
borderWidth: 5,
borderColor: '#fff',
borderRadius: 64,
overflow: 'hidden',
backgroundColor: '#fff'
},
actionButtonWrapper: {
flex: 1,
alignItems: 'flex-end',
justifyContent: 'flex-end'
},
actionButton: {
backgroundColor: '#fff',
color: '#d169e5',
borderWidth: 1,
borderColor: '#d169e5',
borderRadius: 24,
paddingVertical: 7,
paddingHorizontal: 22,
fontWeight: 'bold',
fontSize: 16,
marginBottom: 8,
elevation: 2,
shadowColor: '#d169e5',
shadowOffset: {
width: 0,
height: 1
},
shadowOpacity: 0.08,
shadowRadius: 2
},
header: {
alignItems: 'flex-start',
paddingTop: 18,
paddingBottom: 24,
width: '100%',
paddingHorizontal: 20
},
displayName: {
fontSize: 24,
fontWeight: 'bold',
marginTop: 10,
marginBottom: 2,
letterSpacing: 0.1
},
subText: {
fontSize: 16,
marginBottom: 2,
color: '#a0a0a0'
},
bio: {
fontSize: 16,
marginTop: 10,
marginBottom: 10,
color: '#666',
fontStyle: 'italic',
lineHeight: 22
},
infoRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 10,
flexWrap: 'wrap'
},
infoItem: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 28,
marginBottom: 4,
minWidth: 120
},
infoText: {
fontSize: 15
},
divider: {
height: 1,
backgroundColor: '#e0e0e0',
width: '100%',
marginVertical: 14
},
statsRow: {
width: '100%',
flex: 1,
flexDirection: 'row',
alignItems: 'center',
marginTop: 6,
marginBottom: 2,
justifyContent: 'space-between'
},
statItem: {
flex: 1,
alignItems: 'center',
minWidth: 50,
marginBottom: 12
},
karmaLabel: {
fontSize: 14,
marginBottom: 2,
textAlign: 'center',
color: '#a0a0a0'
},
karmaAmount: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
letterSpacing: 0.2
},
// Error handling styles
errorHeader: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: '#E0E0E0'
},
backButton: {
padding: 8,
marginRight: 16
},
errorTitle: {
fontSize: 20,
fontWeight: 'bold'
},
errorContent: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 32
},
errorIcon: {
marginBottom: 16
},
errorText: {
fontSize: 18,
fontWeight: '600',
textAlign: 'center',
marginBottom: 8
},
errorSubtext: {
fontSize: 14,
textAlign: 'center',
opacity: 0.7
}
});
var _default = exports.default = ProfileScreen;
//# sourceMappingURL=ProfileScreen.js.map