Compare commits

...

6 Commits

Author SHA1 Message Date
gpt-engineer-app[bot]
4d21dc4435 Fix RLS policy for contact submissions 2025-10-28 18:25:06 +00:00
gpt-engineer-app[bot]
cfda7e8d56 feat: Add refresh button to Admin Inbox 2025-10-28 18:19:16 +00:00
gpt-engineer-app[bot]
1c7ca8fe32 Fix AdminContact loading state 2025-10-28 18:17:10 +00:00
gpt-engineer-app[bot]
b3bb55ecd9 Fix: Ensure admin header and sidebar are present 2025-10-28 18:13:22 +00:00
gpt-engineer-app[bot]
c8c210a6e5 Fix: "Rendered more hooks than during previous render" error 2025-10-28 18:11:44 +00:00
gpt-engineer-app[bot]
2b8ee9061e Add Inbox to Admin Navigation 2025-10-28 18:10:38 +00:00
3 changed files with 73 additions and 26 deletions

View File

@@ -1,4 +1,4 @@
import { LayoutDashboard, FileText, Flag, Users, Settings, ArrowLeft, ScrollText, BookOpen } from 'lucide-react';
import { LayoutDashboard, FileText, Flag, Users, Settings, ArrowLeft, ScrollText, BookOpen, Inbox } from 'lucide-react';
import { NavLink } from 'react-router-dom';
import { useUserRole } from '@/hooks/useUserRole';
import { useSidebar } from '@/hooks/useSidebar';
@@ -38,6 +38,11 @@ export function AdminSidebar() {
url: '/admin/reports',
icon: Flag,
},
{
title: 'Inbox',
url: '/admin/contact',
icon: Inbox,
},
{
title: 'System Log',
url: '/admin/system-log',

View File

@@ -14,6 +14,7 @@ import {
ArrowUpRight,
ArrowDownLeft,
Loader2,
RefreshCw,
} from 'lucide-react';
import { supabase } from '@/integrations/supabase/client';
import { Button } from '@/components/ui/button';
@@ -50,6 +51,7 @@ import { handleError, handleSuccess } from '@/lib/errorHandler';
import { logger } from '@/lib/logger';
import { contactCategories } from '@/lib/contactValidation';
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
import { AdminLayout } from '@/components/layout/AdminLayout';
interface ContactSubmission {
id: string;
@@ -86,7 +88,7 @@ interface EmailThread {
export default function AdminContact() {
const queryClient = useQueryClient();
const { theme } = useTheme();
const { isAdmin } = useUserRole();
const { isAdmin, loading: rolesLoading } = useUserRole();
const [statusFilter, setStatusFilter] = useState<string>('all');
const [categoryFilter, setCategoryFilter] = useState<string>('all');
const [searchQuery, setSearchQuery] = useState('');
@@ -97,23 +99,6 @@ export default function AdminContact() {
const [emailThreads, setEmailThreads] = useState<EmailThread[]>([]);
const [loadingThreads, setLoadingThreads] = useState(false);
// Admin-only access check
if (!isAdmin()) {
return (
<div className="flex items-center justify-center min-h-screen">
<Card className="max-w-md">
<CardContent className="pt-6 text-center">
<AlertCircle className="h-12 w-12 text-destructive mx-auto mb-4" />
<h2 className="text-xl font-semibold mb-2">Access Denied</h2>
<p className="text-muted-foreground">
Email response features are only available to administrators.
</p>
</CardContent>
</Card>
</div>
);
}
// Fetch contact submissions
const { data: submissions, isLoading } = useQuery({
queryKey: ['admin-contact-submissions', statusFilter, categoryFilter, searchQuery],
@@ -275,6 +260,36 @@ export default function AdminContact() {
return cat?.label || category;
};
const handleRefreshSubmissions = () => {
queryClient.invalidateQueries({ queryKey: ['admin-contact-submissions'] });
};
// Show loading state while roles are being fetched
if (rolesLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<p className="text-muted-foreground">Loading...</p>
</div>
);
}
// Admin-only access check (after loading complete)
if (!isAdmin()) {
return (
<div className="flex items-center justify-center min-h-screen">
<Card className="max-w-md">
<CardContent className="pt-6 text-center">
<AlertCircle className="h-12 w-12 text-destructive mx-auto mb-4" />
<h2 className="text-xl font-semibold mb-2">Access Denied</h2>
<p className="text-muted-foreground">
Email response features are only available to administrators.
</p>
</CardContent>
</Card>
</div>
);
}
// Theme-aware EmailThreadItem component
function EmailThreadItem({ thread }: { thread: EmailThread }) {
const isOutbound = thread.direction === 'outbound';
@@ -323,13 +338,24 @@ export default function AdminContact() {
};
return (
<div className="container mx-auto px-4 py-8 max-w-7xl">
<AdminLayout>
{/* Header */}
<div className="mb-8">
<h1 className="text-4xl font-bold mb-2">Contact Submissions</h1>
<p className="text-muted-foreground">
Manage and respond to user contact form submissions
</p>
<div className="mb-8 flex items-start justify-between">
<div>
<h1 className="text-4xl font-bold mb-2">Contact Submissions</h1>
<p className="text-muted-foreground">
Manage and respond to user contact form submissions
</p>
</div>
<Button
variant="outline"
size="icon"
onClick={handleRefreshSubmissions}
disabled={isLoading}
title="Refresh submissions"
>
<RefreshCw className={`h-4 w-4 ${isLoading ? 'animate-spin' : ''}`} />
</Button>
</div>
{/* Stats Cards */}
@@ -675,6 +701,6 @@ export default function AdminContact() {
)}
</DialogContent>
</Dialog>
</div>
</AdminLayout>
);
}

View File

@@ -0,0 +1,16 @@
-- Fix RLS policy that's causing "permission denied for table users" error
-- The issue is the policy tries to SELECT from auth.users which isn't allowed
-- Drop the problematic policy
DROP POLICY IF EXISTS "Users can view own contact submissions" ON public.contact_submissions;
-- Recreate it using auth.jwt() to get email instead of querying auth.users
CREATE POLICY "Users can view own contact submissions"
ON public.contact_submissions
FOR SELECT
TO public
USING (
user_id = auth.uid()
OR
(auth.uid() IS NOT NULL AND email = (auth.jwt() ->> 'email'))
);