freshyo/apps/admin-ui/app/(drawer)/user-management/[id].tsx
2026-02-09 00:59:44 +05:30

254 lines
8.6 KiB
TypeScript

import React, { useCallback } from 'react';
import {
View,
TouchableOpacity,
ActivityIndicator,
ScrollView,
} from 'react-native';
import { useRouter, useLocalSearchParams } from 'expo-router';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import {
AppContainer,
MyText,
tw,
Checkbox,
} from 'common-ui';
import { trpc } from '@/src/trpc-client';
import { formatDistanceToNow } from 'date-fns';
import dayjs from 'dayjs';
interface Order {
id: number;
readableId: number;
totalAmount: string;
createdAt: string;
status: string;
isFlashDelivery: boolean;
itemCount: number;
}
interface OrderItemProps {
order: Order;
onPress: () => void;
}
const getStatusColor = (status: string) => {
switch (status) {
case 'delivered':
return 'text-green-600 bg-green-50 border-green-100';
case 'cancelled':
return 'text-red-600 bg-red-50 border-red-100';
default:
return 'text-yellow-600 bg-yellow-50 border-yellow-100';
}
};
const OrderItem: React.FC<OrderItemProps> = ({ order, onPress }) => {
const statusStyle = getStatusColor(order.status);
return (
<TouchableOpacity
onPress={onPress}
activeOpacity={0.7}
style={tw`bg-white rounded-xl border border-gray-100 p-4 mb-3 shadow-sm`}
>
<View style={tw`flex-row items-center justify-between mb-3`}>
<View style={tw`flex-row items-center`}>
<MyText style={tw`text-lg font-bold text-gray-900`}>
#{order.readableId}
</MyText>
{order.isFlashDelivery && (
<View style={tw`ml-2 px-2 py-0.5 bg-amber-100 rounded-full border border-amber-200`}>
<MyText style={tw`text-[10px] font-black text-amber-700 uppercase`}></MyText>
</View>
)}
</View>
<View style={tw`px-3 py-1 rounded-full border ${statusStyle}`}>
<MyText style={tw`text-xs font-bold uppercase tracking-wider ${statusStyle.split(' ')[0]}`}>
{order.status}
</MyText>
</View>
</View>
<View style={tw`flex-row justify-between items-center`}>
<View>
<MyText style={tw`text-gray-500 text-sm mb-1`}>
{dayjs(order.createdAt).format('MMM DD, YYYY • h:mm A')}
</MyText>
<MyText style={tw`text-gray-400 text-xs`}>
{order.itemCount} {order.itemCount === 1 ? 'item' : 'items'}
</MyText>
</View>
<MyText style={tw`text-xl font-bold text-blue-600`}>
{order.totalAmount}
</MyText>
</View>
<View style={tw`mt-3 pt-3 border-t border-gray-100 flex-row items-center justify-center`}>
<MyText style={tw`text-blue-600 font-medium text-sm`}>
View Order Details
</MyText>
<MaterialIcons name="chevron-right" size={18} color="#3b82f6" />
</View>
</TouchableOpacity>
);
};
export default function UserDetails() {
const router = useRouter();
const { id } = useLocalSearchParams<{ id: string }>();
const userId = id ? parseInt(id) : 0;
const { data, isLoading, error, refetch } = trpc.admin.user.getUserDetails.useQuery(
{ userId },
{ enabled: !!userId }
);
const updateSuspension = trpc.admin.user.updateUserSuspension.useMutation({
onSuccess: () => {
refetch();
},
});
const handleOrderPress = useCallback((orderId: number) => {
router.push(`/(drawer)/manage-orders/order-details/${orderId}`);
}, [router]);
const handleSuspensionToggle = useCallback((isSuspended: boolean) => {
updateSuspension.mutate({ userId, isSuspended });
}, [userId, updateSuspension]);
if (isLoading) {
return (
<AppContainer>
<View style={tw`flex-1 justify-center items-center`}>
<ActivityIndicator size="large" color="#3b82f6" />
<MyText style={tw`text-gray-500 mt-4`}>Loading user details...</MyText>
</View>
</AppContainer>
);
}
if (error || !data) {
return (
<AppContainer>
<View style={tw`flex-1 justify-center items-center p-8`}>
<MaterialIcons name="error-outline" size={64} color="#ef4444" />
<MyText style={tw`text-gray-900 text-lg font-bold mt-4`}>Error</MyText>
<MyText style={tw`text-gray-500 mt-2 text-center`}>
{error?.message || 'Failed to load user details'}
</MyText>
<TouchableOpacity
onPress={() => refetch()}
style={tw`mt-6 bg-blue-600 px-6 py-3 rounded-full`}
>
<MyText style={tw`text-white font-semibold`}>Retry</MyText>
</TouchableOpacity>
</View>
</AppContainer>
);
}
const { user, orders } = data;
const displayName = user.name || 'Unnamed User';
return (
<AppContainer>
<View style={tw`flex-1 bg-gray-50`}>
{/* Header */}
<View style={tw`bg-white px-4 py-4 border-b border-gray-200 flex-row items-center`}>
<TouchableOpacity
onPress={() => router.back()}
style={tw`p-2 -ml-4`}
>
<MaterialIcons name="chevron-left" size={24} color="#374151" />
</TouchableOpacity>
<MyText style={tw`text-xl font-bold text-gray-900 ml-2`}>User Details</MyText>
</View>
<ScrollView showsVerticalScrollIndicator={false}>
{/* User Info Card */}
<View style={tw`bg-white p-5 m-4 rounded-2xl shadow-sm border border-gray-100`}>
<View style={tw`flex-row items-center mb-4`}>
<View style={tw`w-12 h-12 bg-blue-50 rounded-full items-center justify-center mr-4`}>
<MaterialIcons name="person" size={24} color="#3B82F6" />
</View>
<View style={tw`flex-1`}>
<View style={tw`flex-row items-center`}>
<MyText style={tw`text-gray-900 font-bold text-lg mb-0.5`}>
{user.mobile || 'No Mobile'}
</MyText>
{user.isSuspended && (
<View style={tw`ml-2 px-2 py-0.5 bg-red-100 rounded-full border border-red-200`}>
<MyText style={tw`text-[10px] font-bold text-red-700 uppercase`}>Suspended</MyText>
</View>
)}
</View>
<MyText style={tw`text-gray-500`}>
{displayName}
</MyText>
</View>
</View>
<View style={tw`bg-gray-50 p-3 rounded-xl mb-4`}>
<View style={tw`flex-row items-center`}>
<MaterialIcons name="access-time" size={18} color="#6B7280" />
<MyText style={tw`ml-2 text-gray-600`}>
Registered {formatDistanceToNow(new Date(user.createdAt), { addSuffix: true })}
</MyText>
</View>
</View>
{/* Suspension Toggle */}
<View style={tw`flex-row items-center justify-between pt-4 border-t border-gray-100`}>
<View style={tw`flex-1`}>
<MyText style={tw`text-gray-900 font-bold text-base mb-1`}>
Suspend User
</MyText>
<MyText style={tw`text-gray-500 text-sm`}>
Prevent user from placing orders
</MyText>
</View>
{updateSuspension.isPending ? (
<ActivityIndicator size="small" color="#3b82f6" />
) : (
<Checkbox
checked={user.isSuspended}
onPress={() => handleSuspensionToggle(!user.isSuspended)}
size={28}
/>
)}
</View>
</View>
{/* Orders Section */}
<View style={tw`px-4 pb-8`}>
<View style={tw`flex-row items-center justify-between mb-4`}>
<MyText style={tw`text-lg font-bold text-gray-900`}>Order History</MyText>
<MyText style={tw`text-gray-500 text-sm`}>
{orders.length} {orders.length === 1 ? 'order' : 'orders'}
</MyText>
</View>
{orders.length === 0 ? (
<View style={tw`bg-white rounded-xl border border-gray-100 p-8 items-center`}>
<MaterialIcons name="shopping-bag" size={48} color="#e5e7eb" />
<MyText style={tw`text-gray-500 mt-4 text-center`}>
No orders yet
</MyText>
</View>
) : (
orders.map((order) => (
<OrderItem
key={order.id}
order={order}
onPress={() => handleOrderPress(order.id)}
/>
))
)}
</View>
</ScrollView>
</View>
</AppContainer>
);
}