Community 93 Premium feature complete

This commit is contained in:
Eric Gullickson
2025-12-21 11:31:10 -06:00
parent 1bde31247f
commit 95f5e89e48
60 changed files with 8061 additions and 350 deletions

View File

@@ -0,0 +1,179 @@
/**
* @ai-summary Mobile admin screen for community station reviews
*/
import React, { useState } from 'react';
import { Navigate } from 'react-router-dom';
import {
Box,
Select,
MenuItem,
FormControl,
InputLabel,
} from '@mui/material';
import { MobileContainer } from '../../../shared-minimal/components/mobile/MobileContainer';
import { useAdminAccess } from '../../../core/auth/useAdminAccess';
import { CommunityStationReviewQueue } from '../components/CommunityStationReviewQueue';
import { CommunityStationsList } from '../../stations/components/CommunityStationsList';
import {
usePendingSubmissions,
useAllCommunitySubmissions,
useReviewStation,
} from '../../stations/hooks/useCommunityStations';
import toast from 'react-hot-toast';
type TabType = 'pending' | 'all';
/**
* Mobile admin screen for reviewing community station submissions
* Touch-friendly layout with tab navigation
*/
export const AdminCommunityStationsMobileScreen: React.FC = () => {
const { isAdmin, loading } = useAdminAccess();
const [activeTab, setActiveTab] = useState<TabType>('pending');
const [statusFilter, setStatusFilter] = useState<string>('');
const [page, setPage] = useState(0);
// Hooks
const pendingSubmissions = usePendingSubmissions(page, 20);
const allSubmissions = useAllCommunitySubmissions(statusFilter || undefined, page, 20);
const reviewMutation = useReviewStation();
if (loading) {
return (
<MobileContainer>
<div className="flex items-center justify-center min-h-[400px]">
<div className="text-center">
<div className="text-slate-500 mb-2">Loading admin access...</div>
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto"></div>
</div>
</div>
</MobileContainer>
);
}
if (!isAdmin) {
return <Navigate to="/garage/settings" replace />;
}
// Handle approval
const handleApprove = async (id: string) => {
try {
await reviewMutation.mutateAsync({
id,
decision: { status: 'approved' },
});
toast.success('Station approved');
} catch (error: any) {
toast.error(error?.response?.data?.message || 'Failed to approve station');
}
};
// Handle rejection
const handleReject = async (id: string, reason: string) => {
try {
await reviewMutation.mutateAsync({
id,
decision: { status: 'rejected', rejectionReason: reason },
});
toast.success('Station rejected');
} catch (error: any) {
toast.error(error?.response?.data?.message || 'Failed to reject station');
}
};
const displayData = activeTab === 'pending' ? pendingSubmissions : allSubmissions;
const stations = displayData.data?.stations || [];
const totalPages = displayData.data?.total ? Math.ceil(displayData.data.total / 20) : 1;
return (
<MobileContainer>
<div className="space-y-4 pb-20 p-4">
{/* Header */}
<div className="text-center mb-6">
<h1 className="text-2xl font-bold text-slate-800">Community Station Reviews</h1>
<p className="text-slate-500 mt-1">Review user-submitted gas stations</p>
</div>
{/* Tab navigation */}
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
<button
onClick={() => {
setActiveTab('pending');
setPage(0);
}}
className={`flex-1 py-2 px-3 rounded-lg font-medium transition ${
activeTab === 'pending'
? 'bg-blue-600 text-white'
: 'bg-slate-100 text-slate-600'
}`}
>
Pending ({pendingSubmissions.data?.total || 0})
</button>
<button
onClick={() => {
setActiveTab('all');
setPage(0);
}}
className={`flex-1 py-2 px-3 rounded-lg font-medium transition ${
activeTab === 'all'
? 'bg-blue-600 text-white'
: 'bg-slate-100 text-slate-600'
}`}
>
All
</button>
</Box>
{/* Status filter for all tab */}
{activeTab === 'all' && (
<FormControl fullWidth size="small" sx={{ mb: 2 }}>
<InputLabel>Filter by Status</InputLabel>
<Select
value={statusFilter}
label="Filter by Status"
onChange={(e) => {
setStatusFilter(e.target.value);
setPage(0);
}}
>
<MenuItem value="">All</MenuItem>
<MenuItem value="pending">Pending</MenuItem>
<MenuItem value="approved">Approved</MenuItem>
<MenuItem value="rejected">Rejected</MenuItem>
</Select>
</FormControl>
)}
{/* Content */}
{activeTab === 'pending' ? (
<CommunityStationReviewQueue
stations={stations}
loading={displayData.isLoading}
error={displayData.error ? 'Failed to load submissions' : null}
onApprove={handleApprove}
onReject={handleReject}
page={page}
onPageChange={setPage}
totalPages={totalPages}
isReviewLoading={reviewMutation.isPending}
/>
) : (
<CommunityStationsList
stations={stations}
loading={displayData.isLoading}
error={displayData.error ? 'Failed to load submissions' : null}
isAdmin={true}
onApprove={handleApprove}
onReject={handleReject}
page={page}
onPageChange={setPage}
totalPages={totalPages}
/>
)}
</div>
</MobileContainer>
);
};
export default AdminCommunityStationsMobileScreen;