@oxyhq/services
Version:
411 lines (410 loc) • 14.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 _index = require("../styles/index.js");
var _Avatar = _interopRequireDefault(require("../components/Avatar.js"));
var _index2 = require("../components/index.js");
var _vectorIcons = require("@expo/vector-icons");
var _useI18n = require("../hooks/useI18n.js");
var _OxyContext = require("../context/OxyContext.js");
var _core = require("@oxyhq/core");
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 PAGE_SIZE = 20;
const UserListScreen = ({
userId,
mode,
initialCount,
theme,
goBack,
navigate
}) => {
const {
oxyServices,
user: currentUser
} = (0, _OxyContext.useOxy)();
const [users, setUsers] = (0, _react.useState)([]);
const [total, setTotal] = (0, _react.useState)(initialCount ?? 0);
const [isLoading, setIsLoading] = (0, _react.useState)(true);
const [isLoadingMore, setIsLoadingMore] = (0, _react.useState)(false);
const [isRefreshing, setIsRefreshing] = (0, _react.useState)(false);
const [error, setError] = (0, _react.useState)(null);
const [hasMore, setHasMore] = (0, _react.useState)(true);
const colors = (0, _index.useThemeColors)(theme ?? 'light');
const styles = createStyles(colors);
const {
t
} = (0, _useI18n.useI18n)();
const currentUserId = currentUser?.id || currentUser?._id;
const fetchUsers = (0, _react.useCallback)(async (offset = 0, isRefresh = false) => {
if (!userId) {
setError('No user ID provided');
setIsLoading(false);
return;
}
try {
if (isRefresh) {
setIsRefreshing(true);
} else if (offset === 0) {
setIsLoading(true);
} else {
setIsLoadingMore(true);
}
setError(null);
const response = mode === 'followers' ? await oxyServices.getUserFollowers(userId, {
limit: PAGE_SIZE,
offset
}) : await oxyServices.getUserFollowing(userId, {
limit: PAGE_SIZE,
offset
});
const newUsers = mode === 'followers' ? response.followers : response.following;
if (offset === 0 || isRefresh) {
setUsers(newUsers);
} else {
setUsers(prev => [...prev, ...newUsers]);
}
setTotal(response.total);
setHasMore(response.hasMore);
} catch (err) {
_core.logger.error(`Failed to fetch ${mode}`, err instanceof Error ? err : new Error(String(err)), {
component: 'UserListScreen'
});
setError(`Failed to load ${mode}. Please try again.`);
} finally {
setIsLoading(false);
setIsLoadingMore(false);
setIsRefreshing(false);
}
}, [userId, mode, oxyServices]);
(0, _react.useEffect)(() => {
fetchUsers(0);
}, [fetchUsers]);
const handleLoadMore = (0, _react.useCallback)(() => {
if (!isLoadingMore && hasMore && !isLoading) {
fetchUsers(users.length);
}
}, [isLoadingMore, hasMore, isLoading, users.length, fetchUsers]);
const handleRefresh = (0, _react.useCallback)(() => {
fetchUsers(0, true);
}, [fetchUsers]);
const handleUserPress = (0, _react.useCallback)(user => {
const targetUserId = user.id || user._id;
if (targetUserId && navigate) {
navigate('Profile', {
userId: targetUserId
});
}
}, [navigate]);
const renderUser = (0, _react.useCallback)(({
item
}) => {
const itemUserId = item.id || item._id || '';
const isCurrentUser = itemUserId === currentUserId;
const description = typeof item.description === 'string' ? item.description : '';
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
style: styles.userItem,
onPress: () => handleUserPress(item),
activeOpacity: 0.7,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_Avatar.default, {
uri: item.avatar ? oxyServices.getFileDownloadUrl(item.avatar, 'thumb') : undefined,
name: item.username || item.name?.full,
size: 48
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.userInfo,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.userName,
numberOfLines: 1,
children: item.name?.full || item.username || 'Unknown User'
}), item.username && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
style: styles.userHandle,
numberOfLines: 1,
children: ["@", item.username]
}), description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.userBio,
numberOfLines: 2,
children: description
}) : null]
}), !isCurrentUser && itemUserId ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.followButtonWrapper,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.FollowButton, {
userId: itemUserId,
size: "small"
})
}) : null]
});
}, [colors, styles, handleUserPress, currentUserId, oxyServices]);
const renderEmpty = (0, _react.useCallback)(() => {
if (isLoading) return null;
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.emptyContainer,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: mode === 'followers' ? 'people-outline' : 'heart-outline',
size: 64,
color: colors.secondaryText
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.emptyTitle,
children: mode === 'followers' ? t('userList.noFollowers') || 'No followers yet' : t('userList.noFollowing') || 'Not following anyone'
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.emptySubtitle,
children: mode === 'followers' ? t('userList.noFollowersDesc') || 'When people follow this user, they will appear here.' : t('userList.noFollowingDesc') || 'When this user follows people, they will appear here.'
})]
});
}, [isLoading, mode, colors, styles, t]);
const renderFooter = (0, _react.useCallback)(() => {
if (!isLoadingMore) return null;
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.footerLoader,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
size: "small",
color: colors.primary
})
});
}, [isLoadingMore, colors, styles]);
const title = mode === 'followers' ? t('userList.followers') || 'Followers' : t('userList.following') || 'Following';
if (isLoading && users.length === 0) {
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.container,
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.header,
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: colors.text
})
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.headerTitle,
children: title
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.headerRight
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.loadingContainer,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
size: "large",
color: colors.primary
})
})]
});
}
if (error) {
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.container,
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.header,
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: colors.text
})
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.headerTitle,
children: title
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.headerRight
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.errorContainer,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_vectorIcons.Ionicons, {
name: "alert-circle",
size: 48,
color: colors.error
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.errorText,
children: error
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
style: styles.retryButton,
onPress: () => fetchUsers(0),
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.retryButtonText,
children: t('common.retry') || 'Retry'
})
})]
})]
});
}
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.container,
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.header,
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: colors.text
})
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
style: styles.headerTitleContainer,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.headerTitle,
children: title
}), total > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
style: styles.headerCount,
children: total
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.headerRight
})]
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
data: users,
renderItem: renderUser,
keyExtractor: (item, index) => item.id || item._id || `user-${index}`,
contentContainerStyle: styles.listContent,
ItemSeparatorComponent: () => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
style: styles.separator
}),
ListEmptyComponent: renderEmpty,
ListFooterComponent: renderFooter,
onEndReached: handleLoadMore,
onEndReachedThreshold: 0.3,
refreshControl: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.RefreshControl, {
refreshing: isRefreshing,
onRefresh: handleRefresh,
tintColor: colors.primary,
colors: [colors.primary]
})
})]
});
};
const createStyles = colors => _reactNative.StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background
},
header: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: colors.border
},
backButton: {
padding: 8,
marginRight: 8
},
headerTitleContainer: {
flex: 1,
flexDirection: 'row',
alignItems: 'center'
},
headerTitle: {
fontSize: 18,
fontWeight: '600',
color: colors.text
},
headerCount: {
fontSize: 16,
color: colors.secondaryText,
marginLeft: 8
},
headerRight: {
width: 40
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
errorContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 32
},
errorText: {
fontSize: 16,
color: colors.error,
textAlign: 'center',
marginTop: 16,
marginBottom: 24
},
retryButton: {
backgroundColor: colors.primary,
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 8
},
retryButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600'
},
listContent: {
flexGrow: 1
},
userItem: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 12
},
userInfo: {
flex: 1,
marginLeft: 12,
marginRight: 8
},
userName: {
fontSize: 16,
fontWeight: '600',
color: colors.text
},
userHandle: {
fontSize: 14,
color: colors.secondaryText,
marginTop: 2
},
userBio: {
fontSize: 14,
color: colors.text,
marginTop: 4,
opacity: 0.8
},
followButtonWrapper: {
marginLeft: 'auto'
},
separator: {
height: 1,
backgroundColor: colors.border,
marginLeft: 76
},
emptyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 32,
paddingTop: 80
},
emptyTitle: {
fontSize: 18,
fontWeight: '600',
color: colors.text,
marginTop: 16,
textAlign: 'center'
},
emptySubtitle: {
fontSize: 14,
color: colors.secondaryText,
marginTop: 8,
textAlign: 'center'
},
footerLoader: {
paddingVertical: 16,
alignItems: 'center'
}
});
var _default = exports.default = UserListScreen;
//# sourceMappingURL=UserListScreen.js.map