enh
This commit is contained in:
parent
ed7318f9ee
commit
dc644aef7e
5 changed files with 226 additions and 11 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
ENV_MODE=PROD
|
ENV_MODE=PROD
|
||||||
DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
|
# DATABASE_URL=postgresql://postgres:meatfarmer_master_password@57.128.212.174:7447/meatfarmer #technocracy
|
||||||
# DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
|
DATABASE_URL=postgres://postgres:meatfarmer_master_password@5.223.55.14:7447/meatfarmer #hetzner
|
||||||
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
|
PHONE_PE_BASE_URL=https://api-preprod.phonepe.com/
|
||||||
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
|
PHONE_PE_CLIENT_ID=TEST-M23F2IGP34ZAR_25090
|
||||||
PHONE_PE_CLIENT_VERSION=1
|
PHONE_PE_CLIENT_VERSION=1
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { UserHomeRoute } from './routes/user-home'
|
||||||
import { SuperAdminRoute } from './routes/super-admin'
|
import { SuperAdminRoute } from './routes/super-admin'
|
||||||
import { CreateCouponRoute } from './routes/create-coupon'
|
import { CreateCouponRoute } from './routes/create-coupon'
|
||||||
import { LocationMarkerRoute } from './routes/location-marker'
|
import { LocationMarkerRoute } from './routes/location-marker'
|
||||||
|
import { UserConnectRoute } from './routes/user-connect'
|
||||||
import Inauguration from './routes/inauguration'
|
import Inauguration from './routes/inauguration'
|
||||||
import { AuthWrapper } from './components/AuthWrapper'
|
import { AuthWrapper } from './components/AuthWrapper'
|
||||||
import { SuperAdminGuard } from './components/SuperAdminGuard'
|
import { SuperAdminGuard } from './components/SuperAdminGuard'
|
||||||
|
|
@ -101,6 +102,18 @@ const createCouponRoute = new Route({
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const userConnectRoute = new Route({
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
path: '/user-connect',
|
||||||
|
component: () => (
|
||||||
|
<Suspense fallback={<p>Loading user connect…</p>}>
|
||||||
|
<AuthWrapper>
|
||||||
|
<UserConnectRoute />
|
||||||
|
</AuthWrapper>
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const locationMarkerRoute = new Route({
|
const locationMarkerRoute = new Route({
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
path: '/location-marker',
|
path: '/location-marker',
|
||||||
|
|
@ -118,6 +131,7 @@ const routeTree = rootRoute.addChildren([
|
||||||
userHomeRoute,
|
userHomeRoute,
|
||||||
superAdminRoute,
|
superAdminRoute,
|
||||||
createCouponRoute,
|
createCouponRoute,
|
||||||
|
userConnectRoute,
|
||||||
locationMarkerRoute,
|
locationMarkerRoute,
|
||||||
inaugurationRoute
|
inaugurationRoute
|
||||||
])
|
])
|
||||||
|
|
@ -139,7 +153,8 @@ declare module '@tanstack/react-router' {
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ to: '/', label: 'Dashboard', exact: true },
|
{ to: '/', label: 'Dashboard', exact: true },
|
||||||
{ to: '/vendor-order-list', label: 'Vendor Order List' }
|
{ to: '/vendor-order-list', label: 'Vendor Order List' },
|
||||||
|
{ to: '/user-connect', label: 'User Connect' }
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
function RootComponent() {
|
function RootComponent() {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,33 @@
|
||||||
|
import { useNavigate } from '@tanstack/react-router'
|
||||||
|
import { useUserStore } from '@/stores/userStore'
|
||||||
|
import { removeAuthToken } from '@/services/auth'
|
||||||
import { StaffUserForm } from '@/components/StaffUserForm';
|
import { StaffUserForm } from '@/components/StaffUserForm';
|
||||||
|
|
||||||
export function SuperAdminRoute() {
|
export function SuperAdminRoute() {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const clearUser = useUserStore((state) => state.clearUser)
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
await removeAuthToken()
|
||||||
|
clearUser()
|
||||||
|
navigate({ to: '/login' as any })
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-6xl mx-auto space-y-6">
|
<div className="max-w-6xl mx-auto space-y-6">
|
||||||
<div className="bg-white rounded-lg shadow-md p-6">
|
<div className="bg-white rounded-lg shadow-md p-6">
|
||||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">Super Admin Dashboard</h1>
|
<div className="flex justify-between items-start">
|
||||||
<p className="text-gray-600">Advanced system management and user administration</p>
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">Super Admin Dashboard</h1>
|
||||||
|
<p className="text-gray-600">Advanced system management and user administration</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleLogout}
|
||||||
|
className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
|
|
||||||
175
apps/fallback-ui/src/routes/user-connect.tsx
Normal file
175
apps/fallback-ui/src/routes/user-connect.tsx
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { trpc } from '@/trpc/client'
|
||||||
|
|
||||||
|
export function UserConnectRoute() {
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
|
const [searchInput, setSearchInput] = useState('')
|
||||||
|
const [cursor, setCursor] = useState<number | undefined>(undefined)
|
||||||
|
|
||||||
|
const { data, isLoading, error } = trpc.admin.user.getAllUsers.useQuery({
|
||||||
|
limit: 50,
|
||||||
|
cursor,
|
||||||
|
search,
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
setSearch(searchInput)
|
||||||
|
setCursor(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNextPage = () => {
|
||||||
|
if (data?.nextCursor) {
|
||||||
|
setCursor(data.nextCursor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePrevPage = () => {
|
||||||
|
setCursor(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
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={() => {
|
||||||
|
setSearch('')
|
||||||
|
setSearchInput('')
|
||||||
|
setCursor(undefined)
|
||||||
|
}}
|
||||||
|
className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors"
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</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">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Showing {data?.users.length || 0} users
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{cursor !== undefined && (
|
||||||
|
<button
|
||||||
|
onClick={handlePrevPage}
|
||||||
|
className="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors"
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{data?.hasMore && (
|
||||||
|
<button
|
||||||
|
onClick={handleNextPage}
|
||||||
|
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -26,18 +26,21 @@ function AdminDashboard() {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
<div className="bg-blue-50 p-4 rounded-lg">
|
<Link
|
||||||
|
to="/user-connect"
|
||||||
|
className="bg-blue-50 p-4 rounded-lg hover:bg-blue-100 transition-colors"
|
||||||
|
>
|
||||||
<h3 className="font-semibold text-blue-900 mb-2">User Management</h3>
|
<h3 className="font-semibold text-blue-900 mb-2">User Management</h3>
|
||||||
<p className="text-blue-700 text-sm">Manage staff users and permissions</p>
|
<p className="text-blue-700 text-sm">View all users and their contact info</p>
|
||||||
</div>
|
</Link>
|
||||||
<div className="bg-green-50 p-4 rounded-lg">
|
{/*<div className="bg-green-50 p-4 rounded-lg">
|
||||||
<h3 className="font-semibold text-green-900 mb-2">System Settings</h3>
|
<h3 className="font-semibold text-green-900 mb-2">System Settings</h3>
|
||||||
<p className="text-green-700 text-sm">Configure system-wide settings</p>
|
<p className="text-green-700 text-sm">Configure system-wide settings</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-purple-50 p-4 rounded-lg">
|
<div className="bg-purple-50 p-4 rounded-lg">
|
||||||
<h3 className="font-semibold text-purple-900 mb-2">Analytics</h3>
|
<h3 className="font-semibold text-purple-900 mb-2">Analytics</h3>
|
||||||
<p className="text-purple-700 text-sm">View system analytics and reports</p>
|
<p className="text-purple-700 text-sm">View system analytics and reports</p>
|
||||||
</div>
|
</div>*/}
|
||||||
{user?.role?.name === 'super_admin' && (
|
{user?.role?.name === 'super_admin' && (
|
||||||
<Link
|
<Link
|
||||||
to="/super-admin"
|
to="/super-admin"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue