freshyo/apps/fallback-ui/src/routes/user-connect.tsx
2026-03-04 01:12:43 +05:30

302 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useCallback } from 'react'
import { trpc } from '@/trpc/client'
export function UserConnectRoute() {
const [search, setSearch] = useState('')
const [searchInput, setSearchInput] = useState('')
const [currentPage, setCurrentPage] = useState(1)
const [visitedCursors, setVisitedCursors] = useState<(number | undefined)[]>([undefined])
const [itemsPerPage, setItemsPerPage] = useState(50)
const cursor = visitedCursors[currentPage - 1]
const { data, isLoading, error } = trpc.admin.user.getAllUsers.useQuery({
limit: itemsPerPage,
cursor,
search,
})
const handleSearch = () => {
setSearch(searchInput)
setCurrentPage(1)
setVisitedCursors([undefined])
}
const handleNextPage = () => {
if (data?.nextCursor) {
const newCursors = [...visitedCursors]
newCursors[currentPage] = data.nextCursor
setVisitedCursors(newCursors)
setCurrentPage(currentPage + 1)
}
}
const handlePrevPage = () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1)
}
}
const handlePageSelect = (page: number) => {
setCurrentPage(page)
}
const handleClearSearch = () => {
setSearch('')
setSearchInput('')
setCurrentPage(1)
setVisitedCursors([undefined])
}
const handleItemsPerPageChange = (newLimit: number) => {
setItemsPerPage(newLimit)
setCurrentPage(1)
setVisitedCursors([undefined])
}
const totalPages = Math.min(currentPage + (data?.hasMore ? 3 : 0), 20)
const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1)
if (isLoading) {
return (
<div className="max-w-6xl mx-auto">
<div className="bg-white rounded-lg shadow-md p-6">
<p className="text-gray-600 text-center">Loading users...</p>
</div>
</div>
)
}
if (error) {
return (
<div className="max-w-6xl mx-auto">
<div className="bg-white rounded-lg shadow-md p-6">
<p className="text-red-600 text-center">Error loading users: {error.message}</p>
</div>
</div>
)
}
return (
<div className="max-w-6xl mx-auto space-y-6">
<div className="bg-white rounded-lg shadow-md p-6">
<h1 className="text-3xl font-bold text-gray-900 mb-2">User Connect</h1>
<p className="text-gray-600">View all registered users and their contact information</p>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex gap-2 mb-4">
<input
type="text"
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
placeholder="Search by mobile number..."
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSearch()
}
}}
/>
<button
onClick={handleSearch}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
Search
</button>
{search && (
<button
onClick={handleClearSearch}
className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors"
>
Clear
</button>
)}
</div>
<div className="flex justify-between items-center mb-4 pb-4 border-b border-gray-200">
<div className="flex items-center gap-4">
<p className="text-sm text-gray-600">
Page {currentPage} Showing {data?.users.length || 0} users
</p>
<div className="flex items-center gap-2">
<label className="text-sm text-gray-600">Per page:</label>
<select
value={itemsPerPage}
onChange={(e) => handleItemsPerPageChange(Number(e.target.value))}
className="px-2 py-1 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value={10}>10</option>
<option value={25}>25</option>
<option value={50}>50</option>
<option value={100}>100</option>
</select>
</div>
</div>
<div className="flex items-center gap-2">
{pageNumbers.length > 1 && (
<>
<button
onClick={handlePrevPage}
disabled={currentPage === 1}
className={`px-3 py-1.5 rounded-md transition-colors ${
currentPage === 1
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
</button>
{pageNumbers.map((page) => (
<button
key={page}
onClick={() => handlePageSelect(page)}
className={`px-3 py-1.5 rounded-md transition-colors ${
currentPage === page
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
{page}
</button>
))}
{data?.hasMore && currentPage >= totalPages - 2 && (
<span className="px-2 text-gray-400">...</span>
)}
<button
onClick={handleNextPage}
disabled={!data?.hasMore}
className={`px-3 py-1.5 rounded-md transition-colors ${
!data?.hasMore
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
</button>
</>
)}
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-gray-200">
<th className="text-left py-3 px-4 font-semibold text-gray-700">ID</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700">Name</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700">Mobile</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700">Total Orders</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700">Last Order</th>
<th className="text-left py-3 px-4 font-semibold text-gray-700">Status</th>
</tr>
</thead>
<tbody>
{data?.users.map((user) => (
<tr key={user.id} className="border-b border-gray-100 hover:bg-gray-50">
<td className="py-3 px-4 text-gray-600">{user.id}</td>
<td className="py-3 px-4 text-gray-900">{user.name || '-'}</td>
<td className="py-3 px-4">
{user.mobile ? (
<a
href={`https://wa.me/91${user.mobile.replace(/\D/g, '')}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 font-mono hover:underline"
>
{user.mobile}
</a>
) : (
<span className="text-gray-400">-</span>
)}
</td>
<td className="py-3 px-4 text-gray-600">{user.totalOrders}</td>
<td className="py-3 px-4 text-gray-600">
{user.lastOrderDate
? new Date(user.lastOrderDate).toLocaleDateString()
: '-'}
</td>
<td className="py-3 px-4">
{user.isSuspended ? (
<span className="px-2 py-1 bg-red-100 text-red-700 text-xs font-medium rounded">
Suspended
</span>
) : (
<span className="px-2 py-1 bg-green-100 text-green-700 text-xs font-medium rounded">
Active
</span>
)}
</td>
</tr>
))}
</tbody>
</table>
{data?.users.length === 0 && (
<p className="text-center text-gray-500 py-8">No users found</p>
)}
</div>
<div className="flex justify-between items-center mt-4 pt-4 border-t border-gray-200">
<div className="flex items-center gap-4">
<p className="text-sm text-gray-600">
Page {currentPage} Showing {data?.users.length || 0} users
</p>
</div>
<div className="flex items-center gap-2">
{pageNumbers.length > 1 && (
<>
<button
onClick={handlePrevPage}
disabled={currentPage === 1}
className={`px-3 py-1.5 rounded-md transition-colors ${
currentPage === 1
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
</button>
{pageNumbers.map((page) => (
<button
key={page}
onClick={() => handlePageSelect(page)}
className={`px-3 py-1.5 rounded-md transition-colors ${
currentPage === page
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
{page}
</button>
))}
{data?.hasMore && currentPage >= totalPages - 2 && (
<span className="px-2 text-gray-400">...</span>
)}
<button
onClick={handleNextPage}
disabled={!data?.hasMore}
className={`px-3 py-1.5 rounded-md transition-colors ${
!data?.hasMore
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
</button>
</>
)}
</div>
</div>
</div>
</div>
)
}