mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-30 12:06:58 -05:00
Compare commits
6 Commits
6acc1c97c7
...
4d21dc4435
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d21dc4435 | ||
|
|
cfda7e8d56 | ||
|
|
1c7ca8fe32 | ||
|
|
b3bb55ecd9 | ||
|
|
c8c210a6e5 | ||
|
|
2b8ee9061e |
@@ -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 { NavLink } from 'react-router-dom';
|
||||||
import { useUserRole } from '@/hooks/useUserRole';
|
import { useUserRole } from '@/hooks/useUserRole';
|
||||||
import { useSidebar } from '@/hooks/useSidebar';
|
import { useSidebar } from '@/hooks/useSidebar';
|
||||||
@@ -38,6 +38,11 @@ export function AdminSidebar() {
|
|||||||
url: '/admin/reports',
|
url: '/admin/reports',
|
||||||
icon: Flag,
|
icon: Flag,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Inbox',
|
||||||
|
url: '/admin/contact',
|
||||||
|
icon: Inbox,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'System Log',
|
title: 'System Log',
|
||||||
url: '/admin/system-log',
|
url: '/admin/system-log',
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
ArrowUpRight,
|
ArrowUpRight,
|
||||||
ArrowDownLeft,
|
ArrowDownLeft,
|
||||||
Loader2,
|
Loader2,
|
||||||
|
RefreshCw,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -50,6 +51,7 @@ import { handleError, handleSuccess } from '@/lib/errorHandler';
|
|||||||
import { logger } from '@/lib/logger';
|
import { logger } from '@/lib/logger';
|
||||||
import { contactCategories } from '@/lib/contactValidation';
|
import { contactCategories } from '@/lib/contactValidation';
|
||||||
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
|
||||||
|
import { AdminLayout } from '@/components/layout/AdminLayout';
|
||||||
|
|
||||||
interface ContactSubmission {
|
interface ContactSubmission {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -86,7 +88,7 @@ interface EmailThread {
|
|||||||
export default function AdminContact() {
|
export default function AdminContact() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const { isAdmin } = useUserRole();
|
const { isAdmin, loading: rolesLoading } = useUserRole();
|
||||||
const [statusFilter, setStatusFilter] = useState<string>('all');
|
const [statusFilter, setStatusFilter] = useState<string>('all');
|
||||||
const [categoryFilter, setCategoryFilter] = useState<string>('all');
|
const [categoryFilter, setCategoryFilter] = useState<string>('all');
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
@@ -97,23 +99,6 @@ export default function AdminContact() {
|
|||||||
const [emailThreads, setEmailThreads] = useState<EmailThread[]>([]);
|
const [emailThreads, setEmailThreads] = useState<EmailThread[]>([]);
|
||||||
const [loadingThreads, setLoadingThreads] = useState(false);
|
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
|
// Fetch contact submissions
|
||||||
const { data: submissions, isLoading } = useQuery({
|
const { data: submissions, isLoading } = useQuery({
|
||||||
queryKey: ['admin-contact-submissions', statusFilter, categoryFilter, searchQuery],
|
queryKey: ['admin-contact-submissions', statusFilter, categoryFilter, searchQuery],
|
||||||
@@ -275,6 +260,36 @@ export default function AdminContact() {
|
|||||||
return cat?.label || category;
|
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
|
// Theme-aware EmailThreadItem component
|
||||||
function EmailThreadItem({ thread }: { thread: EmailThread }) {
|
function EmailThreadItem({ thread }: { thread: EmailThread }) {
|
||||||
const isOutbound = thread.direction === 'outbound';
|
const isOutbound = thread.direction === 'outbound';
|
||||||
@@ -323,13 +338,24 @@ export default function AdminContact() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-8 max-w-7xl">
|
<AdminLayout>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8 flex items-start justify-between">
|
||||||
<h1 className="text-4xl font-bold mb-2">Contact Submissions</h1>
|
<div>
|
||||||
<p className="text-muted-foreground">
|
<h1 className="text-4xl font-bold mb-2">Contact Submissions</h1>
|
||||||
Manage and respond to user contact form submissions
|
<p className="text-muted-foreground">
|
||||||
</p>
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
@@ -675,6 +701,6 @@ export default function AdminContact() {
|
|||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</AdminLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'))
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user